ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

水下机器人仿真:uuv_simulator环境搭建与插件开发实战

水下机器人仿真:uuv_simulator环境搭建与插件开发实战 1. 水下机器人仿真到底在仿什么第一次接触水下机器人仿真的人脑子里冒出来的问题通常很直接水下的东西为什么非得在电脑里先跑一遍直接扔进水池里试不行吗行但代价太大。一台中小型ROV下水一次要协调场地、人员、供电、吊装、安全监护一天下来成本轻松过万。更麻烦的是水下实验的“可观测性”极差——你在岸上只能看到几个姿态角、深度和推进器电流至于机器人到底是哪个环节出了问题往往要靠猜。而仿真环境里你可以随时暂停、回放、把任意一个关节的受力曲线拉出来看这种“上帝视角”是真实水池给不了的。uuv_simulator就是干这件事的。它是一套跑在ROS和Gazebo上的开源仿真包专门用来模拟水下无人航行器UUVUnmanned Underwater Vehicle包括ROV遥控潜水器和AUV自主水下航行器。它把水动力学、浮力、推进器推力、传感器噪声、水下通信延迟这些东西都塞进了Gazebo的物理引擎里让你在没有任何硬件的情况下就能把控制算法、导航策略、任务逻辑跑通。这套东西适合谁三类人最需要它。第一类是做水下机器人控制算法研究的学生和工程师你不可能每次都拿真机去调PID第二类是做多机器人协同的团队仿真里开十台UUV比造十台便宜太多第三类是想入门ROS但不想只玩 turtlesim 的开发者水下场景的复杂度刚好能让你把ROS的通信机制、TF变换、插件系统全部过一遍。但说实话uuv_simulator的文档不算友好版本兼容性也经常让人头疼。我见过太多人卡在“Gazebo能启动但机器人不动”或者“推进器转了但机器人不往前走”这种问题上。下面我把整个搭建流程和插件开发的关键点拆开讲尽量让你少走弯路。2. 环境搭建从零到能跑起来2.1 版本选择与依赖关系uuv_simulator对ROS和Gazebo的版本相当敏感。目前社区里跑得最稳的组合是Ubuntu 20.04 ROS Noetic Gazebo 11。如果你用的是Ubuntu 22.04ROS 2 Humble 下也有对应的移植版本但插件生态没有Noetic那么全很多老教程里的launch文件直接拿过来会报错。为什么版本这么重要因为uuv_simulator的核心是一组Gazebo插件而Gazebo的插件API在版本之间有过几次不兼容的改动。比如gazebo::physics::Model的某些接口在Gazebo 9和Gazebo 11里签名就不一样。你如果混用编译能过运行时直接段错误。安装依赖的时候除了ROS桌面完整版还需要额外装几个包sudo apt install ros-noetic-uuv-simulator但我不建议直接apt装。原因有两个一是apt里的版本往往落后于GitHub仓库二是你后面要改插件源码apt装的包改起来很别扭。更稳妥的做法是从源码编译cd ~/catkin_ws/src git clone https://github.com/uuvsimulator/uuv_simulator.git cd .. rosdep install --from-paths src --ignore-src -r -y catkin_make这里有个坑rosdep有时候会漏掉gazebo_ros相关的依赖你需要手动确认gazebo_ros_pkgs已经装好。另外如果你之前装过其他版本的Gazebocatkin_make可能会链接到错误的库编译前先用gazebo --version确认一下。2.2 工作空间编译的常见报错编译阶段最容易遇到的是Could not find a package configuration file provided by uuv_gazebo_plugins。这通常是因为catkin_make的依赖顺序没处理好。解决办法是先单独编译插件包catkin_make --pkg uuv_gazebo_plugins catkin_make还有一个经典问题是Protobuf版本冲突。Gazebo 11 依赖 protobuf 3.6而某些ROS包会拉进来更新的版本。如果你看到undefined reference to google::protobuf::...这类链接错误基本就是这个问题。排查方法是ldd devel/lib/libuuv_gazebo_plugins.so | grep protobuf看看它链接的是哪个路径下的库。如果指向了/usr/local/lib而不是/usr/lib/x86_64-linux-gnu说明你系统里装了多个protobuf需要调整CMAKE_PREFIX_PATH或者干脆把多余的版本卸掉。提示编译前先source /opt/ros/noetic/setup.bash编译后再source devel/setup.bash这两个顺序不能反。我见过有人先source了devel再source ROS结果环境变量被覆盖运行时找不到插件。2.3 启动第一个仿真场景编译通过后先跑一个最简单的场景验证环境roslaunch uuv_gazebo_worlds herkules_ship_wreck.launch这个launch会启动Gazebo加载一个海底沉船的世界里面有一台Herkules ROV。如果Gazebo能正常打开左侧模型树里能看到uuv_herkules说明基础环境没问题。但很多人到这里会遇到“Gazebo界面一直在闪”的情况。这个问题通常和显卡驱动有关。Gazebo默认用OpenGL渲染如果你的机器用的是Nouveau开源驱动或者虚拟机里的软件渲染界面就会疯狂闪烁甚至卡死。解决办法是强制使用软件渲染export LIBGL_ALWAYS_SOFTWARE1或者换用专有驱动。如果你在虚拟机里跑建议至少分配4GB显存和8GB内存否则Gazebo加载海底地形模型时会直接OOM。3. 核心插件体系拆解3.1 推进器插件推力是怎么算出来的uuv_simulator里最核心的插件是推进器插件ThrusterPlugin。它的作用是把ROS发过来的推力指令转换成Gazebo物理引擎能理解的力和力矩作用在机器人模型上。推进器的建模方式有好几种最常用的是基于螺旋桨转速的模型。你给推进器一个转速指令单位是RPM插件根据螺旋桨的推力系数和扭矩系数算出推力和反扭矩。公式大致是这样的T rho * D^4 * K_T * n^2 Q rho * D^5 * K_Q * n^2其中rho是水的密度D是螺旋桨直径K_T和K_Q是无量纲的推力系数和扭矩系数n是转速转每秒。这两个系数通常来自螺旋桨的敞水特性曲线你可以从螺旋桨厂商的datasheet里查到或者用OpenProp这类工具算出来。为什么不用简单的“推力系数×指令”线性模型因为水下推进器的推力跟转速是平方关系线性模型在低速段误差很大。你如果做的是精细的悬停控制线性模型会导致机器人要么不动要么突然窜出去。在URDF或SDF文件里推进器的配置大概长这样plugin namethruster_0 filenamelibuuv_thruster_plugin.so link_namethruster_0/link_name propeller_diameter0.1/propeller_diameter thrust_coefficient0.25/thrust_coefficient torque_coefficient0.02/torque_coefficient fluid_density1025/fluid_density max_thrust100/max_thrust /pluginfluid_density默认是海水密度1025 kg/m³如果你在淡水池里做实验改成1000。max_thrust是保护值防止指令过大导致物理引擎数值爆炸。3.2 水动力学插件浮力、阻力和附加质量水下机器人和空中无人机最大的区别在于水是不可忽略的介质。uuv_simulator用HydrodynamicsPlugin来处理这些力。浮力这块相对简单就是阿基米德原理浮力等于排开水的重力。插件会根据机器人的体积和重心/浮心位置自动计算浮力和浮力矩。这里的关键参数是center_of_buoyancy也就是浮心坐标。如果浮心在重心上方机器人天然稳定如果浮心在重心下方机器人就会翻。很多新手搭的模型一下水就底朝天八成是浮心位置设错了。阻力就复杂多了。水阻力跟速度的平方成正比而且不同方向的阻力系数差别巨大。一个细长的鱼雷形AUV轴向阻力系数可能只有0.1但侧向阻力系数能到1.0以上。插件里用的是一个6×6的阻尼矩阵你需要在SDF里把矩阵的各个元素填进去plugin namehydrodynamics filenamelibuuv_hydrodynamics_plugin.so link_namebase_link/link_name linear_damping x10.0/x y50.0/y z50.0/z /linear_damping quadratic_damping x5.0/x y20.0/y z20.0/z /quadratic_damping /plugin这些系数怎么来的有经验的团队会做CFD仿真或者拖曳水池实验。没有条件的话可以用经验公式估算比如根据机器人的迎流面积和形状因子。我个人的做法是先用保守值偏大跑起来之后再根据仿真结果微调。附加质量Added Mass是另一个容易被忽略的效应。当机器人加速时它周围的水也会被带动相当于机器人的“有效质量”变大了。对于水下机器人附加质量能达到本体质量的30%到100%。插件里通过added_mass矩阵来配置如果你不设机器人会显得比实际“轻快”控制参数到了真机上就不准了。3.3 传感器插件IMU、DVL和深度计仿真里如果没有传感器那跟动画片没区别。uuv_simulator提供了几种水下常用的传感器插件。IMU插件输出三轴加速度、角速度和姿态。Gazebo自带的IMU插件就能用但水下场景里要注意加噪声。真实的MEMS IMU在静态下也有零偏和随机游走你如果仿真里用理想IMU调出来的姿态估计算法到了真机上会发散。建议在插件配置里加上高斯噪声imu angular_velocity xnoise typegaussianmean0/meanstddev0.0002/stddev/noise/x /angular_velocity /imuDVL多普勒测速仪插件用来测对地速度。它的原理是发射声波根据回波的多普勒频移算速度。仿真里简化成了直接输出机器人相对于海底的速度但你可以加上“底跟踪丢失”的逻辑——当机器人离海底太远时DVL输出无效值。这个在真实DVL里是常见现象仿真里加上能让你的导航算法更鲁棒。深度计插件其实就是压力传感器根据水深算静水压。p rho * g * h反过来h p / (rho * g)。这个没什么好说的但要注意Gazebo里的压力传感器默认是在大气环境下的你需要把reference_pressure设成水面大气压。4. 从零开发一个自定义Gazebo插件4.1 插件的基本骨架Gazebo插件本质上就是一个动态链接库Gazebo在运行时加载它然后调用里面的回调函数。一个标准的模型插件需要继承gazebo::ModelPlugin类实现Load和Reset两个虚函数。#include gazebo/gazebo.hh #include gazebo/physics/physics.hh namespace gazebo { class MyUUVPlugin : public ModelPlugin { public: void Load(physics::ModelPtr model, sdf::ElementPtr sdf) override { this-model model; this-updateConnection event::Events::ConnectWorldUpdateBegin( std::bind(MyUUVPlugin::OnUpdate, this)); } void Reset() override {} private: void OnUpdate() { // 每个仿真步都会调用这里 } physics::ModelPtr model; event::ConnectionPtr updateConnection; }; GZ_REGISTER_MODEL_PLUGIN(MyUUVPlugin) }Load函数在插件加载时调用一次你在这里读取SDF参数、初始化ROS节点、订阅话题。OnUpdate在每个仿真步被调用你在这里做实时计算。为什么用ConnectWorldUpdateBegin而不是自己开一个线程因为Gazebo的物理引擎不是线程安全的。你在自己的线程里读写模型状态很容易跟物理引擎的更新冲突导致数据竞争甚至崩溃。ConnectWorldUpdateBegin保证你的代码在物理引擎更新之前执行时序是确定的。4.2 ROS通信的接入方式插件里要跟ROS通信有两种方式。一种是直接在插件里初始化一个ros::NodeHandle另一种是用Gazebo的ROS API。我推荐第一种因为更灵活。#include ros/ros.h #include std_msgs/Float64.h void Load(...) { if (!ros::isInitialized()) { int argc 0; char** argv nullptr; ros::init(argc, argv, my_uuv_plugin, ros::init_options::NoSigintHandler); } this-nh std::make_uniqueros::NodeHandle(); this-sub this-nh-subscribe(thruster_cmd, 10, MyUUVPlugin::CmdCallback, this); }这里有个细节ros::init的第三个参数是节点名如果多个插件用同一个节点名ROS会报错。建议在节点名里加上模型名或者随机后缀。另外ros::init_options::NoSigintHandler这个选项很重要它防止插件接管CtrlC信号否则你关Gazebo的时候可能会卡住。4.3 推力分配与推进器布局一台ROV通常有4到8个推进器你从控制器拿到的是期望的力和力矩比如前进力、侧向力、偏航力矩需要分配给各个推进器。这就是推力分配矩阵要解决的问题。假设你有6个推进器布局是4个水平、2个垂直。水平推进器呈45度角布置那么推力分配矩阵大概是这样的[Fx] [cos45 cos45 -cos45 -cos45] [T1] [Fy] [sin45 -sin45 sin45 -sin45] [T2] [Mz] [ -l l -l l ] [T3] [T4]其中l是推进器到机器人中心的力臂。这个矩阵的伪逆就是分配矩阵。你可以在插件里硬编码这个矩阵也可以从SDF参数里读。实际做的时候推进器还有饱和限制。如果某个推进器需要的推力超过了它的最大值你得做饱和处理同时尽量保持合力矩不变。最简单的做法是等比例缩放但更好的方法是解一个带约束的优化问题。我试过用Eigen的QuadProg来解效果不错但计算量比等比例缩放大不少在1kHz的仿真步长下要小心。5. 仿真调试与问题排查实录5.1 机器人不动或者乱飞这是最高频的问题。排查顺序我一般是这样的现象可能原因排查方法推进器不转插件没加载看Gazebo终端有没有libuuv_thruster_plugin.so加载日志推进器转但机器人不动推力方向反了在Gazebo里开Wireframe看力的箭头方向机器人原地打转浮心重心位置不对检查SDF里的center_of_buoyancy和惯性矩阵机器人突然飞出去物理参数数值爆炸检查max_thrust和阻尼系数是否合理推进器方向反了这个问题特别常见。因为URDF里关节的坐标系定义很容易搞混你以为推进器朝前实际上朝后。我的做法是在Gazebo里把推进器的力可视化打开visualizetrue/visualize这样你能直接看到每个推进器输出的力向量一眼就能判断方向对不对。5.2 仿真速度太慢水下场景的模型面数通常很高海底地形、沉船、机器人本体加起来几十万个三角面Gazebo的实时率RTF可能只有0.2甚至更低。这意味着仿真1秒需要5秒真实时间调参效率极低。优化手段有几个。第一降低视觉模型的精度用简化的碰撞体代替复杂的视觉网格。Gazebo里视觉和碰撞是分开的你可以给机器人一个精细的视觉模型和一个简单的碰撞体比如几个盒子物理计算量能降一个数量级。第二关掉不必要的光照和阴影在.world文件里把scene的shadows设为 false。第三如果做控制算法验证可以把物理步长从1ms放宽到4ms精度损失可接受速度提升明显。注意物理步长改大之后接触力的计算会变粗糙如果你的机器人有跟海底的接触交互可能会出现穿透。这种情况还是老老实实用1ms。5.3 插件编译通过但运行时找不到符号这个问题的典型报错是undefined symbol: _ZN...。原因通常是插件编译时用的Gazebo版本跟运行时加载的版本不一致。比如你编译时链接的是Gazebo 9的头文件但系统里跑的是Gazebo 11。排查方法是ldd libMyPlugin.so | grep gazebo看看它链接的libgazebo_physics.so在哪个路径。如果路径不对在CMakeLists.txt里显式指定find_package(gazebo REQUIRED) include_directories(${GAZEBO_INCLUDE_DIRS}) link_directories(${GAZEBO_LIBRARY_DIRS})还有一个坑是C ABI不兼容。Gazebo 11用的是C14如果你的插件用C17编译某些标准库符号的mangled name会不一样。保险起见插件的CMakeLists.txt里统一设成C14。6. 进阶玩法多UUV协同与任务仿真单台UUV跑通之后下一步通常是多机器人协同。uuv_simulator支持在同一个世界里生成多个UUV每个UUV有独立的命名空间和话题。启动多机器人场景时launch文件里用group标签给每个机器人分配不同的nsgroup nsuuv1 include file$(find uuv_gazebo_ros_plugins)/launch/start_thruster_manager.launch arg nameuuv_name valueuuv1/ /include /group每个机器人的话题会自动加上命名空间前缀比如/uuv1/thruster_cmd、/uuv2/thruster_cmd。你在写控制器的时候注意不要订阅错了话题。多机器人仿真里最耗资源的是TF变换。每台UUV都有自己的base_link到odom的变换如果都用同一个tf前缀RViz里会乱成一团。解决办法是给每台UUV的TF加前缀在URDF里用robotNamespace参数控制。任务级仿真方面你可以用uuv_simulator自带的uuv_control_utils包来做轨迹跟踪。它提供了几种控制器PID、滑模、反步法。我试过PID和滑模PID调起来简单但抗扰能力一般滑模在存在海流扰动时表现更好但抖振问题需要处理。如果你做的是精细作业比如水下对接建议用反步法理论保证更好但参数整定需要一些经验。海流扰动可以在世界文件里配置plugin nameocean_current filenamelibuuv_ocean_current_plugin.so current_velocity0.5 0.0 0.0/current_velocity current_direction1.0 0.0 0.0/current_direction /plugin这个插件会给所有水下模型施加一个均匀流场。注意海流是随深度变化的表面流速大深处流速小你可以用velocity_profile来定义分层流速。7. 一些踩坑之后的经验之谈Gazebo的物理引擎在接触力计算上有个特点它用的是软接触模型接触刚度不是无穷大。这意味着如果你的机器人质量很大而接触刚度设得不够机器人会慢慢陷进海底。解决办法是调大kp和kd参数但调太大又会导致数值不稳定。我的经验是kp设在 1e6 到 1e7 之间kd设在kp的百分之一左右具体值要根据机器人质量试出来。ROS的话题队列长度也值得注意。推进器指令话题的队列如果设成10在仿真卡顿的时候会积压指令导致机器人动作滞后。我一般把控制指令的队列设成1保证控制器发出来的永远是最新指令。还有一点uuv_simulator的默认推进器模型是理想化的没有考虑螺旋桨的进速效应。也就是说机器人前进速度越快螺旋桨的实际推力会越小因为来流速度增加了。如果你做的是高速航行器的仿真需要自己扩展推进器模型把进速系数加进去。这个改动不大在ThrusterPlugin的OnUpdate里根据机器人当前速度修正一下转速就行。最后说一个调试技巧在Gazebo里按CtrlShiftR可以录屏把整个仿真过程录下来。调参的时候反复回看比盯着实时画面有效得多。尤其是那种偶发的振荡或者发散录下来慢放很容易定位到是哪个环节出了问题。
返回列表