RtabMap仿真之无人车建模

无人车

Posted by LXG on July 10, 2026

源码


lxg@lxg:~/code/xt/gitlab/rtabmap_sim_ws/src$ tree
.
├── rtabmap_sim_bringup
│   ├── CMakeLists.txt
│   ├── launch
│   │   ├── bringup_mapping.launch.py
│   │   └── bringup_navigation.launch.py
│   ├── maps
│   ├── package.xml
│   └── rviz
│       ├── mapping.rviz
│       └── navigation.rviz
├── rtabmap_sim_description
│   ├── CMakeLists.txt
│   ├── launch
│   │   └── display.launch.py
│   ├── meshes
│   ├── package.xml
│   ├── rviz
│   │   └── display.rviz
│   └── urdf
│       ├── gz_plugins.xacro
│       ├── robot.urdf.xacro
│       └── sensors
│           ├── imu.xacro
│           ├── lidar_2d.xacro
│           └── rgbd_camera.xacro
├── rtabmap_sim_gazebo
│   ├── CMakeLists.txt
│   ├── config
│   │   └── ros_gz_bridge.yaml
│   ├── launch
│   │   └── spawn_robot.launch.py
│   ├── materials
│   │   └── textures
│   │       ├── brick_wall.png
│   │       ├── checker.png
│   │       ├── concrete.png
│   │       └── wood_floor.png
│   ├── package.xml
│   └── worlds
│       ├── empty_test.sdf
│       └── sim_world.sdf
├── rtabmap_sim_localization
│   ├── CMakeLists.txt
│   ├── config
│   │   └── ekf.yaml
│   ├── launch
│   │   └── ekf.launch.py
│   └── package.xml
├── rtabmap_sim_navigation
│   ├── CMakeLists.txt
│   ├── config
│   │   ├── behavior_tree.xml
│   │   └── nav2_params.yaml
│   ├── launch
│   │   └── navigation.launch.py
│   └── package.xml
├── rtabmap_sim_slam
│   ├── CMakeLists.txt
│   ├── config
│   │   └── rtabmap.yaml
│   ├── launch
│   │   └── slam.launch.py
│   └── package.xml
└── rtabmap_sim_teleop
    ├── CMakeLists.txt
    ├── launch
    │   └── teleop.launch.py
    └── package.xml

坐标树结构


map
 └── odom                                (动态, 由 rtabmap 发布, 低频修正)
      └── base_footprint                 (动态, 由 EKF 发布, 高频连续)
           └── base_link                 (静态, base_footprint→base_link 固定高度差, robot_state_publisher)
                ├── wheel_left_link      (动态-关节, 车轮转动角度, robot_state_publisher ← /joint_states)
                ├── wheel_right_link    (动态-关节)
                ├── caster_wheel_link   (静态或动态,视万向轮建模而定)
                ├── imu_link            (静态, robot_state_publisher)
                ├── laser_link          (静态, 2D LiDAR 安装位置, robot_state_publisher)
                └── camera_link         (静态, robot_state_publisher)
                     ├── camera_rgb_frame
                     │    └── camera_rgb_optical_frame     (静态, REP-103 光学坐标系: Z前 X右 Y下)
                     └── camera_depth_frame
                          └── camera_depth_optical_frame  (静态)


rosgraph

Topic

机器人纯几何模型 rtabmap_sim_description

每个刚体部件就是一个 link。一个 link 由三部分组成:

link
 ├── visual     —— 长得什么样(只是视觉显示,不影响物理)
 ├── collision  —— 碰撞检测用(可以比 visual 简化以提升性能)
 └── inertial   —— 质量/惯量
子元素 🟢 通用 🔵 仿真 🟠 实物 说明
visual ✅ RViz 显示、TF 可视化 ✅ Gazebo 视觉渲染 ✅ RViz 显示 通用
collision ✅ Nav2 代价地图避障 ✅ Gazebo 物理碰撞检测 ✅ Nav2 代价地图避障 通用,但仿真中物理碰撞更精细
inertial ❌ 实物不依赖 URDF 惯量 ✅ Gazebo 物理引擎必须 ❌ 实物用真实物理,不需要 URDF 中的惯性数据 🔵 仿真必须写;🟢 实物可以不写或写个近似值(ROS 控制不依赖它)

仿真差速小车模型

