2. 仿真环境搭建:Gazebo与Stage仿真器选型,世界模型与机器人模型加载
说到仿真环境搭建,我见过不少新手一上来就纠结「到底用哪个仿真器」。其实这事没那么玄乎。Gazebo 和 Stage,说白了就是两个不同量级的工具。你想想看,一个是要做高保真物理模拟,一个是要快速验证算法逻辑,它们压根就不是替代关系。
我个人习惯是这么分的:如果你的导航算法需要跟真实传感器特性、动力学模型死磕,那就上 Gazebo;如果你只是想快速调通路径规划、行为状态机这些逻辑层,Stage 反而更顺手。我在项目中遇到过好几次,团队一上来就上 Gazebo,结果光调模型参数就花了两周,最后发现核心算法逻辑还没跑通。嗯,这里要注意,选仿真器之前,先想清楚你要验证什么。
2.1 Gazebo vs Stage:核心差异对比
先看一张对比表,这样你心里就有数了。
| 对比维度 | Gazebo | Stage |
|---|---|---|
| 物理引擎 | ODE / Bullet / DART 等,支持碰撞、摩擦、惯性 | 无物理引擎,仅做位姿更新 |
| 传感器仿真 | 激光雷达、IMU、相机、深度相机等,带噪声模型 | 仅支持简化的激光测距和声纳 |
| 渲染能力 | OGRE 3D 渲染,支持光照、纹理、阴影 | 2D 平面渲染,像素风格 |
| 计算开销 | 高,需要 GPU 加速效果更好 | 极低,单核 CPU 就能跑 |
| 适用场景 | 传感器选型、动力学验证、SLAM 闭环测试 | 路径规划、行为决策、多机器人调度 |
| 启动速度 | 30秒~数分钟(取决于模型复杂度) | 1~3秒 |
看到这里你可能会问:「那我到底该选哪个?」我的建议是——两个都要用。我在做导航全链路联调时,通常先用 Stage 把规划、控制、行为树这些逻辑层跑通,确认没有逻辑 bug 之后,再切到 Gazebo 做传感器噪声、动力学响应层面的验证。这样既快又稳。
核心原则:逻辑验证用 Stage,物理验证用 Gazebo。不要试图用一个仿真器解决所有问题。
2.2 世界模型加载:从空白地图到结构化环境
世界模型,说白了就是机器人要面对的那个「世界」。在仿真里,它通常是一个描述环境几何、障碍物、地面属性的文件。
Gazebo 的世界模型是 .world 文件,里面用 SDF 或 URDF 格式描述环境。Stage 的世界模型是 .world 文件(没错,同名但格式不同),用 Stage 自己的 .inc 文件描述。
我刚开始用 Gazebo 时,踩过一个坑:直接加载官方提供的 empty.world,结果机器人一启动就往下掉。后来才发现,empty.world 默认没有地面。嗯,这里要注意,一定要显式添加地面模型。
下面是一个典型的 Gazebo 世界模型文件结构:
<?xml version="1.0" ?>
<sdf version="1.6">
<world name="navigation_world">
<include>
<uri>model://sun</uri>
</include>
<include>
<uri>model://ground_plane</uri>
</include>
<include>
<uri>model://obstacle_1</uri>
<name>wall_01</name>
<pose>2.0 0.0 0.0 0 0 0</pose>
</include>
<include>
<uri>model://obstacle_2</uri>
<name>pillar_01</name>
<pose>-1.5 2.0 0.0 0 0 0</pose>
</include>
</world>
</sdf>
Stage 的世界模型更轻量,一个简单的 .world 文件长这样:
define floorplan model
(
color "gray"
boundary 1
gui_nose 0
gui_grid 0
gui_outline 0
gripper_return 0
fiducial_return 0
laser_return 1
)
define robot position
(
size [0.4 0.4 0.3]
origin [0 0 0 0]
gui_nose 1
drive "diff"
)
# 加载地图
floorplan
(
name "office_floor"
bitmap "office_map.png"
size [20.0 20.0 1.0]
pose [0 0 0 0]
)
# 加载机器人
robot
(
name "robot1"
pose [0.0 0.0 0.0]
)
小技巧:Stage 的 bitmap 支持 PNG 格式的栅格地图。你可以用任何图像编辑软件画一张黑白图,黑色代表障碍物,白色代表可通行区域。我经常用这个方式快速搭建测试场景,比 Gazebo 一个个摆模型快多了。
2.3 机器人模型加载:URDF 与 SDF 的抉择
机器人模型怎么加载?这又是一个让新手头疼的问题。Gazebo 支持两种格式:URDF 和 SDF。Stage 则用自己定义的 position 或 differential 模型描述。
我个人更推荐在 Gazebo 里用 SDF 格式。为什么?因为 SDF 是 Gazebo 的原生格式,支持更丰富的传感器标签、物理参数和插件系统。URDF 虽然也能用,但经常需要额外写 .gazebo 扩展文件来补充 Gazebo 特有的配置。
我曾经在一个项目里用 URDF 加载机器人,结果激光雷达的噪声参数怎么都调不生效。折腾了两天,最后换成 SDF 格式,十分钟搞定。说白了,原生格式永远是最省心的。
下面是一个简单的 SDF 机器人模型示例:
<?xml version="1.0" ?>
<sdf version="1.6">
<model name="my_robot">
<link name="base_link">
<pose>0 0 0.1 0 0 0</pose>
<inertial>
<mass>10.0</mass>
<inertia>
<ixx>0.1</ixx>
<iyy>0.1</iyy>
<izz>0.1</izz>
</inertia>
</inertial>
<collision name="collision">
<geometry>
<cylinder>
<radius>0.2</radius>
<length>0.2</length>
</cylinder>
</geometry>
</collision>
<visual name="visual">
<geometry>
<cylinder>
<radius>0.2</radius>
<length>0.2</length>
</cylinder>
</geometry>
</visual>
</link>
<plugin name="diff_drive" filename="libDiffDrivePlugin.so">
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.4</wheel_separation>
<wheel_diameter>0.1</wheel_diameter>
</plugin>
</model>
</sdf>
注意:Stage 不支持 SDF/URDF 格式。如果你要在 Stage 里用自定义机器人,必须用 Stage 自己的模型描述语法。不过 Stage 的模型描述极其简单,通常十几行就能搞定一个差速驱动机器人。
2.4 仿真器选型决策流程
说了这么多,我画了一张决策流程图,帮你快速判断该用哪个仿真器。
2.5 加载实战:一条命令启动仿真
理论说完了,咱们来点实际的。假设你已经装好了 ROS 和 Gazebo,加载一个带机器人的世界模型只需要一条命令:
# 启动 Gazebo 并加载世界模型和机器人
roslaunch my_navigation_package navigation_world.launch
这个 launch 文件的内容大致如下:
<launch>
<!-- 启动 Gazebo 世界 -->
<include file="$(find gazebo_ros)/launch/empty_world.launch">
<arg name="world_name" value="$(find my_navigation_package)/worlds/navigation_world.world"/>
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
<arg name="gui" value="true"/>
</include>
<!-- 加载机器人模型到 Gazebo -->
<node name="spawn_robot" pkg="gazebo_ros" type="spawn_model"
args="-file $(find my_navigation_package)/models/my_robot.sdf
-sdf -model my_robot -x 0 -y 0 -z 0.1"/>
</launch>
Stage 的启动更简单:
# 启动 Stage 仿真
rosrun stage_ros stageros $(find my_navigation_package)/worlds/office.world
个人经验:我习惯在 launch 文件里加一个 paused 参数。调试阶段设为 true,这样 Gazebo 启动后不会立刻开始物理模拟,我可以先检查模型位置、传感器配置是否正确。确认无误后,再手动点击 Play 按钮开始仿真。这个小习惯帮我避免了很多「模型飞出去」的尴尬。
2.6 避坑指南:我踩过的三个坑
最后分享几个我实际踩过的坑,希望能帮你省点时间。
- 坑一:模型坐标对不上 —— 我曾经在 Gazebo 里加载机器人,结果它直接穿模到地板下面。后来发现是
.world文件里地面的pose设成了0 0 0,而机器人的z坐标也是0。解决办法:地面z保持0,机器人z设为底盘高度/2。 - 坑二:Stage 地图像素和实际尺寸不匹配 —— Stage 的
bitmap加载地图时,size参数的单位是米。如果你画了一张 100x100 像素的 PNG,但size设成了[10 10 1],那每个像素就代表 0.1 米。我一开始没注意这个比例,结果机器人在地图里走两步就撞墙。 - 坑三:Gazebo 插件不生效 —— 差速驱动插件加载后,机器人就是不动。排查了半天,发现是
left_joint和right_joint的名字写错了,跟 URDF/SDF 里定义的关节名不一致。嗯,这种低级错误最容易让人抓狂。