ROS2 无人车学习

RtabMap SLAM 导航

Posted by LXG on June 29, 2026

建图模式

mapping_localization.sh


source_with_nounset_guard /opt/ros/humble/setup.bash

reset_rtabmap_database_on_start "$RTABMAP_DATABASE_PATH"

echo ""
echo "============================================"
echo "   RTABMAP建图 + Nav2导航"
echo "============================================"
echo ""
echo "RTAB-Map 数据库: $RTABMAP_DATABASE_PATH"
echo "启动前删除数据库: $RTABMAP_DELETE_DB_ON_START"
echo "RViz 配置: $MAP_RVIZ_CONFIG"
echo "机器人模型配置: $ROBOT_DESCRIPTION_CONFIG_FILE"
echo "Nav2 参数文件: $NAV2_PARAMS_FILE"
echo "Nav2 启动入口: $NAV2_LAUNCH_PACKAGE/$NAV2_LAUNCH_FILE"
echo "启用 robot_localization: $ENABLE_ROBOT_LOCALIZATION"
if [ "$ENABLE_ROBOT_LOCALIZATION" = "1" ]; then
  echo "robot_localization 参数文件: $ROBOT_LOCALIZATION_PARAMS_FILE"
  echo "视觉里程计话题: $RTABMAP_RGBD_ODOM_TOPIC"
  echo "激光里程计话题: $RTABMAP_LASER_ODOM_TOPIC"
  echo "融合里程计话题: $FUSED_ODOM_TOPIC"
fi
echo "启用 BlueSea 雷达: $ENABLE_BLUESEA_LIDAR"
if [ "$ENABLE_BLUESEA_LIDAR" = "1" ]; then
  echo "BlueSea 双雷达 launch: $BLUESEA_LIDAR_LAUNCH_FILE"
  echo "左雷达参数文件: $BLUESEA_LEFT_PARAMS_FILE"
  echo "右雷达参数文件: $BLUESEA_RIGHT_PARAMS_FILE"
  echo "雷达融合参数文件: $BLUESEA_SCAN_MERGER_PARAMS_FILE"
fi
echo "RTAB-Map 地面分割: $RTABMAP_GRID_GROUND_SEGMENTATION"
echo "RTAB-Map 订阅激光: $RTABMAP_SUBSCRIBE_SCAN"
echo "RTAB-Map 图像降采样: $RTABMAP_ODOM_IMAGE_DECIMATION"
echo "RTAB-Map 最大特征数: $RTABMAP_VIS_MAX_FEATURES"
echo "RTAB-Map 局部地图大小: $RTABMAP_ODOM_F2M_MAX_SIZE"
echo "RTAB-Map 里程计BA: $RTABMAP_ODOM_F2M_BUNDLE_ADJUSTMENT"
echo "RTAB-Map 建图位移更新阈值: $RTABMAP_RGBD_LINEAR_UPDATE"
echo "RTAB-Map 建图角度更新阈值: $RTABMAP_RGBD_ANGULAR_UPDATE"
echo "底盘控制脚本: $BASE_CONTROLLER_SCRIPT"
echo "终端日志目录: $TERMINAL_LOG_DIR"
echo ""

启动机器人模型与固定传感器TF


echo "1. 启动机器人模型与固定传感器TF"
launch_terminal "[1] Robot Model" "
  ${ROBOT_MODEL_PREFIX:+$ROBOT_MODEL_PREFIX }ros2 launch description diff_base.launch.py \
  use_rviz:=false \
  config_file:=$ROBOT_DESCRIPTION_CONFIG_FILE
"

启动 BlueSea 双雷达与 LaserScan 融合”


if [ "$ENABLE_BLUESEA_LIDAR" = "1" ]; then
  wait_for_tf_transform "base_link" "lidar_left" 20 || true
  wait_for_tf_transform "base_link" "lidar_right" 20 || true

  echo "2. 启动 BlueSea 双雷达与 LaserScan 融合"
  launch_terminal "[2] BlueSea LiDAR" "
    ${LIDAR_PREFIX:+$LIDAR_PREFIX }ros2 launch '$BLUESEA_LIDAR_LAUNCH_FILE' \
      left_params_file:='$BLUESEA_LEFT_PARAMS_FILE' \
      right_params_file:='$BLUESEA_RIGHT_PARAMS_FILE' \
      enable_scan_merger:=true \
      scan_merger_params_file:='$BLUESEA_SCAN_MERGER_PARAMS_FILE' \
      use_robot_model:=false \
      start_rviz:=false \
      use_sim_time:=false
  "
