4. 传感器仿真与数据采集

做机器人导航,传感器就是眼睛和耳朵。没有真实的传感器数据,SLAM和Nav2都成了空中楼阁。这一章我们就在Gazebo里把激光雷达、IMU、里程计全部仿真出来,再搭一个能跑起来的测试环境。我个人习惯先把传感器“喂饱”,再调算法,这样后面少踩很多坑。

传感器仿真与数据采集 激光雷达仿真 IMU仿真 里程计仿真 Gazebo测试环境

4.1 激光雷达仿真:从模型到点云

激光雷达是SLAM的主力传感器。Gazebo里用gazebo_plugins提供的RayPlugin就能模拟2D激光。我刚开始搞仿真时,总以为雷达模型越复杂越好,其实一个16线或单线就够用,关键是帧率和噪声参数要调得像真家伙。

核心思路: 在URDF中加入<gazebo>标签引用雷达插件,设置<update_rate>、<range>、<noise>。我个人习惯把最小距离设成0.15m,避免机器人自身反射干扰。
<!-- 激光雷达插件示例 -->
<gazebo reference="laser_link">
  <sensor type="ray" name="head_laser">
    <pose>0 0 0.1 0 0 0</pose>
    <visualize>true</visualize>
    <update_rate>10</update_rate>
    <ray>
      <scan>
        <horizontal>
          <samples>360</samples>
          <resolution>1</resolution>
          <min_angle>-3.14159</min_angle>
          <max_angle>3.14159</max_angle>
        </horizontal>
      </scan>
      <range>
        <min>0.15</min>
        <max>12.0</max>
      </range>
      <noise>
        <type>gaussian</type>
        <mean>0.0</mean>
        <stddev>0.01</stddev>
      </noise>
    </ray>
  </sensor>
</gazebo>

你想想看,如果stddev设成0,仿真数据就太完美了,拿到真实机器人上反而容易崩。我一般加0.01~0.02m的高斯噪声,这样建图时能提前暴露定位抖动问题。

4.2 IMU仿真:角速度与加速度

IMU提供高频的角速度和线加速度,对里程计融合至关重要。Gazebo里用ImuPlugin就能生成6轴数据。嗯,这里要注意:IMU的坐标系定义和真实传感器一致,通常ENU或NED,我习惯用ENU,和ROS默认的REP105对齐。

小技巧: 在URDF里给IMU单独加一个imu_link,并设置<gravity>为true,这样仿真器会自动叠加重力加速度。我曾经因为忘了开重力,IMU输出的z轴一直是0,融合定位直接飘飞……
<gazebo reference="imu_link">
  <sensor type="imu" name="imu_sensor">
    <pose>0 0 0 0 0 0</pose>
    <update_rate>200</update_rate>
    <imu>
      <noise>
        <type>gaussian</type>
        <rate>
          <mean>0.0</mean>
          <stddev>0.0002</stddev>
        </rate>
        <accel>
          <mean>0.0</mean>
          <stddev>0.017</stddev>
        </accel>
      </noise>
    </imu>
  </sensor>
</gazebo>

IMU的更新率我设200Hz,和真实工业级IMU接近。噪声参数参考了MPU9250的数据手册,这样仿真数据可以直接用来测试滤波算法。

4.3 里程计仿真:轮式编码器与差分驱动

里程计是导航的基础。Gazebo里用libgazebo_ros_diff_drive.so插件,直接根据轮速积分出odom。说白了,就是给机器人装一对虚拟编码器。

避坑指南: 我曾经把wheel_separation设错了0.01m,结果机器人原地转圈时odom角度偏差巨大,AMCL死活收敛不了。一定要用卡尺量你的轮距,仿真里也要按真实值来。
<gazebo>
  <plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so">
    <ros>
      <namespace>/</namespace>
      <argument>odom</argument>
    </ros>
    <update_rate>50</update_rate>
    <left_joint>left_wheel_joint</left_joint>
    <right_joint>right_wheel_joint</right_joint>
    <wheel_separation>0.35</wheel_separation>
    <wheel_diameter>0.1</wheel_diameter>
    <max_wheel_torque>20</max_wheel_torque>
    <max_wheel_acceleration>1.0</max_wheel_acceleration>
    <odometry_frame>odom</odometry_frame>
    <robot_base_frame>base_footprint</robot_base_frame>
    <publish_odom>true</publish_odom>
    <publish_odom_tf>true</publish_odom_tf>
    <publish_wheel_tf>true</publish_wheel_tf>
  </plugin>