对本项目的差速小车:

       俯视图(从上方看)
   ┌─────────────────────┐
   │       前万向轮        │
   │         ●            │
   │ 左轮■           ■右轮 │
   │   (base_link 中心)    │
   │         ●            │
   │       后万向轮        │
   └─────────────────────┘

       侧视图(Z 方向高度关系)

               ↑ Z
   ┌───────────┴───────────┐   ← base_link 中心
   │        底盘           │       z = base_footprint_to_base_link_z
   │    (base_link)        │         = wheel_radius + base_height/2.0
   │    高=base_height     │         = 0.05 + 0.05 = 0.10m
   └───────────┬───────────┘
               │
               │ base_height/2.0 = 0.05m
               │
          ─────┴─────      ← 轮子安装面(底盘底面)
               │
          ─────┴─────      ← 驱动轮中心(z = wheel_radius = 0.05m)
               │
            ───┴───        ← 驱动轮触地点
        ═══════╧════════   ← 地面(z = 0),base_footprint 在此平面

  注:万向轮半径(0.025m) < 驱动轮半径(0.05m)
       所以万向轮 Z = -(base_height/2.0) - (0.05-0.025) = -0.075m
       比驱动轮的 -(base_height/2.0) = -0.05m 多下移 0.025m 才能接地
        └──────┘

robot.urdf.xacro


<?xml version="1.0"?>
<!--
  Step 1:纯几何模型(无 Gazebo 插件、无传感器)。
  坐标系与命名遵循 docs/架构设计.md 第3节 TF 坐标树设计:
    base_footprint -> base_link -> {left_wheel_link, right_wheel_link,
                      front_caster_wheel_link, rear_caster_wheel_link}
  后续步骤会在此文件基础上:
    - Step 2  : 引入 gz_plugins.xacro(gz-sim DiffDrive / JointStatePublisher 插件)
    - Step 3~5: 引入 sensors/lidar_2d.xacro、imu.xacro、rgbd_camera.xacro

  本文件仅作为编排入口,通过 xacro:include 按依赖顺序组合各模块。
-->
<robot name="rtabmap_sim_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!-- Step 1a: 参数配置 -->
  <xacro:include filename="$(find rtabmap_sim_description)/urdf/config/robot_params.xacro"/>

  <!-- Step 1b: 惯性计算宏(被 body 和传感器文件引用) -->
  <xacro:include filename="$(find rtabmap_sim_description)/urdf/config/robot_inertia_macros.xacro"/>

  <!-- Step 1c: 机器人本体(base_footprint / base_link / 驱动轮 / 万向轮) -->
  <xacro:include filename="$(find rtabmap_sim_description)/urdf/config/robot_body.xacro"/>

  <!-- Step 2: gz-sim 仿真插件(DiffDrive / JointStatePublisher) -->
  <xacro:include filename="$(find rtabmap_sim_description)/urdf/gz_plugins.xacro"/>

  <!-- Step 3: 2D LiDAR 传感器 -->
  <xacro:include filename="$(find rtabmap_sim_description)/urdf/sensors/lidar_2d.xacro"/>

  <!-- Step 4: IMU 传感器 -->
  <xacro:include filename="$(find rtabmap_sim_description)/urdf/sensors/imu.xacro"/>

  <!-- Step 5: RGBD 相机 -->
  <xacro:include filename="$(find rtabmap_sim_description)/urdf/sensors/rgbd_camera.xacro"/>

</robot>

robot_params.xacro

这辆车有多大、多重


<?xml version="1.0"?>
<!--
  Step 1 参数配置文件:包含机器人几何/物理参数、材质定义和惯量宏。
  由 robot.urdf.xacro 在开头 include 引入。