else
  echo "2. 跳过 BlueSea 激光雷达,ENABLE_BLUESEA_LIDAR=$ENABLE_BLUESEA_LIDAR"
fi

启动深度相机


echo "3. 启动深度相机"
launch_terminal "[3] Depth Camera" "
  ${CAMERA_PREFIX:+$CAMERA_PREFIX }ros2 launch orbbec_camera gemini_330_series.launch.py \
    camera_name:=camera \
    publish_tf:=true \
    enable_sync_host_time:=true \
    time_domain:=global \
    enable_color:=true \
    enable_depth:=true \
    enable_accel:=true \
    enable_gyro:=true \
    enable_sync_output_accel_gyro:=true \
    enable_frame_sync:=true \
    color_width:=640 \
    color_height:=480 \
    color_fps:=30 \
    depth_width:=640 \
    depth_height:=480 \
    depth_fps:=30 \
    align_mode:=HW \
    depth_registration:=true \
    enable_point_cloud:=false \
    color_qos:=default \
    depth_qos:=default \
    color_camera_info_qos:=default \
    depth_camera_info_qos:=default
"
sleep 3

启动底盘控制


if [ "$ENABLE_BASE_CONTROLLER" = "1" ]; then
  echo "4. 启动底盘控制"
  launch_terminal "[4] Base Controller" "
    ${BASE_CONTROLLER_PREFIX:+$BASE_CONTROLLER_PREFIX }python3 '$BASE_CONTROLLER_SCRIPT'
  "
  sleep 2
else
  echo "4. 跳过底盘控制,ENABLE_BASE_CONTROLLER=$ENABLE_BASE_CONTROLLER"
fi

启动视觉里程计


echo "5. 启动视觉里程计"
launch_terminal "[5] RGBD Odometry" "
${RGBD_ODOM_PREFIX:+$RGBD_ODOM_PREFIX }ros2 run rtabmap_odom rgbd_odometry \
    --Vis/MinInliers 12 \
    --OdomF2M/BundleAdjustment false \
    --Reg/Force3DoF true \
    --ros-args \
    -p frame_id:=base_link \
    -p odom_frame_id:=odom \
    -p publish_tf:=false \
    -p wait_for_transform:=0.2 \
    -p wait_imu_to_init:=true \
    -p approx_sync:=true \
    -p approx_sync_max_interval:=0.02 \
    -p topic_queue_size:=30 \
    -p sync_queue_size:=30 \
    -p qos:=0 \
    -p qos_camera_info:=0 \
    -p qos_imu:=0 \
    -r rgb/image:=/camera/color/image_raw \
    -r depth/image:=/camera/depth/image_raw \
    -r rgb/camera_info:=/camera/color/camera_info \
    -r imu:=/imu/data \
    -r odom:=$RTABMAP_RGBD_ODOM_TOPIC
"
sleep 2

启动激光ICP里程计


if [ "$ENABLE_BLUESEA_LIDAR" = "1" ]; then
  echo "6. 启动激光ICP里程计"
  launch_terminal "[6] Laser ICP Odometry" "
${LASER_ODOM_PREFIX:+$LASER_ODOM_PREFIX }ros2 run rtabmap_odom icp_odometry \
    --Reg/Force3DoF true \
    --Icp/PointToPlane false \
    --Icp/MaxCorrespondenceDistance 0.3 \
    --ros-args \
    -p frame_id:=base_link \
    -p odom_frame_id:=odom \
    -p publish_tf:=false \
    -p wait_for_transform:=0.2 \
    -p wait_imu_to_init:=false \
    -p scan_range_min:=0.2 \
    -p scan_range_max:=15.0 \
    -p scan_voxel_size:=0.05 \
    -p qos:=1 \
    -p qos_imu:=1 \
    -r scan:=/scan \
    -r imu:=/imu/data \
    -r odom:=$RTABMAP_LASER_ODOM_TOPIC
  "
  sleep 2