</gazebo>

里程计的话题是/odom,频率50Hz。我个人习惯同时发布TF变换,这样rviz里直接能看到odom→base_footprint的连线,调试起来非常直观。

4.4 在Gazebo中搭建测试环境

有了传感器,还得有个场地跑起来。我一般用gazebo_ros的spawn_entity把机器人放进去,再加载一个带墙壁和障碍物的世界。

快速启动命令:
ros2 launch your_robot_gazebo robot_world.launch.py
世界文件可以用building_editor画,或者直接下载开源迷宫。我推荐先用一个10m×10m的方形房间,放几个箱子,这样SLAM和导航都能验证。

下面是一个简单的世界文件片段,包含地面、墙壁和两个柱子:

<?xml version="1.0" ?>
<sdf version="1.6">
  <world name="test_world">
    <include>
      <uri>model://sun</uri>
    </include>
    <include>
      <uri>model://ground_plane</uri>
    </include>
    <!-- 墙壁 -->
    <model name="wall1">
      <static>true</static>
      <link name="link">
        <collision name="collision">
          <geometry><box><size>10 0.2 0.5</size></box></geometry>
        </collision>
        <visual name="visual">
          <geometry><box><size>10 0.2 0.5</size></box></geometry>
          <material><ambient>0.8 0.8 0.8 1</ambient></material>
        </visual>
      </link>
      <pose>0 5 0.25 0 0 0</pose>
    </model>
    <!-- 障碍物 -->
    <model name="obstacle1">
      <static>true</static>
      <link name="link">
        <collision name="collision">
          <geometry><cylinder><radius>0.3</radius><length>0.6</length></cylinder></geometry>
        </collision>
        <visual name="visual">
          <geometry><cylinder><radius>0.3</radius><length>0.6</length></cylinder></geometry>
        </visual>
      </link>
      <pose>2 2 0.3 0 0 0</pose>
    </model>
  </world>
</sdf>

启动后,用ros2 topic echo /scan就能看到激光数据,/imu/data输出IMU读数,/odom发布里程计。我个人习惯同时打开rviz2,添加LaserScan、Imu和Odometry显示,一眼就能看出传感器是否正常工作。

数据采集小建议: 用ros2 bag record -a录制一个3分钟的数据包,包含直线、转弯、原地旋转。这个bag后面做SLAM和导航联合调试时非常宝贵。我每次换传感器参数都会重新录一份,方便对比。

4.5 联合检查:传感器同步与时间戳

三个传感器都跑起来后,一定要检查时间戳是否对齐。Gazebo默认使用仿真时间,但如果你忘了设置use_sim_time:=true,ROS2会拿系统时间,导致TF和odom错乱。我曾经因为这个bug排查了整整一个下午……

传感器话题名典型频率常见问题
激光雷达/scan10 Hz帧率太低导致建图畸变
IMU/imu/data200 Hz重力方向未对齐
里程计/odom50 Hz轮距误差导致旋转漂移

启动时加上--params-file指定use_sim_time: true,或者在launch文件里设置。我个人习惯在launch里统一声明:

from launch.actions import SetParameter
SetParameter(name='use_sim_time', value=True)

这样所有节点都自动使用仿真时钟,数据采集天然同步。

本章小结: 激光雷达、IMU、里程计是SLAM与Nav2的三大数据源。Gazebo里通过插件配置噪声、频率、坐标系,就能生成接近真实的数据。搭一个带墙壁和障碍物的测试世界,用ros2 bag采集数据,后续所有算法验证都基于这套仿真管线。我自己的项目里,这套流程已经帮我在上真车前排掉了80%的传感器问题。