第12天 ROS Noetic 《Gazebo仿真保姆级教程:激光雷达、摄像头传感器建模+RViz可视化+话题数据分析》

简介: 本文基于 Ubuntu20.04 + ROS Noetic + Gazebo11,打造零基础保姆级 机器人传感器仿真教程。分为激光雷达、摄像头两个独立完整实验,手把手实现 URDF 机器人建模、Gazebo 传感器插件配置、RViz 可视化、ROS 话题数据分析,适合 ROS 新手入门、高校课程实验、移动机器人实训。

第12天-:传感器+摄像头仿真实验


一、环境前置说明

1. 软硬件环境

  • 操作系统:Ubuntu 20.04 LTS

  • ROS 版本:ROS Noetic Ninjemys

  • 仿真工具:Gazebo 11(ROS 官方适配仿真器)

提前安装全部依赖包,终端执行:

bash 复制代码
sudo apt install ros-noetic-desktop-full ros-noetic-gazebo-ros-pkgs
sudo apt install ros-noetic-urdf ros-noetic-rviz

2. 实验设计规范

本文包含两个完全独立的仿真实验 :分别创建独立工作空间、独立功能包、独立 URDF 模型,互不干扰,新手可以单独练习、分步掌握。文末附带激光雷达+摄像头多传感器融合拓展方案


二、实验1:Gazebo 360°激光雷达仿真

预计耗时:45分钟

2.1 实验目标

  1. 使用 URDF 搭建简易小车底盘,挂载 360° 激光雷达模型;

  2. 通过 Gazebo 官方插件实现雷达仿真,自动发布 /scan 激光话题;

  3. RViz 实时可视化激光点云、障碍物测距效果;

  4. 使用 rostopic 工具解析 LaserScan原始数据;

  5. 掌握 Gazebo 仿真引擎与 ROS 消息总线的数据流转逻辑。

2.2 核心原理:数据流转链路

Gazebo 物理仿真器负责模拟真实激光测距,通过桥接插件转为 ROS 标准话题,供可视化与算法

bash 复制代码
Gazebo物理仿真引擎(模拟激光测距)
        ↓ libgazebo_ros_laser.so 桥接插件
发布 sensor_msgs/LaserScan 消息 → ROS话题 /scan
        ↙        ↘
RViz可视化显示    终端/自定义节点订阅解析
LaserScan 消息字段详解
bash 复制代码
header          时间戳 + 雷达坐标系 frame_id
angle_min       扫描起始弧度(-π = -180°)
angle_max       扫描终止弧度(π = 180°)
angle_increment 单束激光角度间隔
range_min       最小探测距离(m)
range_max       最大探测距离(m)
ranges[]        全方位距离数组,inf 代表无障碍物

2.3 完整实操步骤

步骤1:新建独立雷达工作空间 laser_ws
bash 复制代码
# 创建工作空间与src目录
mkdir -p ~/laser_ws/src
cd ~/laser_ws/src

# 创建功能包,依赖 urdf、gazebo_ros
catkin_create_pkg laser_demo urdf gazebo_ros

# 返回工作空间编译
cd ~/laser_ws
catkin_make
source devel/setup.bash
步骤2:编写带激光雷达的 URDF 模型

文件路径:~/laser_ws/src/laser_demo/urdf/robot_laser.urdf

