1. 概述

本文介绍如何在没有轮式里程计和IMU的情况下,仅依靠2D激光雷达实现机器人的自主导航和避障。整个方案基于ROS2 Humble,使用Cartographer进行纯激光SLAM建图,Nav2负责路径规划和避障。

用于底盘驱动板换过电机和轮子,也没有时间去重新调试编码器来确定里程计数据,使用该方法成功实现建图、导航和避障。

前提:底盘驱动安装完成,激光雷达驱动安装完成

1.1 适用场景

  • 底盘不提供里程计数据

  • 没有IMU或IMU数据质量差

  • 想要快速实现室内自主导航

  • 预算有限的机器人项目

1.2 本人硬件

硬件最低要求推荐配置
激光雷达2D,360°扫描思岚A1
计算平台香橙派5PRORK3588S-8G
ROS2版本HumbleHumble或以上

1.3 软件架构

激光雷达 → Cartographer(纯激光SLAM) → /map → Nav2导航栈
                    ↓
               提供/odom和TF
           TF树为:map→odom→base_link

2. 环境安装

2.1 安装ROS2 Humble

推荐鱼香ros的一键安装工具

wget http://fishros.com/install -O fishros && . fishros

2.2 安装Cartographer

# Cartographer核心包
sudo apt install ros-humble-cartographer ros-humble-cartographer-ros

2.3 安装Nav2导航栈

# 完整导航栈
sudo apt install ros-humble-navigation2 ros-humble-nav2-bringup

# 地图保存工具
sudo apt install ros-humble-nav2-map-server

2.4 安装激光雷达驱动

根据你的雷达型号选择(以思岚A1为例):

# 思岚雷达驱动
sudo apt install ros-humble-rplidar-ros

3. URDF模型配置

3.1 创建机器人描述文件

创建你的机器人URDF文件,关键点是定义正确的坐标系

<?xml version="1.0"?>
<robot name="my_robot">

  <!-- 机器人基座链接 -->
  <link name="base_link">
    <inertial>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
      <mass value="1.0"/>
      <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
    </inertial>
    <visual>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
      <geometry>
        <box size="0.4 0.3 0.2"/>
      </geometry>
      <material name="blue">
        <color rgba="0 0 1 1"/>
      </material>
    </visual>
  </link>

  <!-- 激光雷达链接 -->
  <link name="laser_link">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <cylinder radius="0.05" length="0.05"/>
      </geometry>
      <material name="red">
        <color rgba="1 0 0 1"/>
      </material>
    </visual>
  </link>

  <!-- 激光雷达关节 -->
  <joint name="laser_joint" type="fixed">
    <origin xyz="0 0 0.12" rpy="0 0 0"/>
    <parent link="base_link"/>
    <child link="laser_link"/>
  </joint>

  <!-- ========== 关键:为Nav2创建的坐标系 ========== -->
  <!-- 如果你的机器人是Y轴向前,需要创建base_nav来转换为X轴向前 -->
  <link name="base_nav"/>

  <joint name="base_nav_joint" type="fixed">
    <origin xyz="0 0 0" rpy="0 0 1.5708"/>  <!-- 旋转90度,使X轴向前 -->
    <parent link="base_link"/>
    <child link="base_nav"/>
  </joint>

</robot>
用于本人机器人是base_link的Y轴向前的,但是nav2的运动需要x轴向前,所以我在map和base_link之间添加了一个中间坐标,X轴向前,最终完整的TF树是:
map (全局地图坐标系)
  │
  └─── odom (里程计坐标系 - Cartographer发布)
          │
          └─── base_nav (导航坐标系 - X轴向前,供Nav2使用)
                  │
                  └─── base_link (底盘坐标系 - Y轴向前,供底盘驱动使用)
                          │
                          └─── laser_link (激光雷达坐标系)

3.2 坐标系说明

坐标系朝向用途
base_linkY轴向前底盘驱动使用
base_navX轴向前Nav2导航使用
laser_linkX轴向前激光雷达数据

4. Cartographer纯激光SLAM配置

4.1 创建配置目录

mkdir -p ~/cartographer_demo/config
cd ~/cartographer_demo

4.2 编写Lua配置文件

创建 ~/cartographer_demo/config/cartographer_2d.lua

include "map_builder.lua"
include "trajectory_builder.lua"

options = {
  map_builder = MAP_BUILDER,
  trajectory_builder = TRAJECTORY_BUILDER,
  map_frame = "map",
  tracking_frame = "base_link",        -- 追踪机器人基座
  published_frame = "base_link",       -- 发布的坐标系
  odom_frame = "odom",
  provide_odom_frame = true,           -- 必须启用,提供里程计
  use_odometry = false,                -- 不使用里程计
  use_nav_sat = false,
  use_landmarks = false,
  num_laser_scans = 1,
  num_multi_echo_laser_scans = 0,
  num_subdivisions_per_laser_scan = 1,
  num_point_clouds = 0,
  lookup_transform_timeout_sec = 0.2,
  submap_publish_period_sec = 0.3,
  pose_publish_period_sec = 5e-3,
  trajectory_publish_period_sec = 30e-3,
}

