3. Gazebo仿真环境搭建:从零开始构建你的测试场

好,咱们进入实战环节。Gazebo仿真环境,说白了就是给机器人算法搭一个「虚拟实验室」。你总不能让机器人直接在真实厂房里乱撞吧?我刚开始做导航时,就吃过这个亏——代码没调好就上真机,结果机器人直接怼墙上了,激光雷达支架都撞歪了。从那以后,我养成了习惯:先在Gazebo里把逻辑跑通,再考虑真机部署。

这一章,咱们就手把手把Gazebo Fortress环境搭起来,包括安装验证、自定义世界、传感器配置。嗯,内容不少,但每一步都很关键。

3.1 Gazebo Fortress安装与验证

Gazebo Fortress是Ignition系列的最新版本,和ROS2 Humble配合得最好。我个人建议直接用二进制安装,省去编译的麻烦。

# 安装Gazebo Fortress
sudo apt-get update
sudo apt-get install ignition-fortress

# 验证安装
ign gazebo --version

如果看到版本号输出,说明安装成功了。你可以试着启动一个空世界看看:

ign gazebo empty.sdf

这时候应该会弹出一个3D窗口,里面啥也没有——别慌,这就是「空世界」。我遇到过有人以为装坏了,其实Gazebo默认就是空的,需要你自己往里加东西。

小提示:如果启动时报错说缺少图形库,检查一下你的显卡驱动。我曾在Ubuntu 22.04上遇到过NVIDIA驱动不兼容的问题,换成开源驱动nouveau就好了。

3.2 创建自定义仿真世界(world文件)

空世界没法用,咱们得自己建一个。Gazebo的世界文件是SDFormat格式,后缀是.sdf或.world。说白了就是一个XML文件,描述场景里有什么东西、长什么样、物理属性如何。

下面是我常用的一个测试世界——一个带围墙的矩形场地,适合做导航避障测试:

<?xml version="1.0" ?>
<sdf version="1.9">
  <world name="nav_test_world">
    <!-- 地面 -->
    <include>
      <uri>https://fuel.gazebosim.org/1.0/OpenRobotics/models/Ground Plane</uri>
    </include>

    <!-- 太阳光 -->
    <include>
      <uri>https://fuel.gazebosim.org/1.0/OpenRobotics/models/Sun</uri>
    </include>

    <!-- 物理引擎配置 -->
    <physics name="1ms" type="ignored">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>
  </world>
</sdf>

保存为my_test_world.sdf,然后用这个命令启动:

ign gazebo my_test_world.sdf

你会看到一个带地面和光照的空白场地。嗯,这才像个样子。

核心要点:world文件是仿真环境的「剧本」。你可以在里面定义地形、建筑、动态物体,甚至天气效果。对于导航测试,我建议至少包含:平坦地面、围墙边界、几个障碍物。

3.3 添加静态障碍物与地面

光有地面不够,咱们得加几个障碍物,模拟真实场景中的柱子、墙壁、箱子。Gazebo里加模型有两种方式:

  • Fuel模型库:在线下载现成的模型,比如箱子、圆柱、椅子
  • 自定义模型:自己写SDF描述,定义形状、大小、颜色

我个人更喜欢自定义,因为可控性强。下面是一个带障碍物的世界文件示例:

<?xml version="1.0" ?>
<sdf version="1.9">
  <world name="obstacle_world">
    <include>
      <uri>https://fuel.gazebosim.org/1.0/OpenRobotics/models/Ground Plane</uri>
    </include>

    <!-- 障碍物1:红色立方体 -->
    <model name="box_obstacle">
      <pose>2.0 1.0 0.5 0 0 0</pose>
      <link name="body">
        <visual name="visual">
          <geometry>
            <box>
              <size>0.8 0.8 1.0</size>
            </box>
          </geometry>
          <material>
            <ambient>0.8 0.2 0.2 1.0</ambient>
          </material>
        </visual>
        <collision name="collision">
          <geometry>
            <box>
              <size>0.8 0.8 1.0</size>
            </box>
          </geometry>
        </collision>
      </link>
    </model>

    <!-- 障碍物2:灰色圆柱 -->
    <model name="cylinder_obstacle">
      <pose>-1.5 -1.0 0.5 0 0 0</pose>
      <link name="body">
        <visual name="visual">
          <geometry>
            <cylinder>
              <radius>0.3</radius>
              <length>1.0</length>
            </cylinder>
          </geometry>
          <material>
            <ambient>0.5 0.5 0.5 1.0</ambient>
          </material>
        </visual>
        <collision name="collision">
          <geometry>
            <cylinder>
              <radius>0.3</radius>
              <length>1.0</length>
            </cylinder>
          </geometry>
        </collision>
      </link>
    </model>
  </world>