bash 复制代码
<?xml version="1.0"?>
<robot name="laser_robot">

  <!-- ========== 机器人底盘 ========== -->
  <link name="base_link">
    <visual>
      <geometry>
        <!-- 一个简单的盒子作为底盘,长0.3m 宽0.2m 高0.1m -->
        <box size="0.3 0.2 0.1"/>
      </geometry>
      <material name="blue">
        <color rgba="0.0 0.0 1.0 1.0"/>
      </material>
    </visual>
    <!-- 碰撞体积,Gazebo 物理引擎用这个计算碰撞 -->
    <collision>
      <geometry>
        <box size="0.3 0.2 0.1"/>
      </geometry>
    </collision>
    <!-- 惯性参数,Gazebo 仿真必须有,否则会报警告 -->
    <inertial>
      <mass value="1.0"/>
      <inertia ixx="0.01" ixy="0" ixz="0"
               iyy="0.01" iyz="0" izz="0.01"/>
    </inertial>
  </link>

  <!-- ========== 激光雷达 Link ========== -->
  <!-- 激光雷达是一个小圆柱体,安装在底盘顶部中央 -->
  <link name="laser_link">
    <visual>
      <geometry>
        <cylinder radius="0.05" length="0.04"/>
      </geometry>
      <material name="black">
        <color rgba="0.0 0.0 0.0 1.0"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.05" length="0.04"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="0.1"/>
      <inertia ixx="0.001" ixy="0" ixz="0"
               iyy="0.001" iyz="0" izz="0.001"/>
    </inertial>
  </link>

  <!-- ========== 激光雷达与底盘的关节 ========== -->
  <!-- 固定关节:激光雷达相对底盘不动 -->
  <!-- xyz="0 0 0.07" 表示激光雷达安装在底盘上方 0.07m 处 -->
  <joint name="laser_joint" type="fixed">
    <parent link="base_link"/>
    <child link="laser_link"/>
    <origin xyz="0 0 0.07" rpy="0 0 0"/>
  </joint>

  <!-- ========== Gazebo 激光雷达插件 ========== -->
  <!--
    这是整个实验的核心部分!
    libgazebo_ros_laser.so 插件让 Gazebo 模拟激光雷达,
    并把数据发布到 ROS Topic。
  -->
  <gazebo reference="laser_link">
    <sensor type="ray" name="laser_sensor">
      <!-- 传感器安装位置(相对于 laser_link 的偏移) -->
      <pose>0 0 0 0 0 0</pose>
      <visualize>true</visualize>  <!-- 在 Gazebo 中显示激光束(绿线) -->
      <update_rate>10</update_rate> <!-- 每秒更新10次 -->

      <ray>
        <scan>
          <horizontal>
            <!-- 一共360束激光,覆盖360度 -->
            <samples>360</samples>
            <resolution>1</resolution>
            <min_angle>-3.14159</min_angle>  <!-- -180度,单位弧度 -->
            <max_angle>3.14159</max_angle>   <!-- +180度,单位弧度 -->
          </horizontal>
        </scan>
        <range>
          <min>0.1</min>   <!-- 最小测量距离:10cm -->
          <max>10.0</max>  <!-- 最大测量距离:10m -->
        </range>
      </ray>

      <!-- 加载 ROS 激光雷达插件,这个插件完成 Gazebo→ROS 的数据桥接 -->
      <plugin name="gazebo_ros_laser" filename="libgazebo_ros_laser.so">
        <!-- 数据发布到这个 Topic,RViz 和你的节点都从这里订阅 -->
        <topicName>/scan</topicName>
        <!-- 坐标系名称,必须与 laser_link 一致 -->
        <frameName>laser_link</frameName>
      </plugin>
    </sensor>
  </gazebo>

</robot>
步骤3:编写启动文件 laser_demo.launch

文件路径:~/laser_ws/src/laser_demo/launch/laser_demo.launch

bash 复制代码
<launch>
  <!-- 将URDF加载到参数服务器robot_description -->
  <param name="robot_description" textfile="$(find laser_demo)/urdf/robot_laser.urdf" />
  <!-- 启动Gazebo空白仿真世界 -->
  <include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="paused" value="false"/>
    <arg name="use_sim_time" value="true"/>
    <arg name="gui" value="true"/>
  </include>
  <!-- 将机器人模型生成到Gazebo -->
  <node pkg="gazebo_ros" type="spawn_model" name="spawn_robot"
        args="-urdf -model laser_robot -param robot_description" output="screen"/>
  <!-- TF坐标转换发布,RViz必备 -->
  <node pkg="robot_state_publisher" type="robot_state_publisher" name="state_pub"/>
  <!-- 自动启动RViz可视化 -->
  <node pkg="rviz" type="rviz" name="rviz_gui" output="screen"/>
</launch>
步骤4:编译并启动仿真
bash 复制代码
cd ~/laser_ws
catkin_make
source devel/setup.bash
roslaunch laser_demo laser_demo.launch

启动成功后,自动弹出Gazebo仿真窗口RViz可视化窗口

步骤5:RViz配置激光点云显示
  1. RViz 左下角 AddBy Topic

  2. 找到 /scan 下的 LaserScan 并添加;

  3. 左侧面板将 Fixed Frame 修改为 base_link

  4. 在 Gazebo 中拖拽障碍物,RViz 实时刷新红色激光点云测距数据。

步骤6:终端查看激光话题原始数据
bash 复制代码
source ~/laser_ws/devel/setup.bash