MAP_BUILDER.use_trajectory_builder_2d = true
MAP_BUILDER.num_background_threads = 4

-- 禁用IMU,纯激光定位
TRAJECTORY_BUILDER_2D.use_imu_data = false

-- 纯激光雷达参数优化
TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching = true
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.linear_search_window = 0.15
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.angular_search_window = 0.3
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.occupied_space_weight = 20.0
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.translation_weight = 15.0
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.rotation_weight = 30.0
TRAJECTORY_BUILDER_2D.motion_filter.max_angle_radians = math.rad(0.2)
TRAJECTORY_BUILDER_2D.motion_filter.max_distance_meters = 0.2
TRAJECTORY_BUILDER_2D.submaps.num_range_data = 45
TRAJECTORY_BUILDER_2D.min_range = 0.1
TRAJECTORY_BUILDER_2D.max_range = 12.0

POSE_GRAPH.constraint_builder.min_score = 0.65
POSE_GRAPH.constraint_builder.global_localization_min_score = 0.7
POSE_GRAPH.optimize_every_n_nodes = 45

return options

4.3 参数详解

参数默认值说明
use_imu_datafalse禁用IMU,仅使用激光雷达
use_online_correlative_scan_matchingtrue启用实时扫描匹配
linear_search_window0.15扫描匹配的线性搜索窗口
angular_search_window0.3扫描匹配的角度搜索窗口
rotation_weight30旋转匹配权重

5. Nav2导航配置

5.1 创建Nav2参数文件

创建 ~/cartographer_demo/config/nav2_params.yaml

也可以到安装目录下去复制一份过来修改,如果需要横向移动也在这个里面修改Y方向速度,不果不建议,车跑起来和漂移一样

/opt/ros/<你的ROS2版本>/share/nav2_bringup/params/nav2_params.yaml
但是默认是base_link,你的机器人base_link是X向前可以不用修改

# AMCL定位(用于导航时的定位)
amcl:
  ros__parameters:
    use_sim_time: false
    odom_frame_id: odom
    base_frame_id: base_footprint
    global_frame_id: map
    robot_model_type: "nav2_amcl::DifferentialMotionModel"
    min_particles: 500
    max_particles: 2000
    update_min_a: 0.2
    update_min_d: 0.25
    laser_model_type: "likelihood_field"
    laser_max_range: 12.0
    laser_min_range: 0.1
    max_beams: 60
    z_hit: 0.5
    z_short: 0.05
    z_max: 0.05
    z_rand: 0.5
    sigma_hit: 0.2
    scan_topic: scan

# 行为树导航器
bt_navigator:
  ros__parameters:
    use_sim_time: false
    global_frame: map
    robot_base_frame: base_nav        # 注意:使用base_nav(X轴向前)
    odom_topic: odom
    bt_loop_duration: 10
    default_server_timeout: 20

# 控制器服务器(路径跟踪)
controller_server:
  ros__parameters:
    use_sim_time: false
    controller_frequency: 20.0
    robot_base_frame: base_nav        # 使用base_nav
    controller_plugins: ["FollowPath"]
    
    FollowPath:
      plugin: "nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController"
      desired_linear_vel: 0.2        # 目标速度 0.2 m/s
      max_angular_vel: 0.8           # 最大角速度 0.8 rad/s
      max_linear_accel: 0.5          # 最大加速度 0.5 m/s²
      max_angular_accel: 0.5
      lookahead_dist: 0.5            # 前瞻距离
      allow_reversing: false         # 禁止倒车
      use_collision_detection: false # 禁用碰撞检测(避免误判)

# 全局规划器
planner_server:
  ros__parameters:
    use_sim_time: false
    planner_plugins: ["GridBased"]
    GridBased:
      plugin: "nav2_navfn_planner/NavfnPlanner"
      tolerance: 0.5
      use_astar: true

# 局部代价地图(实时避障)
local_costmap:
  local_costmap:
    ros__parameters:
      use_sim_time: false
      robot_base_frame: base_nav
      global_frame: odom
      rolling_window: true
      width: 3                      # 3米宽
      height: 3                     # 3米高
      resolution: 0.05              # 5cm分辨率
      footprint: "[[0.15, 0.125], [0.15, -0.125], [-0.15, -0.125], [-0.15, 0.125]]"
      plugins: ["voxel_layer", "inflation_layer"]
      voxel_layer:
        plugin: "nav2_costmap_2d::VoxelLayer"
        enabled: true
        observation_sources: scan
        scan:
          topic: /scan
          marking: true
          clearing: true
      inflation_layer:
        plugin: "nav2_costmap_2d::InflationLayer"
        enabled: true
        inflation_radius: 0.4       # 障碍物膨胀半径
        cost_scaling_factor: 3.0