-->
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!-- ============ 底盘参数 🟢 ============ -->
  <xacro:property name="base_length" value="0.30"/>   <!-- 🟢 底盘长(m),X方向 -->
  <xacro:property name="base_width" value="0.22"/>    <!-- 🟢 底盘宽(m),Y方向 -->
  <xacro:property name="base_height" value="0.10"/>   <!-- 🟢 底盘高(m),Z方向 -->
  <xacro:property name="base_mass" value="5.0"/>      <!-- 🟢 底盘质量(kg),实物可不填 -->

  <!-- ============ 驱动轮参数 🟢 ============ -->
  <xacro:property name="wheel_radius" value="0.05"/>        <!-- 🟢 驱动轮半径(m) -->
  <xacro:property name="wheel_thickness" value="0.04"/>     <!-- 🟢 驱动轮厚度/宽度(m) -->
  <xacro:property name="wheel_mass" value="0.2"/>           <!-- 🟢 单个驱动轮质量(kg),实物可不填 -->
  <xacro:property name="wheel_separation" value="${base_width + wheel_thickness}"/>  <!-- 🟢 左右轮中心距=底盘宽+轮厚 -->
  <xacro:property name="wheel_x_offset" value="0.0"/>       <!-- 🟢 驱动轮在X方向的偏移,0=前后居中 -->

  <!-- ============ 万向轮参数(十字矩形四点支撑布局)🟢 ============ -->
  <!-- 参数说明:底盘前后各一个万向轮,+ 左右两个驱动轮,形成四点支撑 -->
  <xacro:property name="caster_radius" value="0.025"/>                <!-- 🟢 万向轮半径(m),比驱动轮小 -->
  <xacro:property name="caster_mass" value="0.05"/>                   <!-- 🟢 单个万向轮质量(kg),实物可不填 -->
  <!-- 🟢 后万向轮X偏移(车尾方向,负X) = -(半车长 - 半轮半径 - 预留间隙) -->
  <xacro:property name="caster_x_offset" value="${-(base_length/2.0 - caster_radius - 0.01)}"/>
  <!-- 🟢 前万向轮X偏移(车头方向,正X) = 半车长 - 半轮半径 - 预留间隙 -->
  <xacro:property name="front_caster_x_offset" value="${base_length/2.0 - caster_radius - 0.01}"/>

  <!-- 🟢 base_footprint(地面z=0) 到 base_link(几何中心)的Z高度 -->
  <!--     = wheel_radius(轮心到地面) + base_height/2.0(底盘半高) -->
  <xacro:property name="base_footprint_to_base_link_z" value="${wheel_radius + base_height/2.0}"/>

  <!-- ============ 材质 🟢 ============ -->
  <material name="chassis_grey">
    <color rgba="0.6 0.6 0.6 1.0"/>  <!-- 🟢 底盘灰色:RGB 0.6 中灰 -->
  </material>
  <material name="wheel_black">
    <color rgba="0.1 0.1 0.1 1.0"/>  <!-- 🟢 轮子黑色:RGB 0.1 近黑 -->
  </material>
  <material name="caster_dark">
    <color rgba="0.2 0.2 0.2 1.0"/>  <!-- 🟢 万向轮深灰 -->
  </material>

</robot>

robot_inertia_macros.xacro

每个部件的物理特性

🔵 仿真必须:Gazebo 物理引擎需要惯量来计算转动加速度。 🟠 实物可选:实物小车的 ROS 软件栈(Nav2、RSP)不依赖 URDF 中的惯性数据;但如果你要用 ros2_control 做高级控制或者做动力学建模,则需要。

robot_body.xacro

这些部件怎么组装


<?xml version="1.0"?>
<!--
  机器人本体(base_footprint + base_link + 驱动轮 + 万向轮)。
  依赖 robot_params.xacro 中的参数和 robot_inertia_macros.xacro 中的惯量宏。