# 1. 查看所有活跃话题
rostopic list

# 2. 查看/scan消息类型
rostopic info /scan

# 3. 实时打印激光测距数据
rostopic echo /scan

# 4. 查看话题发布频率
rostopic hz /scan

关键字段说明

  • frame_id:laser_link:数据所属传感器坐标系;

  • ranges 数组:存储360°全方位距离,inf 表示无障碍物遮挡。

2.4 实验小结

Gazebo 通过 ray 传感器模拟激光测距,依托 libgazebo_ros_laser.so 桥接插件,将测距数据封装为 ROS 标准 LaserScan 消息对外发布,可直接用于 RViz 可视化、导航避障、算法开发。


三、实验2:Gazebo 摄像头视觉仿真

预计耗时:45分钟

3.1 实验目标

  1. 独立搭建带前置摄像头的 URDF 小车模型;

  2. 仿真相机输出图像话题 /camera/image_raw、内参话题 /camera/camera_info

  3. RViz 实时预览仿真相机画面;

  4. 掌握 ROS 标准 sensor_msgs/Image 图像消息结构。

3.2 雷达 & 相机仿真核心对比

对比项 激光雷达 仿真摄像头
桥接插件 libgazebo_ros_laser.so libgazebo_ros_camera.so
核心话题 /scan /camera/image_raw
消息类型 sensor_msgs/LaserScan sensor_msgs/Image
核心数据 距离浮点数组 RGB 像素字节流
附属话题 /camera/camera_info(相机内参)
Image 图像消息结构
bash 复制代码
header      时间戳+相机坐标系
height      图像高度像素
width       图像宽度像素
encoding    像素格式(RGB8/BGR8)
step        单行总字节数(width×3)
data[]      全部像素原始字节流

3.3 完整实操步骤

步骤1:新建相机独立工作空间 camera_ws
bash 复制代码
mkdir -p ~/camera_ws/src
cd ~/camera_ws/src
catkin_create_pkg camera_demo urdf gazebo_ros
cd ~/camera_ws
catkin_make
source devel/setup.bash
步骤2:相机 URDF 模型

路径:~/camera_ws/src/camera_demo/urdf/robot_camera.urdf

bash 复制代码
<?xml version="1.0"?>
<robot name="camera_robot">

  <!-- ========== 机器人底盘 ========== -->
  <link name="base_link">
    <visual>
      <geometry>
        <box size="0.3 0.2 0.1"/>
      </geometry>
      <material name="green">
        <color rgba="0.0 1.0 0.0 1.0"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <box size="0.3 0.2 0.1"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="1.0"/>
      <inertia ixx="0.01" ixy="0" ixz="0"
               iyy="0.01" iyz="0" izz="0.01"/>
    </inertial>
  </link>

  <!-- ========== 摄像头 Link ========== -->
  <!-- 摄像头用一个小盒子表示,安装在底盘前方顶部 -->
  <link name="camera_link">
    <visual>
      <geometry>
        <box size="0.05 0.05 0.05"/>
      </geometry>
      <material name="red">
        <color rgba="1.0 0.0 0.0 1.0"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <box size="0.05 0.05 0.05"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="0.05"/>
      <inertia ixx="0.0001" ixy="0" ixz="0"
               iyy="0.0001" iyz="0" izz="0.0001"/>
    </inertial>
  </link>

  <!-- ========== 摄像头安装关节 ========== -->
  <!--
    xyz="0.15 0 0.08":
      x=0.15 → 安装在底盘前方 15cm 处
      z=0.08 → 高于底盘中心 8cm
  -->
  <joint name="camera_joint" type="fixed">
    <parent link="base_link"/>
    <child link="camera_link"/>
    <origin xyz="0.15 0 0.08" rpy="0 0 0"/>
  </joint>

  <!-- ========== Gazebo 摄像头插件 ========== -->
  <!--
    libgazebo_ros_camera.so 是摄像头的桥接插件。
    Gazebo 渲染引擎把摄像头"看到"的画面打包成
    sensor_msgs/Image 消息,发布到 ROS Topic。
  -->
  <gazebo reference="camera_link">
    <sensor type="camera" name="camera_sensor">
      <update_rate>30.0</update_rate>  <!-- 30帧/秒 -->
      <visualize>true</visualize>

      <camera name="head_camera">
        <!-- 水平视场角:80度 -->
        <horizontal_fov>1.3962634</horizontal_fov>
        <image>
          <width>640</width>    <!-- 图像宽度(像素) -->
          <height>480</height>  <!-- 图像高度(像素) -->
          <format>R8G8B8</format>  <!-- RGB格式,每通道8位 -->
        </image>
        <clip>
          <near>0.02</near>  <!-- 近裁剪面:2cm 以内看不到 -->
          <far>300</far>     <!-- 远裁剪面:300m 以外看不到 -->
        </clip>
      </camera>

      <!-- 加载摄像头插件 -->
      <plugin name="gazebo_ros_camera" filename="libgazebo_ros_camera.so">
        <!-- 命名空间:Topic 会以此为前缀 -->
        <cameraName>camera</cameraName>
        <!-- 图像 Topic 名:完整名为 /camera/image_raw -->
        <imageTopicName>image_raw</imageTopicName>
        <!-- 相机内参 Topic:/camera/camera_info -->
        <cameraInfoTopicName>camera_info</cameraInfoTopicName>
        <!-- 坐标系名称 -->
        <frameName>camera_link</frameName>
      </plugin>
    </sensor>
  </gazebo>