else
  echo "6. 跳过激光ICP里程计,ENABLE_BLUESEA_LIDAR=$ENABLE_BLUESEA_LIDAR"
fi

启动 robot_localization 融合视觉/激光里程计


if [ "$ENABLE_ROBOT_LOCALIZATION" = "1" ]; then
  echo "7. 启动 robot_localization 融合视觉/激光里程计"
  launch_terminal "[7] Robot Localization EKF" "
${ROBOT_LOCALIZATION_PREFIX:+$ROBOT_LOCALIZATION_PREFIX }ros2 run robot_localization ekf_node \
    --ros-args \
    --params-file '$ROBOT_LOCALIZATION_PARAMS_FILE' \
    -r odometry/filtered:=$FUSED_ODOM_TOPIC
  "
  sleep 2
else
  echo "7. 跳过 robot_localization,ENABLE_ROBOT_LOCALIZATION=$ENABLE_ROBOT_LOCALIZATION"
fi

启动RTABMAP建图


echo "8. 启动RTABMAP建图"
launch_terminal "[8] RTABMAP Mapping" "
${RTABMAP_PREFIX:+$RTABMAP_PREFIX }ros2 launch rtabmap_launch rtabmap.launch.py \
    database_path:=$RTABMAP_DATABASE_PATH \
    rtabmap_viz:=false \
    rviz:=true \
    rviz_cfg:=$MAP_RVIZ_CONFIG \
    frame_id:=base_link \
    visual_odometry:=false \
    icp_odometry:=false \
    vo_frame_id:=odom \
    odom_topic:=$FUSED_ODOM_TOPIC \
    publish_tf_map:=true \
    publish_tf_odom:=false \
    rgb_topic:=/camera/color/image_raw \
    depth_topic:=/camera/depth/image_raw \
    camera_info_topic:=/camera/color/camera_info \
    imu_topic:=/imu/data \
    wait_imu_to_init:=true \
    approx_sync:=true \
    approx_sync_max_interval:=0.02 \
    subscribe_scan:=$RTABMAP_SUBSCRIBE_SCAN \
    scan_topic:=/scan \
    topic_queue_size:=30 \
    sync_queue_size:=30 \
    odom_args:='
        --Vis/MinInliers 25
        --OdomF2M/BundleAdjustment true
    ' \
    args:='
        --RGBD/LinearUpdate 0.05
        --RGBD/AngularUpdate 0.03
        --Optimizer/PriorsIgnored false
        --Optimizer/GravitySigma 0.0
        --Reg/Force3DoF true
        --Grid/FromDepth false
        --Grid/Sensor 0
        --Grid/RangeMax 5.0
        --Grid/RangeMin 0.2
        --Grid/CellSize 0.05
        --Grid/MaxGroundHeight $RTABMAP_GRID_MAX_GROUND_HEIGHT
        --Grid/MinGroundHeight $RTABMAP_GRID_MIN_GROUND_HEIGHT
        --Grid/MaxObstacleHeight $RTABMAP_GRID_MAX_OBSTACLE_HEIGHT
        --Grid/NormalsSegmentation $RTABMAP_GRID_GROUND_SEGMENTATION
        --Grid/GroundIsObstacle $RTABMAP_GRID_GROUND_IS_OBSTACLE
        --Grid/MaxGroundAngle $RTABMAP_GRID_MAX_GROUND_ANGLE
        --Grid/ClusterRadius 0.1
        --Grid/MinClusterSize 10
        --Grid/NoiseFilteringRadius 0.1
        --Grid/NoiseFilteringMinNeighbors 8
        --Grid/FlatObstacleDetected $RTABMAP_GRID_FLAT_OBSTACLE_DETECTED
        --Grid/RayTracing true
        --Grid/3D false
    '
"
sleep 3

启动Nav2导航(边建图边导航,静态/动态障碍物避障)


