ROS2上无底盘里程计数据依靠激光雷达实现自主导航避障
1. 概述
本文介绍如何在没有轮式里程计和IMU的情况下,仅依靠2D激光雷达实现机器人的自主导航和避障。整个方案基于ROS2 Humble,使用Cartographer进行纯激光SLAM建图,Nav2负责路径规划和避障。
用于底盘驱动板换过电机和轮子,也没有时间去重新调试编码器来确定里程计数据,使用该方法成功实现建图、导航和避障。

前提:底盘驱动安装完成,激光雷达驱动安装完成
1.1 适用场景
-
底盘不提供里程计数据
-
没有IMU或IMU数据质量差
-
想要快速实现室内自主导航
-
预算有限的机器人项目
1.2 本人硬件
| 硬件 | 最低要求 | 推荐配置 |
|---|---|---|
| 激光雷达 | 2D,360°扫描 | 思岚A1 |
| 计算平台 | 香橙派5PRO | RK3588S-8G |
| ROS2版本 | Humble | Humble或以上 |
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_link | Y轴向前 | 底盘驱动使用 |
base_nav | X轴向前 | Nav2导航使用 |
laser_link | X轴向前 | 激光雷达数据 |
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_data | false | 禁用IMU,仅使用激光雷达 |
use_online_correlative_scan_matching | true | 启用实时扫描匹配 |
linear_search_window | 0.15 | 扫描匹配的线性搜索窗口 |
angular_search_window | 0.3 | 扫描匹配的角度搜索窗口 |
rotation_weight | 30 | 旋转匹配权重 |
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_frame | base_nav | 机器人基座坐标系(X轴向前) |
desired_linear_vel | 0.2 | 目标线速度 m/s |
max_angular_vel | 0.8 | 最大角速度 rad/s |
inflation_radius | 0.4 | 障碍物膨胀半径(米) |
lookahead_dist | 0.5 | Pure 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中按以下步骤配置:
-
设置Fixed Frame:
map -
添加Map显示:
-
点击
Add→By topic→/map→Map
-
-
添加LaserScan显示:
-
点击
Add→By topic→/scan→LaserScan -
设置
Color为红色,Size为 0.05
-
-
添加Path显示:
-
点击
Add→By topic→/plan→Path -
设置颜色为蓝色
-
-
添加RobotModel显示(可选):
-
点击
Add→RobotModel
-
-
添加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激光雷达实现自主导航的完整方案:
| 组件 | 作用 | 配置要点 |
|---|---|---|
| Cartographer | SLAM建图 | 禁用IMU,启用纯激光匹配 |
| Nav2 | 导航避障 | 使用RegulatedPurePursuitController |
| 坐标系 | 定位基础 | map→odom→base_link链 |
| TF变换 | 方向修正 | base_nav→base_link旋转修正 |
关键成功因素:
-
正确的坐标系定义(URDF)
-
Cartographer配置文件中的
provide_odom_frame = true -
Nav2使用
base_nav作为robot_base_frame -
合理的速度参数设置
通过本方案,你可以在不具备里程计的机器人上实现稳定、可靠的自主导航功能。
更多推荐
所有评论(0)