-->
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!-- ============================ base_footprint ============================ -->
  <!-- 虚拟根坐标系,投影在地面(z=0),Nav2 碰撞检测的参考点 -->
  <link name="base_footprint"/>

  <!-- base_joint:从地面抬升到车身几何中心 -->
  <!-- 升高度 = wheel_radius(轮心高) + base_height/2.0(半车高) -->
  <joint name="base_joint" type="fixed">
    <parent link="base_footprint"/>
    <child link="base_link"/>
    <origin xyz="0 0 ${base_footprint_to_base_link_z}" rpy="0 0 0"/>
  </joint>

  <!-- ============================== base_link =============================== -->
  <!-- 底盘主体:长×宽×高 = base_length × base_width × base_height -->
  <!-- 所有轮子和传感器都装在 base_link 上(parent 都是它) -->
  <link name="base_link">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <box size="${base_length} ${base_width} ${base_height}"/>  <!-- 长(X) × 宽(Y) × 高(Z) -->
      </geometry>
      <material name="chassis_grey"/>
    </visual>
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <box size="${base_length} ${base_width} ${base_height}"/>
      </geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <mass value="${base_mass}"/>  <!-- 底盘质量(kg) -->
      <xacro:box_inertia mass="${base_mass}" lx="${base_length}" ly="${base_width}" lz="${base_height}"/>
    </inertial>
  </link>

  <!-- ======================== drive_wheel 宏 ================================ -->
  <!-- 驱动轮:差速驱动,一轮一宏                                     -->
  <!-- 宏参数:                                                       -->
  <!--   prefix  = "left" / "right"                                   -->
  <!--   reflect = ±1:左轮+1→Y正(左边),右轮-1→Y负(右边)           -->
  <!--                                                                 -->
  <!-- 安装位置:                                                     -->
  <!--   X = wheel_x_offset = 0(前后居中)                           -->
  <!--   Y = reflect × wheel_separation / 2 (左正右负)              -->
  <!--       wheel_separation = base_width + wheel_thickness           -->
  <!--   Z = -(base_height/2.0)(从底盘中心向下到底面再装轮子)       -->
  <!-- ================================================================ -->
  <xacro:macro name="drive_wheel" params="prefix reflect">

    <!-- 轮子 link:圆柱体,绕 X 轴转 90° 让轴向从 Z 变到 Y -->
    <link name="${prefix}_wheel_link">
      <visual>
        <origin xyz="0 0 0" rpy="1.5708 0 0"/>
        <geometry>
          <cylinder radius="${wheel_radius}" length="${wheel_thickness}"/>  <!-- 半径 × 厚度 -->
        </geometry>
        <material name="wheel_black"/>
      </visual>
      <collision>
        <origin xyz="0 0 0" rpy="1.5708 0 0"/>
        <geometry>
          <cylinder radius="${wheel_radius}" length="${wheel_thickness}"/>
        </geometry>
      </collision>
      <inertial>
        <origin xyz="0 0 0" rpy="1.5708 0 0"/>
        <mass value="${wheel_mass}"/>  <!-- 单个驱动轮质量(kg) -->
        <xacro:cylinder_inertia mass="${wheel_mass}" radius="${wheel_radius}" length="${wheel_thickness}"/>
      </inertial>
    </link>

    <!-- 轮子 joint:continuous = 可无限旋转 -->
    <joint name="${prefix}_wheel_joint" type="continuous">
      <parent link="base_link"/>
      <child link="${prefix}_wheel_link"/>
      <origin xyz="
        ${wheel_x_offset}
        ${reflect * wheel_separation / 2.0}
        ${-(base_height/2.0)}" rpy="0 0 0"/>
      <axis xyz="0 1 0"/>  <!-- 绕 Y 轴旋转(差速驱动标准) -->
      <dynamics damping="0.1" friction="0.0"/>
    </joint>
  </xacro:macro>

  <!-- 实例化左右两个驱动轮 -->
  <xacro:drive_wheel prefix="left" reflect="1"/>
  <xacro:drive_wheel prefix="right" reflect="-1"/>

  <!-- ======================== caster_wheel 宏 =============================== -->
  <!-- 万向轮:固定球体被动脚轮,前后各一个,十字矩形四点支撑                 -->
  <!-- 宏参数:                                                               -->
  <!--   prefix = "front" / "rear"                                            -->
  <!--   x, y   = 安装偏移(前轮+X,后轮-X,Y=0 居中)                       -->
  <!--                                                                         -->
  <!-- Z 公式:-(base_height/2.0) - (wheel_radius - caster_radius)            -->
  <!--    = 到底盘底面 + 额外下移                                            -->
  <!--  驱动轮半径(0.05m) > 万向轮半径(0.025m),需要多下移 0.025m 才能接地    -->
  <!-- ======================================================================== -->
  <xacro:macro name="caster_wheel" params="prefix x y">

    <link name="${prefix}_caster_wheel_link">
      <visual>
        <origin xyz="0 0 0" rpy="0 0 0"/>
        <geometry>
          <sphere radius="${caster_radius}"/>
        </geometry>
        <material name="caster_dark"/>
      </visual>
      <collision>
        <origin xyz="0 0 0" rpy="0 0 0"/>
        <geometry>
          <sphere radius="${caster_radius}"/>
        </geometry>
      </collision>
      <inertial>
        <origin xyz="0 0 0" rpy="0 0 0"/>
        <mass value="${caster_mass}"/>  <!-- 单个万向轮质量(kg) -->
        <xacro:sphere_inertia mass="${caster_mass}" radius="${caster_radius}"/>
      </inertial>
    </link>

    <!-- fixed = 固定关节,万向轮不旋转 -->
    <joint name="${prefix}_caster_wheel_joint" type="fixed">
      <parent link="base_link"/>
      <child link="${prefix}_caster_wheel_link"/>
      <origin xyz="${x} ${y} ${-(base_height/2.0) - (wheel_radius - caster_radius)}" rpy="0 0 0"/>
    </joint>
  </xacro:macro>

  <!-- 实例化前后两个万向轮,Y=0 居中 -->
  <!-- front_caster_x_offset = base_length/2.0 - caster_radius - 0.01  (到前边缘,缩半轮半径,留空隙) -->
  <!-- caster_x_offset       = -(base_length/2.0 - caster_radius - 0.01) (方向朝后) -->
  <xacro:caster_wheel prefix="front" x="${front_caster_x_offset}" y="0"/>
  <xacro:caster_wheel prefix="rear"  x="${caster_x_offset}" y="0"/>

</robot>

lidar_2d.xacro

2D激光雷达模型参数


<?xml version="1.0"?>
<!--
  Step 3:2D LiDAR。
  base_link -> laser_link 固定关节(几何/惯量),并通过 <gazebo reference="laser_link">
  声明 gz-sim gpu_lidar 传感器(由 world 级别已加载的 gz::sim::systems::Sensors 插件驱动,
  无需在此额外声明 <plugin>)。

  桥接:/scan(GZ→ROS),见 config/ros_gz_bridge.yaml。