</sdf>

这里要注意:<pose>标签定义了模型的位置和朝向,格式是x y z roll pitch yaw。我习惯把障碍物放在地面以上0.5米处,这样看起来自然。

避坑指南:我曾经把障碍物的z坐标设成0,结果模型一半陷在地面里,看起来像被「吃掉」了。记得把z设为高度的一半,比如1米高的箱子,z设0.5。

3.4 传感器插件配置(IMU、激光雷达、里程计)

环境搭好了,机器人也得有「眼睛」和「耳朵」。Gazebo里传感器是通过插件实现的,说白了就是一段C++代码,模拟真实传感器的行为。

下面是一个完整的机器人模型SDF,包含IMU、激光雷达和里程计插件:

<?xml version="1.0" ?>
<sdf version="1.9">
  <model name="my_robot">
    <link name="base_link">
      <!-- 机器人本体 -->
      <visual name="visual">
        <geometry>
          <box>
            <size>0.4 0.3 0.2</size>
          </box>
        </geometry>
      </visual>
    </link>

    <!-- IMU传感器 -->
    <link name="imu_link">
      <pose>0 0 0.1 0 0 0</pose>
      <sensor name="imu_sensor" type="imu">
        <always_on>1</always_on>
        <update_rate>100</update_rate>
        <visualize>false</visualize>
        <topic>/imu/data</topic>
      </sensor>
    </link>

    <!-- 激光雷达 -->
    <link name="lidar_link">
      <pose>0.2 0 0.15 0 0 0</pose>
      <sensor name="lidar_sensor" type="lidar">
        <always_on>1</always_on>
        <update_rate>10</update_rate>
        <visualize>true</visualize>
        <topic>/scan</topic>
        <lidar>
          <scan_count>1</scan_count>
          <scan_angle>
            <min>-2.35619</min>
            <max>2.35619</max>
          </scan_angle>
          <range>
            <min>0.1</min>
            <max>30.0</max>
          </range>
        </lidar>
      </sensor>
    </link>

    <!-- 里程计插件 -->
    <plugin name="odometry" filename="libignition-gazebo-odometry-system.so">
      <body_name>base_link</body_name>
      <topic>/odom</topic>
      <update_rate>50</update_rate>
    </plugin>
  </model>
</sdf>

这里有几个关键参数,我解释一下:

传感器 关键参数 说明
IMU update_rate: 100Hz 提供加速度和角速度,用于状态估计
激光雷达 scan_angle: ±135° 270度扫描范围,30米探测距离
里程计 update_rate: 50Hz 基于轮子编码器推算位置
经验之谈:激光雷达的更新率我一般设10Hz,和真实雷达差不多。IMU设100Hz是因为导航里需要高频姿态数据。别把激光雷达设太高,否则CPU扛不住——我试过设50Hz,结果仿真直接卡成PPT。

3.5 本章知识体系

下面这张图,帮你理清Gazebo仿真环境的整体结构:

Gazebo仿真环境搭建核心结构 世界文件 (.sdf / .world) 环境模型 机器人模型 传感器插件 环境模型 • 地面(Ground Plane) • 静态障碍物(箱子/圆柱) • 光照与物理引擎 机器人模型 • 底盘(base_link) • 传感器安装位置 • 碰撞与视觉属性 传感器插件 • IMU(100Hz) • 激光雷达(10Hz) • 里程计(50Hz) 输出:/scan /imu/data /odom

从这张图可以看得很清楚:世界文件是总纲,它把环境、机器人和传感器组合在一起。环境提供物理约束,机器人提供运动载体,传感器提供数据输出——这三者缺一不可。

嗯,到这里Gazebo仿真环境就搭好了。你可以在自定义世界里放几个障碍物,挂上传感器,然后启动看看效果。如果一切正常,你应该能在终端看到/scan、/imu/data、/odom这些话题在发布数据。

验证方法:用ign topic -l列出所有话题,或者用ign topic -e -t /scan查看激光雷达数据。如果能看到数据在刷新,说明传感器插件配置成功了。