# 全局代价地图
global_costmap:
  global_costmap:
    ros__parameters:
      use_sim_time: false
      robot_base_frame: base_nav
      global_frame: map
      width: 50
      height: 50
      resolution: 0.05
      footprint: "[[0.15, 0.125], [0.15, -0.125], [-0.15, -0.125], [-0.15, 0.125]]"
      plugins: ["static_layer", "inflation_layer"]
      static_layer:
        plugin: "nav2_costmap_2d::StaticLayer"
        map_subscribe_transient_local: true
      inflation_layer:
        plugin: "nav2_costmap_2d::InflationLayer"
        enabled: true
        inflation_radius: 0.4

# 行为服务器(恢复行为)
behavior_server:
  ros__parameters:
    use_sim_time: false
    robot_base_frame: base_nav
    behavior_plugins: ["spin", "backup", "wait"]
    spin:
      plugin: "nav2_behaviors::Spin"
    backup:
      plugin: "nav2_behaviors::BackUp"
    wait:
      plugin: "nav2_behaviors::Wait"

# 速度平滑器
velocity_smoother:
  ros__parameters:
    use_sim_time: false
    smoothing_frequency: 20.0
    max_velocity: [0.3, 0.0, 0.8]
    min_velocity: [-0.3, 0.0, -0.8]
    max_accel: [1.0, 0.0, 1.0]
    max_decel: [-1.0, 0.0, -1.0]

# 地图服务器
map_server:
  ros__parameters:
    use_sim_time: false
    yaml_filename: ""

5.2 Nav2参数详解

参数说明
robot_base_framebase_nav机器人基座坐标系(X轴向前)
desired_linear_vel0.2目标线速度 m/s
max_angular_vel0.8最大角速度 rad/s
inflation_radius0.4障碍物膨胀半径(米)
lookahead_dist0.5Pure Pursuit前瞻距离

6. 启动

6.1 分步启动

终端1 - 启动URDF发布TF树

ros2 launch urdf_tutorial display.launch.py model:=/home/user/Downloads/my_robot.urdf

一定要验证好TF树的完整,可以在RVIZ2中添加TF查看

终端2 - Cartographer建图

cd ~/cartographer_demo
ros2 run cartographer_ros cartographer_node \
    -configuration_directory ~/cartographer_demo/config \
    -configuration_basename cartographer_2d.lua \
    --ros-args --remap scan:=/scan

终端3 - 占用栅格地图

ros2 run cartographer_ros cartographer_occupancy_grid_node -resolution 0.05 -publish_period_sec 1.0

终端4 - Nav2导航

ros2 launch nav2_bringup navigation_launch.py \
    params_file:=~/cartographer_demo/config/nav2_params.yaml \
    use_sim_time:=false

终端5 - RViz2

rviz2

6.2效果

建图

自主导航

7. RViz2配置

7.1 添加必要显示

在RViz2中按以下步骤配置:

  1. 设置Fixed Framemap

  2. 添加Map显示

    • 点击 Add → By topic → /map → Map

  3. 添加LaserScan显示

    • 点击 Add → By topic → /scan → LaserScan

    • 设置 Color 为红色,Size 为 0.05

  4. 添加Path显示

    • 点击 Add → By topic → /plan → Path

    • 设置颜色为蓝色

  5. 添加RobotModel显示(可选):

    • 点击 Add → RobotModel

  6. 添加TF显示

    • 点击 Add → TF

7.2 保存配置

保存RViz2配置以便下次使用:

mkdir -p ~/cartographer_demo/config
# 在RViz2中:File → Save Config As → ~/cartographer_demo/config/navigation.rviz

以后可以这样启动:

rviz2 -d ~/cartographer_demo/config/navigation.rviz

8. 地图保存

建图完成后,保存地图:

# 保存当前地图
ros2 run nav2_map_server map_saver_cli -f ~/Desktop/my_map

# 查看生成的文件
ls ~/Desktop/my_map.*

生成的my_map.yaml内容示例:

image: my_map.pgm
mode: trinary
resolution: 0.05
origin: [-6.2, -5.0, 0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.25

9. 总结

本文介绍了在无里程计和无IMU情况下,仅依靠2D激光雷达实现自主导航的完整方案:

组件作用配置要点
CartographerSLAM建图禁用IMU,启用纯激光匹配
Nav2导航避障使用RegulatedPurePursuitController
坐标系定位基础map→odom→base_link链
TF变换方向修正base_nav→base_link旋转修正

关键成功因素

  1. 正确的坐标系定义(URDF)

  2. Cartographer配置文件中的provide_odom_frame = true

  3. Nav2使用base_nav作为robot_base_frame

  4. 合理的速度参数设置

通过本方案,你可以在不具备里程计的机器人上实现稳定、可靠的自主导航功能。


Logo

立足具身智能前沿赛道,致力于搭建全球化、开源化、全栈式技术交流与实践共创平台。

更多推荐