</robot>
步骤3:相机启动文件 camera_demo.launch

路径:~/camera_ws/src/camera_demo/launch/camera_demo.launch

bash 复制代码
<launch>
  <param name="robot_description" textfile="$(find camera_demo)/urdf/robot_camera.urdf" />
  <!-- 启动Gazebo空世界 -->
  <include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="paused" value="false"/>
    <arg name="use_sim_time" value="true"/>
    <arg name="gui" value="true"/>
  </include>
  <!-- 生成小车模型 -->
  <node pkg="gazebo_ros" type="spawn_model" name="spawn_car"
        args="-urdf -model camera_robot -param robot_description" output="screen"/>
  <node pkg="robot_state_publisher" type="robot_state_publisher" name="tf_pub"/>
  <node pkg="rviz" type="rviz" name="rviz_cam" output="screen"/>
</launch>
步骤4:启动相机仿真
bash 复制代码
cd ~/camera_ws
catkin_make
source devel/setup.bash
roslaunch camera_demo camera_demo.launch
步骤5:RViz 显示相机画面
  1. RViz AddBy Topic,选中 /camera/image_raw 的 Image 组件;

  2. 在 Gazebo 小车前方放置任意模型,RViz 实时渲染相机成像画面。

步骤6:终端查看图像话题数据
bash 复制代码
source ~/camera_ws/devel/setup.bash

# 查看所有相机相关话题
rostopic list | grep camera

# 查看图像消息类型
rostopic info /camera/image_raw

# 仅打印图像元数据,屏蔽超长像素数组
rostopic echo /camera/image_raw --noarr

# 查看相机输出帧率
rostopic hz /camera/image_raw

3.4 实验小结

Gazebo 相机插件将仿真渲染画面转为标准 ROS 图像消息,并同步输出相机内参,可直接用于目标检测、视觉识别、视觉 SLAM 算法仿真开发。


四、拓展实验:激光雷达+摄像头多传感器融合仿真思路

基于以上两个独立实验,可快速搭建多传感器融合机器人模型,实现激光测距+视觉成像同步感知:

  1. 新建综合工作空间,编写融合版 URDF,同时包含底盘、雷达、相机 link 与固定关节;

  2. 在同一个 URDF 中同时配置 ray 雷达插件与 camera 相机插件;

  3. 复用 launch 启动文件,仅替换融合模型;

  4. RViz 同时添加 LaserScan、Image 组件,同步观测距离数据与视觉画面;

  5. 编写 Python 订阅节点,同时订阅 /scan/camera/image_raw,实现视觉+激光多传感器融合。

五、全文总结

本文从零完成了 Gazebo 激光雷达、摄像头双传感器仿真,覆盖 URDF 机器人建模、Gazebo 插件配置、RViz 可视化、ROS 话题数据分析全流程,是新手入门 ROS 机器人仿真的完整闭环教程。

核心学习要点:

  1. Gazebo 插件是连接仿真物理引擎与 ROS 消息总线的核心桥梁;

  2. 激光、相机传感器对应 ROS 标准消息固定,可无缝对接导航、视觉、避障算法;

  3. 双工作空间独立设计,低耦合、易练习、易拓展;

  4. 通过 rostopic 工具可快速解析传感器数据,适配算法调试与实验分析。