-->
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!-- 🟢 以下参数仿真和实物共用,决定传感器外观尺寸和TF位置 -->
  <xacro:property name="laser_radius" value="0.03"/>   <!-- 🟢 激光雷达外壳圆柱半径(m) -->
  <xacro:property name="laser_height" value="0.04"/>   <!-- 🟢 激光雷达外壳圆柱高度(m) -->
  <xacro:property name="laser_mass" value="0.05"/>     <!-- 🟢 激光雷达质量(kg),实物可不填 -->

  <material name="laser_orange">
    <color rgba="0.9 0.4 0.05 1.0"/>  <!-- 🟢 橙色,RViz中显示用 -->
  </material>

  <!-- 🟢 link + joint:描述传感器安装位置,实物也需要(定义TF树和碰撞体积) -->
  <!-- 🔵 inertial:实物不依赖URDF惯量,但仿真需要 -->
  <link name="laser_link">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <cylinder radius="${laser_radius}" length="${laser_height}"/>
      </geometry>
      <material name="laser_orange"/>
    </visual>
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <cylinder radius="${laser_radius}" length="${laser_height}"/>
      </geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <mass value="${laser_mass}"/>
      <xacro:cylinder_inertia mass="${laser_mass}" radius="${laser_radius}" length="${laser_height}"/>
    </inertial>
  </link>

  <!-- 🟢 安装在底盘正上方中心,实物传感器也装在这个位置 -->
  <!-- 🟢 Z = base_height/2.0(底盘半高) + laser_height/2.0(传感器半高),让底部贴紧底盘顶面 -->
  <joint name="laser_joint" type="fixed">
    <parent link="base_link"/>
    <child link="laser_link"/>
    <origin xyz="0 0 ${base_height/2.0 + laser_height/2.0}" rpy="0 0 0"/>
  </joint>

  <!-- ====================================================================== -->
  <!-- 🔵 以下为Gazebo仿真专用,物理小车不需要 -->
  <!--     实物小车用自己的激光雷达硬件驱动发布/scan话题               -->
  <!-- ====================================================================== -->
  <gazebo reference="laser_link">
    <sensor name="lidar_2d" type="gpu_lidar">  <!-- 🔵 GPU加速的2D激光雷达仿真传感器 -->
      <pose>0 0 0 0 0 0</pose>  <!-- 🔵 传感器相对laser_link的偏移,0=重合 -->
      <topic>scan</topic>  <!-- 🔵 Gazebo发布的话题名,经ros_gz_bridge桥接为ROS2的/scan -->
      <gz_frame_id>laser_link</gz_frame_id>  <!-- 🔵 scan消息的frame_id,与link名一致 -->
      <update_rate>10</update_rate>  <!-- 🔵 仿真扫描频率(Hz) -->
      <always_on>true</always_on>  <!-- 🔵 仿真启动后持续运行 -->
      <visualize>true</visualize>  <!-- 🔵 在Gazebo GUI中显示激光射线,便于调试 -->
      <lidar>
        <scan>
          <horizontal>
            <samples>360</samples>           <!-- 🔵 每圈采样点数,360=1°/点 -->
            <resolution>1</resolution>       <!-- 🔵 相邻射线间距(°) -->
            <min_angle>-3.14159265</min_angle>  <!-- 🔵 扫描起始角(rad),-π=正右方开始 -->
            <max_angle>3.14159265</max_angle>    <!-- 🔵 扫描结束角(rad),+π=正右方结束=完整360° -->
          </horizontal>
        </scan>
        <range>
          <min>0.12</min>    <!-- 🔵 最小探测距离(m) -->
          <max>12.0</max>    <!-- 🔵 最大探测距离(m) -->
          <resolution>0.01</resolution>  <!-- 🔵 距离分辨率(m) -->
        </range>
      </lidar>
    </sensor>
  </gazebo>

</robot>


rgbd_camera.xacro


<?xml version="1.0"?>
<!--
  Step 5:RGBD 相机。
  base_link -> camera_link(机械安装系,X 前/Z 上,与机器人本体一致)
             -> camera_rgb_frame -> camera_rgb_optical_frame(光学系,REP-103:Z前/X右/Y下)

  gz-sim 的 rgbd_camera 传感器是"单一视点"模拟(不像真实硬件 RGB 与 Depth 光心分离),
  因此 color/depth 两路图像共用同一个 gz_frame_id = camera_rgb_optical_frame,
  不再单独建 camera_depth_optical_frame(与设计文档相比的一个基于仿真实现现实的简化,
  已记录在仓库记忆中)。

  桥接:/camera/color/image_raw、/camera/color/camera_info、
        /camera/depth/image_raw、/camera/depth/camera_info(均 GZ→ROS),
  见 config/ros_gz_bridge.yaml。
