资讯动态

Gazebo仿真UUV水下机器人环境搭建与人工势场避障算法实践

发布时间:2026/9/29 2:35:14 来源:尧图企业网站定制
先说结论如果你是想用 Gazebo 仿真 UUV 水下机器人并且希望从零开始把环境搭起来、再把避障算法跑通这篇内容应该能帮你省掉至少一周的摸索时间。这里不会讲太多虚的东西而是按照我实际踩坑走通的路径从 Gazebo 仿真 UUV 水下机器人的环境搭建讲起最后落到一个人工势场避障算法的完整实现。整个过程覆盖了版本选型、uuv_simulator 安装、水下模型物理参数、声呐传感器配置以及避障节点的代码与调参思路。适合刚接触水下机器人仿真、有一定 ROS 基础但没碰过水下物理模型的人也适合那些已经装好了 Gazebo、却被水下动力学搞到怀疑人生的朋友。1. 为什么用 Gazebo 做水下机器人仿真1.1 水下仿真和空中/地面仿真的本质差异很多人从轮式机器人或四旋翼转到水下机器人第一反应是“不都是 ROS Gazebo 吗改个模型不就行了”。这个想法我一开始也有结果被现实狠狠教育了一顿。水下仿真和地面、空中仿真最大的区别在于你面对的不是一个可以用简单运动方程糊弄过去的系统而是一个充满流体耦合、浮力、阻力和科氏力的复杂动力学对象。在地面机器人里轮胎和地面的摩擦力模型虽然麻烦但大多数场景下你给个速度指令它就能按预期走。四旋翼稍微复杂一点但动力学模型已经被无数人封装好甚至 Gazebo 里直接有现成的插件。水下的情况完全不一样。UUV 在运动过程中前后左右上下都会有水的作用力而且这些作用力是速度的强函数。速度越快阻力越大姿态稍微倾斜浮力产生的恢复力矩就会拉着机器人回正。更麻烦的是附加质量效应——水会随着机器人一起加速运动等效质量比机器人本体质量大不少这直接导致水下机器人在制动和转向时显得“迟钝”。所以在 Gazebo 里做 UUV 仿真本质上就是先解决“如何让仿真环境里有一个动力学行为接近真实 UUV 的模型”这个问题。如果这一步不解决后面谈避障算法都是空中楼阁。1.2 为什么选 Gazebo 而不是其他仿真器水下仿真其实也有不少专用工具比如 UWSim、VRX、MORSE还有一些商业软件。但我的选择一直是 Gazebo原因很直白开源免费、资料多、插件体系成熟而且有一个专门的 uuv_simulator 生态。uuv_simulator 是 GitHub 上 uuvsimulator 组织维护的一套开源工具包里面包含了完整的 UUV 动力学插件、浮力插件、推进器模型和多种现成的水下机器人模型。也就是说你不需要自己从零手写水下动力学方程只需要把机器人的质量、体积、浮心、重心、推进器布局这些参数配好Gazebo 插件就会替你算浮力、阻力、附加质量这些让人头大的东西。这对绝大多数做算法研究的人来说是巨大的解放。相比其他仿真器Gazebo 还有一个天然优势和 ROS 是深度绑定的。水下机器人算法栈里大量用到的 topic 通信、rviz 可视化、launch 文件管理在 Gazebo 里都是一等公民。你可以很自然地把后面的避障节点跑在 ROS 里通过 topic 和仿真器里的声呐模型交互整个开发体验非常顺畅。1.3 UUV 仿真要解决哪些核心问题把 Gazebo 仿真 UUV 这件事拆开看无外乎四块。第一是建模也就是让仿真器里的机器人具备合理的水下受力和运动特性这包括浮力、阻力、附加质量以及推进器作用力。第二是感知需要给机器人装上一套仿真传感器最常见的就是声呐、IMU 和深度计不然避障算法就是一个空壳。第三是控制底层要能把期望速度或期望航向转成推进器指令让机器人真正动起来。第四是场景仿真环境里要有明确的障碍物和目标点算法才能有评估对象。这四块环环相扣。建模不对感知数据就是错的感知不对控制就是在瞎指挥控制不稳避障算法再优美也没机会展示。所以这篇内容也会按照这个顺序讲而不是一上来就塞一段避障代码。2. 开发环境搭建2.1 版本组合怎么选环境搭建里第一个坑就是版本组合。Gazebo 本身不是一个独立软件它和 ROS 的版本、Ubuntu 系统的版本是绑死的。选错组合后面每一步都可能出现莫名其妙的编译错误和模型加载问题。以我实际经验来看如果想要最稳妥、最省心的方案推荐 Ubuntu 20.04 ROS Noetic Gazebo 11。这也是 uuv_simulator 支持最完善的一条路线。很多官方 demo 和插件都优先在这条链路上测试过遇到问题也最容易搜到解决方案。如果你非要尝试 ROS2比如 Ubuntu 22.04 ROS2 Humble Gazebo Classic理论上也能跑但 uuv_simulator 的 ROS2 分支还在持续完善中部分插件的编译和运行会有适配问题调试成本会明显上升。至于 Ubuntu 24.04 ROS2 Jazzy Gazebo Harmonic 这种最新组合我不建议在跑 uuv_simulator 的时候用很多插件还没有迁移到新版本到时候光是处理兼容性问题就够喝一壶的。贴一张我整理过的版本匹配表组合系统版本ROS 版本Gazebo 版本适用情况经典稳定Ubuntu 20.04NoeticGazebo 11首选uuv_simulator 支持最完善过渡组合Ubuntu 22.04HumbleGazebo Classic 11需要 ROS2 时选用前沿实验Ubuntu 24.04JazzyGazebo Harmonic推荐新技术但不适合 uuv_simulator2.2 ROS Gazebo 安装实操系统装好之后先把软件源更新到最新然后装 ROS Noetic 桌面完整版。直接安装 gazebo 相关包ros-noetic-desktop-full 其实已经自带 Gazebo 11 的基本库但为了方便控制机器人我会额外装 ros-noetic-gazebo-ros-pkgs 和 ros-noetic-gazebo-ros-control。核心命令大致是这样sudo apt update sudo apt install curl curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - sudo sh -c echo deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main /etc/apt/sources.list.d/ros-latest.list sudo apt install ros-noetic-desktop-full sudo apt install ros-noetic-gazebo-ros-pkgs ros-noetic-gazebo-ros-control sudo apt install python3-rosdep python3-rosinstall build-essential sudo rosdep init rosdep update echo source /opt/ros/noetic/setup.bash ~/.bashrc source ~/.bashrc这里要提醒一个新手容易忽略的细节如果你同时装了 ROS1 和 ROS2一定不要把它们写入同一个 .bashrc 的自动 source 里否则环境变量的互相污染会让你连 gazebo 都起不来。我对多个 ROS 版本的态度很简单能分开就分开最好给不同工作空间单独写 setup 脚本用的时候手动 source。2.3 安装 uuv_simulator 仿真包环境干净了下面就是重头戏安装 uuv_simulator。这个包不推荐 apt 直接装因为版本往往比较旧最好从源码编译这样才能确保和你的 ROS 版本对上。先把工作空间准备好然后克隆仓库。这里有一个经验之谈克隆之前先到 GitHub 上看一眼仓库的远程分支确认有没有适配你当前 ROS 版本的分支。uuv_simulator 目前有针对 Noetic、Foxy、Humble 等版本的分支选错了分支后面编译大概率会报错。mkdir -p ~/uuv_ws/src cd ~/uuv_ws/src git clone https://github.com/uuvsimulator/uuv_simulator.git cd ~/uuv_ws source /opt/ros/noetic/setup.bash rosdep install --from-paths src --ignore-src -r -y catkin_make echo source ~/uuv_ws/devel/setup.bash ~/.bashrc source ~/.bashrcrosdep 这步很多人会跳过但我建议无论如何都要执行。uuv_simulator 依赖的一堆系统库如果没装上编译过程中的报错会让你觉得是代码问题其实是缺依赖。卡在这里的时候先别急着改代码八成是依赖没装全。安装完成后还有一步非常重要确认 GAZEBO_MODEL_PATH 环境变量是否正常工作。uuv_simulator 的模型文件依赖这个路径才能被 Gazebo 找到。source 完工作空间后执行下面命令能看到你工作空间下的 uuv_simulator 路径就说明正常echo $GAZEBO_MODEL_PATH2.4 验证启动第一个水下世界如果环境没问题现在可以启动一个最简单的水下世界来验证一下。uuv_simulator 自带了好几个 demo最直接的是 ocean_waves 世界和 rexrov2 模型 demo。命令行输入roslaunch uuv_gazebo_worlds ocean_waves.launch这个命令会同时启动 Gazebo 和一个有波浪效果的水下场景。看到熟悉的 Gazebo 窗口、场景里有一条水下机器人模型、左下角没有报错信息恭喜你环境搭建这一步算是过了。启动之后别急着关先在另一个终端里确认一下话题和节点是否正常。执行 rosnode list 能看到 gazebo 相关节点rostopic list 能看到 /gazebo/model_states、/gazebo/parameter_descriptions 这类基础话题。如果 model_states 里出现了 rexrov2 或者其他模型名称说明 UUV 模型已经正常加载到仿真世界中了。这个验证步骤别省我见过太多人环境没起来就急着跑算法最后根本分不清是算法问题还是环境问题。3. UUV 仿真模型与水下物理属性3.1 常用 UUV 模型怎么选uuv_simulator 里提供了好几种 UUV 模型常见的有 rexrov2、lauv、egof 这些。它们的差别主要体现在尺寸、推进器数量和驱动方式上。如果是新手入门我强烈推荐 rexrov2。它不是最大的模型但推进器布局清爽水平方向和垂直方向都有独立的推进器属于全驱动模型方便你直接用速度和力去控制。而且 rexrov2 的模型文件、传感器配置和示例 topic 都写得比较完整避障演示时不太容易被底层控制折腾到怀疑人生。lauv 是一个更紧凑的小型 UUV适合做编队或狭窄环境测试。egof 则偏向于中等尺寸的科研平台配置更加复杂不太适合第一天就上手。选模型可以遵循一个原则先跑官方 demo 跑顺了再换到自己关心的模型。不要一上来就加载一个没有官方教程支援的模型那大概率会变成一场调试马拉松。3.2 水下物理关键参数浮力、阻力、附加质量有些人在 Gazebo 里加载 UUV 模型后发现机器人要么慢慢沉底要么慢慢飘到水面甚至一动不动此时先别怀疑方向控制的问题问题几乎都出在水下物理参数上。uuv_simulator 的水下物理插件里有三个参数组是必须搞清楚的。第一是浮力模型核心是浮力与重力的关系。机器人自身有一个重力同时排水体积会提供一个向上的浮力。如果重力大于浮力机器人会下沉如果浮力大于重力机器人会浮到水面。一个稳定的 UUV 应该让两者略微平衡且浮心要位于重心上方这样才会有自稳的恢复力矩。你在模型文件里看到的 buoyancy 相关参数就是这个逻辑。第二是阻力模型。水流对机器人的阻力分为线性和二次阻力。线性阻力类似于阻尼在低速时显著二次阻力随速度平方增加在高速时主导。如果这两个系数配得太小机器人就会像在真空中游泳一样给一点力就飘出去停都停不下来。这也是水下机器人在仿真中“发飘”最常见的原因。第三是附加质量。这个概念很多做地面机器人的人第一次接触时会很不适应。简单说水会随着机器人一起运动导致机器人的等效惯性质量大于它的物理质量。因此在调整最大加速度、速度增益时你会发现机器人的响应总是慢半拍。这个慢半拍不是控制算法的问题而是流体带来的惯性在调 PID 时一定要把这个因素考虑进去。你不需要从零手写这些物理方程——uuv_simulator 的 gazebo 插件已经把这些物理效果都实现了你只需要在 URDF/Xacro 模型文件里把质量和排水体积、浮心位置等参数配好。但理解这些参数背后的含义会在你调试时节省大量时间因为很多奇怪的仿真行为用参数直觉就能判断出来源。3.3 传感器模型声呐、IMU、深度计模型能动起来之后下一步就是感知。UUV 在水下最核心的传感器是声呐因为水里的光学能见度通常很差避障主要靠声学手段。uuv_simulator 提供了声呐传感器仿真常见实现是用 Gazebo 的 ray sensor 模拟多波束声呐输出一个类似 LaserScan 的消息类型话题里是一组距离值代表机器人在不同角度上探测到的障碍物距离。理解成“水下版激光雷达”也可以虽然物理原理不同但在避障算法层面接口是类似的。除了声呐还需要 IMU 来提供姿态和角速度信息深度计提供下游深度数据。在 uuv_simulator 的模型配置里这些传感器都会作为 link 挂载到机器人模型上并发布出对应的 ROS 话题。调试的时候你可以在 rviz 里订阅这些话题直观看到机器人在水下的姿态和探测到的障碍物点云或射线这比看 terminal 里的数字要高效得多。4. 避障算法实战4.1 避障问题定义和算法选型模型和传感器都正常了终于到了这篇的核心避障算法。先想清楚问题定义机器人在某个水下场景里要从当前点运动到目标点场景里散布着若干障碍物机器人在运动过程中需要实时用声呐感知周围障碍物并且动态调整运动方向避免碰撞。这是典型的局部避障问题和地面移动机器人里的“局部路径规划”非常像。水下避障的主流方法有很多比如人工势场法、VFH、动态窗口法 DWA以及某些基于采样和优化的方法。但对于第一次在 uuv_simulator 里跑通避障的人来说我建议一定从人工势场法 APF 开始。原因在于它实现逻辑最简单物理直觉也最清晰目标点产生吸引力障碍物产生排斥力机器人沿着合力的方向走。也许在复杂场景里它不够完美但作为理解 UUV 避障全流程的第一课再合适不过。读到这里你可能已经体会到Gazebo 仿真 UUV 水下机器人这条路径里环境搭建是身体物理模型是骨架传感器是眼睛而避障算法才是最后真正跑起来的“大脑”。这四者的顺序不能乱前面的基础不牢后面代码写得再漂亮也演示不出效果。4.2 人工势场法节点实现下面这段代码是我在实际项目里整理出来的简化版人工势场法节点用 ROS1 的 rospy 实现。它的设计思路是订阅声呐扫描数据从前方探测范围内找到最近的障碍物计算出排斥力方向同时订阅一个目标点话题计算出吸引力方向两者叠加后经过幅度限制作为机器人坐标系下的速度指令发布出去。#!/usr/bin/env python3 import rospy import math from sensor_msgs.msg import LaserScan from geometry_msgs.msg import Twist, PoseStamped from nav_msgs.msg import Odometry class UUVAPF: def __init__(self): self.d0 3.0 self.k_rep 1.2 self.k_att 0.8 self.max_speed 1.0 self.goal None self.position None self.scan None rospy.Subscriber(/scan, LaserScan, self.scan_cb) rospy.Subscriber(/goal_pose, PoseStamped, self.goal_cb) rospy.Subscriber(/odom, Odometry, self.odom_cb) self.cmd_pub rospy.Publisher(/cmd_vel, Twist, queue_size1) def scan_cb(self, msg): self.scan msg def goal_cb(self, msg): self.goal msg.pose def odom_cb(self, msg): self.position msg.pose.pose def calc_repulsion(self): if self.scan is None: return 0.0, 0.0 min_range float(inf) min_angle 0.0 center len(self.scan.ranges) // 2 start max(0, center - 30) end min(len(self.scan.ranges), center 30) for i in range(start, end): r self.scan.ranges[i] if math.isnan(r) or math.isinf(r): continue if r min_range: min_range r min_angle self.scan.angle_min i * self.scan.angle_increment if math.isinf(min_range) or min_range self.d0: return 0.0, 0.0 force self.k_rep * (1.0 / min_range - 1.0 / self.d0) / (min_range * min_range) fx -force * math.cos(min_angle) fy -force * math.sin(min_angle) return fx, fy def calc_attraction(self): if self.goal is None or self.position is None: return 0.0, 0.0 dx self.goal.position.x - self.position.position.x dy self.goal.position.y - self.position.position.y dist math.hypot(dx, dy) if dist 0.5: return 0.0, 0.0 fx self.k_att * dx / dist fy self.k_att * dy / dist return fx, fy def run(self): rate rospy.Rate(10) while not rospy.is_shutdown(): ax, ay self.calc_attraction() rx, ry self.calc_repulsion() total_x ax rx total_y ay ry cmd Twist() norm math.hypot(total_x, total_y) if norm self.max_speed: total_x total_x / norm * self.max_speed total_y total_y / norm * self.max_speed cmd.linear.x total_x cmd.linear.y total_y self.cmd_pub.publish(cmd) rate.sleep() if __name__ __main__: rospy.init_node(uuv_apf_obstacle_avoidance) apf UUVAPF() apf.run()这段代码有几点要说明。第一话题名 /scan、/odom、/goal_pose、/cmd_vel 要根据你实际加载的模型和 launch 文件来替换不同 UUV 模型的话题前缀可能不一样。第二我刻意只取了声呐中间视角附近的 60 度范围来计算前方障碍物因为水下机器人通常不需要像扫地机器人那样全方位避障重点是前方区域。第三输出的是机器人坐标系下的线速度指令在 rexrov2 这种全驱动模型上可以直接生效如果你是欠驱动 UUV就需要把合力方向转成期望航向角再通过底层 PID 去跟踪。4.3 在仿真世界加入障碍物并调参代码写好了空的仿真世界可没法测避障。你需要往场景里添加一些障碍物。最快的方式是在启动 world 后用 spawn_model 命令动态加载障碍物模型这样不用改 world 文件。下面的命令往当前 Gazebo 场景里放一个尺寸为 2 x 2 x 3 的立方体rosrun gazebo_ros spawn_model -file /path/to/box/model.sdf -sdf -model obstacle1 -x 10 -y 5 -z 1你也可以直接修改已有的 world 文件把障碍物以 model 的形式写进去。用一段很简化的 SDF 就能实现sdf version1.6 model nameobstacle_box pose10 5 0 0 0 0/pose link namelink collision namecollision geometry boxsize2 2 3/size/box /geometry /collision visual namevisual geometry boxsize2 2 3/size/box /geometry /visual /link /model /sdf障碍物放好后先手动给机器人发送一个目标点看避障节点能不能让机器人绕开障碍物到达目标。这一步非常重要一定要先在没有障碍物的场景里验证节点本身能正常移动到目标点再放入障碍物测避障否则你根本分不清是控制问题还是避障逻辑问题。调参时重点看三个参数参数作用调参建议k_att目标吸引力强度过大会导致机器人直冲障碍物过小导致原地缓慢挪动k_rep障碍物排斥力强度过大容易震荡过小会碰撞一般先设成 k_att 的 1.5 倍左右再细调d0斥力影响范围设置为声呐探测距离的一半左右太大会导致目标被“屏蔽”实测下来机器人最常出现的两种问题一种是在障碍物附近来回抖动这说明斥力过强或控制频率偏高可以降低 k_rep同时把最大速度上限调低。另一种是绕开障碍物后又转向目标点太晚贴着障碍物边蹭过去这说明 d0 太短或者声呐扫描范围不够宽。调整的原则是让合力方向变化平滑不要让速度指令突然跳变。5. 常见问题与排查笔记5.1 Gazebo 界面一直闪或黑屏如果你遇到 Gazebo 窗口不停闪烁、画面撕裂甚至直接黑屏不要慌这大概率不是代码问题而是图形渲染环境的问题。Gazebo 依赖 OpenGL 渲染当显卡驱动或者虚拟机的图形加速配置不佳时就会出现这类现象。我实测过几种方案按优先级排序第一在启动命令前设置软件渲染环境变量也就是把 LIBGL_ALWAYS_SOFTWARE 设为 1强迫 Gazebo 使用 CPU 软渲染牺牲一点性能但能稳定运行。第二检查在虚拟机里是不是没有开启 3D 加速VMware 或者 VirtualBox 里通常有一个“加速 3D 图形”选项打开后会有明显改善。第三直接用 gzserver 运行仿真不看 Gazebo 的 gzclient 图形界面通过 rviz 来做可视化这样也能完全避开 Gazebo 渲染问题。有一个技巧是用命令行启动时加上 --verbose把 Gazebo 的日志打出来如果里面有和 GL、libGL 相关的报错基本可以锁定是渲染问题而不是模型加载问题。5.2 模型加载失败与 GAZEBO_MODEL_PATH模型加载失败是最常见的问题之一现象是 launch 文件启动了但是 Gazebo 里空空如也终端提示找不到模型。引起这个问题的主要原因是 GAZEBO_MODEL_PATH 环境变量没有指向模型所在的目录。首先确认你 source 了 uuv_simulator 的工作空间其次再看 echo $GAZEBO_MODEL_PATH 有没有输出。如果这个变量是空的即使模型文件存在于你的仓库里Gazebo 也不会主动去找。解决方法是手动把它加入 .bashrcexport GAZEBO_MODEL_PATH$GAZEBO_MODEL_PATH:$HOME/uuv_ws/src/uuv_simulator/uuv_gazebo_worlds/models另外还有个容易被忽略的问题模型文件路径里有中文或空格。Gazebo 对模型路径的容错性较差路径里一旦出现特殊字符就会莫名其妙地加载失败。这里没有捷径把工作空间统一放在纯英文路径下能省掉很多无意义的排查时间。5.3 水下机器人抖动、漂移、沉浮如果你发现机器人一放入水下世界就抖个不停或者慢慢往一个方向漂甚至直接沉到海底/浮到水面问题往往不在算法而在物理参数配置。抖动通常是阻尼参数太小机器人受到微小扰动后无法快速衰减反复振荡。你可以尝试增大线性阻尼和角阻尼让运动变得更“黏稠”。漂移则大概率是浮力中心和重心没有对齐或者初始化位置有点倾斜。沉浮问题就简单了直接看浮力和重力的相对大小浮力太大就减少排水体积重力太大就减小质量确保机器人本体在目标深度自然悬浮。还有一个在实际调试中很重要的问题仿真步长设置。水下物理插件对仿真步长比较敏感如果 world 里设置的 step size 太大物理计算会不稳定导致机器人剧烈抖动。我的习惯是设置成 0.001虽然会稍微增加 CPU 负载但稳定性提升非常明显。5.4 声呐没数据或控制指令不响应声呐话题没有数据最常见的原因是话题名不对。很多 UUV 模型在 urdf 里会把声呐话题名加上模型前缀比如 /rexrov2/sonar/scan而不是简单的 /scan。遇到这种情况先用 rostopic list 把所有话题列出来找到带 sonar 或者 distance、range 关键字的话题再改代码里的订阅名。控制指令不响应则是另一个常见问题导致它最隐蔽的原因是控制话题被重映射过或者模型本身使用的是推进器直接控制模式而不是接收 /cmd_vel 速度指令。第 2.4 节里我让你先跑通官方 demo就是为了此刻不掉链子。先确认官方 demo 里机器人能正常运动再跑自己的避障节点这样才能把问题隔离。如果你发现代码里发布的速度指令已经发出但机器人完全不动用 rostopic echo /cmd_vel 确认消息内容是否有值再看 gazebo 里控制插件的参数是否写对了 topic 名。水下机器人系统的 topic 重映射是很常见的操作这里需要耐心核对没有捷径。最后一点个人体会把这条路径完整走一遍之后你会发现 Gazebo 仿真 UUV 水下机器人的难点并不只在某一环而是环境、模型、传感器和算法这四层之间的衔接。我自己第一次跑通避障的时候机器人一直在障碍物附近来回转圈一度以为是势场参数写得有问题后来才发现是声呐更新频率太低避障节点在两次声呐数据之间用的是同一个旧障碍物信息导致指令反复横跳。把控制频率降下来同时和声呐更新频率对齐后问题立刻消失。这种“看着像算法 bug 其实是时序问题”的坑在仿真里其实很常见。所以最后再给你一个建议调避障算法的时候不要只盯着算法参数。花点时间把传感器话题的发布频率、控制指令的执行延时、物理仿真步长这些基础设施摸清楚往往比调 k_att 和 k_rep 更有效。Gazebo 仿真 UUV 水下机器人这条路走通其实并不难难的是一步一步把细节磨到位。只要按我说的从环境搭建开始到模型参数再到传感器和避障算法稳扎稳打你完全可以在一个周末内看到自己的水下机器人绕着障碍物流畅航行。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