echo "9. 启动Nav2导航(边建图边导航,静态/动态障碍物避障)"
launch_terminal "[9] Nav2 Navigation" "
${NAV2_PREFIX:+$NAV2_PREFIX }ros2 launch '$NAV2_LAUNCH_PACKAGE' '$NAV2_LAUNCH_FILE' \
    use_sim_time:=false \
    params_file:=$NAV2_PARAMS_FILE \
    autostart:=true \
    use_composition:=False \
    use_respawn:=False \
    log_level:=info
"

启动 Collision Monitor(/scan 紧急防撞)


echo "10. 启动 Collision Monitor(/scan 紧急防撞)"
launch_terminal "[10] Collision Monitor" "
${COLLISION_MONITOR_PREFIX:+$COLLISION_MONITOR_PREFIX }ros2 launch nav2_collision_monitor collision_monitor_node.launch.py \
    use_sim_time:=false \
    params_file:=$NAV2_PARAMS_FILE
"

无人车底盘

差速底盘

差速底盘(Differential Drive)是移动机器人里最经典、也是 ROS 生态最“默认假设”的一种运动模型。它的核心思想其实很简单:用左右两侧轮子的速度差,来控制机器人在平面上的运动与转向。

差速底盘的运动只用两个量描述:

v:线速度(forward/backward) ω:角速度(旋转)

👉 “通过左右轮速度差,控制机器人在平面上做前进 + 转向组合运动”

四轮车做差速通常有三种工程方案:

方案 本质 工程常见度
1️⃣ 左右双电机(推荐) 左侧2轮同速 + 右侧2轮同速 ⭐⭐⭐⭐⭐
2️⃣ 4电机独立控制 4轮全独立,再做“虚拟差速” ⭐⭐⭐
3️⃣ 中央差速器(机械) 类似汽车结构 ⭐⭐

全向底盘

全向底盘(Omni-directional / Mecanum / Holonomic Drive)可以简单理解为:

🚗 机器人可以“往任何方向走”,包括横移、斜移、原地旋转,而不需要转弯半径

为什么它“看起来很高级”但工程很难?


❌ 1. 非常容易打滑

因为:

轮子本身就允许侧滑
地面不平 → 模型失真
❌ 2. odom 非常容易漂

RTAB-Map / EKF 会遇到:

x/y 同时误差
方向不稳定
❌ 3. 对地面要求高
地面	表现
光滑地板	很好
水泥地	一般
室外粗糙路面	很差
❌ 4. 控制更复杂

需要:

逆运动学矩阵
4轮解耦控制
轮速同步

diff_base.yaml 描述底盘结构



                         Z↑ (高度方向)

                GPS (front mast, ~1.07m)
                         
                         |
                         |
                Camera (0.884m)
                         
                         |
        ┌────────────────┴────────────────┐
        |                                 |
   Lidar Left                        Lidar Right
 (0.388, +0.285)                 (0.388, -0.285)
   yaw +45°                        yaw -45°
        \                               /
         \                             /
          \                           /
           ┌──────── base_link ────────┐
           |        (0,0,0)            |
           |                            |
           |   wheels / chassis        |
           |                            |
           └─────────┬──────────────────┘
                     |
                     |
                Rear GPS
                 
                 |
                (-0.325, rear mast)

diff_base.launch.py

1️⃣ 📦 读取机器人配置(YAML)

2️⃣ 🔧 生成 URDF(Xacro → robot_description)

3️⃣ 🚀 启动 ROS 运行系统


YAML配置
   ↓
Python解析
   ↓
Xacro参数映射
   ↓
URDF(robot_description)
   ↓
robot_state_publisher
   ↓
TF树发布
   ↓
RViz显示

双激光雷达

dual_lidar.launch.py

双雷达系统集成编排层(System Orchestration Layer)

它不做算法,只负责:

  1. 启动两个雷达驱动
  2. 建立 TF 坐标关系
  3. 融合 scan
  4. 提供 RViz 可视化
  5. 可选加载机器人模型