-->
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!-- 🟢 相机外壳尺寸和质量参数,仿真和实物共用(实物质量可不填) -->
  <xacro:property name="camera_len_x" value="0.03"/>    <!-- 🟢 相机外壳长(m),X方向(车身前后) -->
  <xacro:property name="camera_len_y" value="0.09"/>    <!-- 🟢 相机外壳宽(m),Y方向(车身左右) -->
  <xacro:property name="camera_len_z" value="0.025"/>   <!-- 🟢 相机外壳高(m),Z方向 -->
  <xacro:property name="camera_mass" value="0.1"/>      <!-- 🟢 相机质量(kg) -->

  <!-- 🟢 安装位置参数 -->
  <!-- 🟢 camera_x_offset:相机安装在底盘前缘(X正方向),离中心 0.10m -->
  <!-- 🟢 mast_height:桅杆高度 0.15m,抬高相机避免视野被地板占据 -->
  <!-- 🟢 camera_z_offset = 底盘半高 + mast_height + 相机半高,从地面算起 -->
  <xacro:property name="camera_x_offset" value="0.10"/>
  <xacro:property name="mast_height" value="0.15"/>
  <xacro:property name="camera_z_offset" value="${base_height/2.0 + mast_height + camera_len_z/2.0}"/>

  <material name="camera_black">
    <color rgba="0.05 0.05 0.05 1.0"/>
  </material>

  <!-- 🟢 camera_link + camera_joint:定义相机安装位置和TF -->
  <!-- 🔵 inertial:实物不需要URDF惯量 -->
  <link name="camera_link">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <box size="${camera_len_x} ${camera_len_y} ${camera_len_z}"/>
      </geometry>
      <material name="camera_black"/>
    </visual>
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <box size="${camera_len_x} ${camera_len_y} ${camera_len_z}"/>
      </geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <mass value="${camera_mass}"/>
      <xacro:box_inertia mass="${camera_mass}" lx="${camera_len_x}" ly="${camera_len_y}" lz="${camera_len_z}"/>
    </inertial>
  </link>

  <!-- 🟢 相机安装在底盘前上方,Z 抬高约 0.21m -->
  <joint name="camera_joint" type="fixed">
    <parent link="base_link"/>
    <child link="camera_link"/>
    <origin xyz="${camera_x_offset} 0 ${camera_z_offset}" rpy="0 0 0"/>
  </joint>

  <!-- 🟢 光学坐标系:纯坐标系 link,无几何/惯量,仅用于TF树 -->
  <link name="camera_rgb_frame"/>
  <joint name="camera_rgb_joint" type="fixed">
    <parent link="camera_link"/>
    <child link="camera_rgb_frame"/>
    <origin xyz="0 0 0" rpy="0 0 0"/>  <!-- 🟢 与camera_link原点重合 -->
  </joint>

  <link name="camera_rgb_optical_frame"/>
  <joint name="camera_rgb_optical_joint" type="fixed">
    <parent link="camera_rgb_frame"/>
    <child link="camera_rgb_optical_frame"/>
    <!-- 🟢 REP-103 旋转:机体系(X前/Y左/Z上) → 光学系(Z前/X右/Y下) -->
    <!--   rpy顺序:先绕Z转-90°(X→Y),再绕X转-90°(Z→X),结果Z指向前方 -->
    <origin xyz="0 0 0" rpy="${-pi/2} 0 ${-pi/2}"/>
  </joint>

  <!-- ====================================================================== -->
  <!-- 🔵 以下为Gazebo仿真专用,物理小车不需要                         -->
  <!--     实物RGBD相机由硬件驱动发布 /camera/color/image_raw 等话题    -->
  <!-- ====================================================================== -->
  <gazebo reference="camera_link">
    <sensor name="rgbd_camera" type="rgbd_camera">  <!-- 🔵 RGBD相机仿真传感器 -->
      <!-- 🔵 <pose>与camera_link一致,identity即沿本地+X看 -->
      <pose>0 0 0 0 0 0</pose>
      <topic>camera</topic>  <!-- 🔵 Gazebo发布的话题前缀,桥接后为/camera/* -->
      <gz_frame_id>camera_rgb_optical_frame</gz_frame_id>  <!-- 🔵 图像消息的frame_id -->
      <update_rate>15</update_rate>  <!-- 🔵 帧率(Hz),标准RGBD相机约15~30Hz -->
      <always_on>true</always_on>  <!-- 🔵 仿真启动后持续运行 -->
      <camera>
        <horizontal_fov>1.047</horizontal_fov>  <!-- 🔵 水平视场角(rad),约60° -->
        <image>
          <width>424</width>        <!-- 🔵 图像宽度(像素),降低分辨率可提升仿真性能 -->
          <height>240</height>      <!-- 🔵 图像高度(像素) -->
          <format>R8G8B8</format>   <!-- 🔵 RGB彩色图,8bit/通道 -->
        </image>
        <clip>
          <near>0.1</near>   <!-- 🔵 最近渲染距离(m),小于此值不显示 -->
          <far>10.0</far>    <!-- 🔵 最远渲染距离(m),超出此值不显示 -->
        </clip>
      </camera>
      <!-- 🔵 深度相机:必须显式声明,否则Gazebo不初始化深度渲染管线 -->
      <depth_camera>
        <clip>
          <near>0.1</near>   <!-- 🔵 深度图最近探测距离(m) -->
          <far>10.0</far>    <!-- 🔵 深度图最远探测距离(m) -->
        </clip>
      </depth_camera>
    </sensor>
  </gazebo>

</robot>

imu.xacro


<?xml version="1.0"?>
<!--
  Step 4:IMU。
  base_link -> imu_link 固定关节(几何/惯量很小,贴近底盘中心安装),
  通过 <gazebo reference="imu_link"> 声明 gz-sim imu 传感器(由 world 级别已加载的
  gz::sim::systems::Imu 插件驱动)。

  桥接:/imu/data(GZ→ROS),见 config/ros_gz_bridge.yaml。
  注意 gz 消息类型是 `gz.msgs.IMU`(全大写 IMU,不是 Imu)。
-->
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!-- 🟢 IMU体积和质量参数,仿真和实物共用(实物质量可不填) -->
  <xacro:property name="imu_size" value="0.02"/>    <!-- 🟢 IMU外壳边长(m),立方体 -->
  <xacro:property name="imu_mass" value="0.02"/>   <!-- 🟢 IMU质量(kg) -->

  <material name="imu_red">
    <color rgba="0.8 0.1 0.1 1.0"/>  <!-- 🟢 红色,RViz中识别用 -->
  </material>

  <!-- 🟢 imu_link:定义IMU外观和碰撞体积,实物也需要 -->
  <!-- 🔵 inertial:实物不需要URDF惯量 -->
  <link name="imu_link">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <box size="${imu_size} ${imu_size} ${imu_size}"/>  <!-- 🟢 小立方体 -->
      </geometry>
      <material name="imu_red"/>
    </visual>
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <box size="${imu_size} ${imu_size} ${imu_size}"/>
      </geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <mass value="${imu_mass}"/>  <!-- 🟢 IMU质量(kg) -->
      <xacro:box_inertia mass="${imu_mass}" lx="${imu_size}" ly="${imu_size}" lz="${imu_size}"/>
    </inertial>
  </link>

  <!-- 🟢 安装在底盘几何中心(0,0,0),实物IMU也建议装在重心附近以减少离心力干扰 -->
  <joint name="imu_joint" type="fixed">
    <parent link="base_link"/>
    <child link="imu_link"/>
    <origin xyz="0 0 0" rpy="0 0 0"/>  <!-- 🟢 与base_link原点完全重合 -->
  </joint>

  <!-- ====================================================================== -->
  <!-- 🔵 以下为Gazebo仿真专用,物理小车不需要                         -->
  <!--     实物IMU由硬件驱动发布 /imu/data 话题                        -->
  <!-- ====================================================================== -->
  <gazebo reference="imu_link">
    <sensor name="imu_sensor" type="imu">  <!-- 🔵 IMU仿真传感器 -->
      <pose>0 0 0 0 0 0</pose>  <!-- 🔵 传感器与imu_link原点重合 -->
      <topic>imu/data</topic>  <!-- 🔵 Gazebo话题名,桥接后为ROS2的/imu/data -->
      <gz_frame_id>imu_link</gz_frame_id>  <!-- 🔵 IMU消息的frame_id -->
      <update_rate>100</update_rate>  <!-- 🔵 IMU采样频率(Hz),实物IMU典型值100~400Hz -->
      <always_on>true</always_on>  <!-- 🔵 仿真启动后持续运行 -->
      <imu>
        <angular_velocity>  <!-- 🔵 角速度噪声:高斯噪声 N(mean, stddev),rad/s -->
          <x><noise type="gaussian"><mean>0.0</mean><stddev>0.0002</stddev></noise></x>  <!-- 🔵 X轴角速度噪声标准差 0.0002 rad/s -->
          <y><noise type="gaussian"><mean>0.0</mean><stddev>0.0002</stddev></noise></y>
          <z><noise type="gaussian"><mean>0.0</mean><stddev>0.0002</stddev></noise></z>
        </angular_velocity>
        <linear_acceleration>  <!-- 🔵 线加速度噪声:高斯噪声,m/s² -->
          <x><noise type="gaussian"><mean>0.0</mean><stddev>0.017</stddev></noise></x>  <!-- 🔵 X轴线加速度噪声标准差 0.017 m/s² -->
          <y><noise type="gaussian"><mean>0.0</mean><stddev>0.017</stddev></noise></y>
          <z><noise type="gaussian"><mean>0.0</mean><stddev>0.017</stddev></noise></z>
        </linear_acceleration>
      </imu>
    </sensor>
  </gazebo>

</robot>

读取机器人模型参数命令


ros2 param get /robot_state_publisher robot_description

display.launch.py


#!/usr/bin/env python3
"""🔵 Step 1 验证用 launch 文件:仅加载机器人几何模型,不接入 Gazebo。

启动内容(每个 Node 为一个独立进程):
  - robot_state_publisher  : 🟢 订阅 /joint_states,发布 TF 与 robot_description
  - joint_state_publisher_gui: 🔵 提供滑块手动驱动各关节,模拟 /joint_states
  - rviz2                  : 🟢 加载预置视图,显示 RobotModel + TF

🔵 本 launch 是仿真/开发调试工具,不是实物运行时的启动入口。
    实物小车另有自己的 launch 文件(启动硬件驱动等),不会用这个。

用法:
  ros2 launch rtabmap_sim_description display.launch.py
"""

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import Command, LaunchConfiguration, PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.descriptions import ParameterValue
from launch_ros.substitutions import FindPackageShare


def generate_launch_description():
    """构建 launch 描述:声明参数 → 定义节点 → 返回 LaunchDescription。"""
    # 通过 FindPackageShare 获取本包在 install 目录下的路径,用于构造模型和 RViz 配置文件的绝对路径
    pkg_share = FindPackageShare("rtabmap_sim_description")

    # 默认模型路径:install/.../urdf/robot.urdf.xacro
    default_model_path = PathJoinSubstitution(
        [pkg_share, "urdf", "robot.urdf.xacro"]
    )
    # 默认 RViz 配置文件路径:install/.../rviz/display.rviz
    default_rviz_config_path = PathJoinSubstitution(
        [pkg_share, "rviz", "display.rviz"]
    )

    # 声明 launch 参数,允许用户在命令行覆盖:ros2 launch ... model:=<你的路径>
    model_arg = DeclareLaunchArgument(
        name="model",
        default_value=default_model_path,
        description="机器人 xacro 模型文件的绝对路径",
    )
    rviz_arg = DeclareLaunchArgument(
        name="rviz_config",
        default_value=default_rviz_config_path,
        description="RViz2 预置视图配置文件路径",
    )

    # 编译 xacro 得到 robot_description(运行 `xacro` 命令,结果作为字符串传入参数)
    robot_description = {
        "robot_description": ParameterValue(
            Command(["xacro ", LaunchConfiguration("model")]), value_type=str
        )
    }

    # 🟢 节点 1:robot_state_publisher
    # 🟢 加载 robot_description → 发布 /robot_description 话题 + 根据 URDF 中的 joint 定义发布 TF
    # 🟢 实物也需要它来宣告机器人模型和 TF 树
    robot_state_publisher_node = Node(
        package="robot_state_publisher",
        executable="robot_state_publisher",
        name="robot_state_publisher",
        output="screen",
        parameters=[robot_description],
    )

    # 🔵 节点 2:joint_state_publisher_gui
    # 🔵 弹出滑块窗口,用户手动拖动各关节 → 发布 /joint_states
    # 🔵 实物不需要:实物有电机编码器驱动直接发布真实的 /joint_states
    joint_state_publisher_gui_node = Node(
        package="joint_state_publisher_gui",
        executable="joint_state_publisher_gui",
        name="joint_state_publisher_gui",
        output="screen",
    )

    # 🟢 节点 3:rviz2
    # 🟢 3D 可视化窗口,加载预置的 display.rviz 视图(已配置 RobotModel + TF 显示)
    # 🟢 实物开发调试时也需要 RViz 查看传感器数据
    rviz_node = Node(
        package="rviz2",
        executable="rviz2",
        name="rviz2",
        output="screen",
        arguments=["-d", LaunchConfiguration("rviz_config")],
    )

    # 按顺序组装:先声明参数,再依次启动 3 个节点
    return LaunchDescription(
        [
            model_arg,
            rviz_arg,
            robot_state_publisher_node,
            joint_state_publisher_gui_node,
            rviz_node,
        ]
    )