dual_lidar.launch.py
│
├── 1️⃣ 参数层(LaunchArguments)
├── 2️⃣ 机器人模型层(robot_state_publisher)
├── 3️⃣ 传感器层(left / right lidar)
├── 4️⃣ 融合层(scan_merger)
└── 5️⃣ 可视化层(RViz)

controller_mqtt.py

一个“多输入源 + 四轮滑移底盘控制 + VIO闭环 + MQTT/串口输出”的ROS2底盘控制中间层


┌─────────────────────────────────────────────┐
│                 输入层                      │
├─────────────────────────────────────────────┤
│ ROS cmd_vel (Twist)                        │
│ Web HTTP (Twist / RPM)                     │
│ MQTT 云控 (Twist)                         │
│ SundayRobot MQTT (Twist)                  │
│ Odometry / VIO (反馈)                     │
└───────────────┬─────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────────┐
│           多缓存状态层(核心)              │
│---------------------------------------------│
│ _latest_ros_cmd (Twist)                    │
│ _latest_web_cmd (Twist)                    │
│ _latest_web_motor_rpm (RPM)                │
│ _latest_mqtt_cmd (Twist)                   │
│ _latest_odom_twist (feedback)              │
│ + 每个都有 timestamp                        │
└───────────────┬─────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────────┐
│      候选生成层(你图里缺的关键层)         │
│---------------------------------------------│
│ Candidate A: ROS TWIST                      │
│ Candidate B: WEB TWIST                     │
│ Candidate C: WEB RPM  ⭐(独立通道)        │
│ Candidate D: MQTT TWIST                    │
│                                         │
│ ⚠ 注意:RPM不是Twist体系!                  │
└───────────────┬─────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────────┐
│      隐式仲裁层 (_pick_fresh_control)       │
│---------------------------------------------│
│ 1. 时间戳最新优先                           │
│ 2. ROS 防抢占规则                           │
│ 3. neutral过滤(避免0命令覆盖Nav)          │
│ 4. Twist / RPM 分流选择                     │
└───────────────┬─────────────────────────────┘
        ┌───────┴────────┐
        ▼                ▼
┌──────────────┐   ┌─────────────────┐
│ TWIST路径     │   │ RPM路径          │
└──────┬───────┘   └──────┬──────────┘
       │                  │
       ▼                  ▼
┌──────────────────┐  ┌──────────────────┐
│ VIO PID闭环       │  │ 直接输出          │
│ _apply_speed_comp │  │ (skip kinematics) │
└──────┬───────────┘  └─────────┬────────┘
       │                         │
       ▼                         ▼
┌─────────────────────────────────────────────┐
│           四轮滑移运动学模型               │
│---------------------------------------------│
│ skid-steer kinematics                      │
│ slip_factor 修正                           │
│ front_rear_ratio                          │
└───────────────┬─────────────────────────────┘
                ▼
┌─────────────────────────────────────────────┐
│               输出层                        │
├─────────────────────────────────────────────┤
│ MQTT wheel_speed                          │
│ 串口 JSON (FOC驱动板)                     │
│ 状态上报 MQTT                             │
└─────────────────────────────────────────────┘

rtabmap 视觉里程计和激光里程计

RTAB-Map 里的视觉里程计(VO)和激光里程计(LO),本质都是在做同一件事:估计相邻帧(或相邻扫描)之间的运动(6DoF pose)

项目 视觉里程计(VO) 激光里程计(LO / ICP)
输入 RGB / RGB-D / Stereo 图像 LiDAR 点云
核心信息 纹理 + 特征点 几何结构(距离)
依赖环境 光照敏感 光照无关
计算方式 特征匹配 / PnP / 光流 ICP / NDT / 点云配准
输出 相机位姿 传感器位姿
稳定性 中等(环境依赖大) 高(结构稳定环境下)
漂移 较明显 较小
适用场景 室内视觉丰富环境 室外/结构化空间

一个非常关键的误区


很多人以为:

“相机自带里程计”

❌ 错

正确是:

Gemini 335/336L
        ↓
ROS driver(orbbec_camera)
        ↓
RTAB-Map
   ├── VO(视觉)
   └── LO(ICP)

👉 VO / LO 都是 RTAB-Map 做的,不是相机