SLAM 建图原理速览与建图节点启动
1. 先说结论
SLAM 是机器人在没有现成地图时,一边估计自身位置,一边建立环境地图的过程。英文全称是 Simultaneous Localization and Mapping,即“同时定位与建图”。
对于移动机器人来说,SLAM 不是单独启动一个程序就能完成的功能。它至少需要同时获得以下几类数据:
- 激光雷达发布的二维扫描数据,通常是
sensor_msgs/msg/LaserScan类型的/scan话题。 - 底盘里程计或激光里程计,通常通过
/odom话题提供机器人的连续运动估计。 - 完整的 TF 坐标变换,至少要能把
odom、机器人底盘坐标系和雷达坐标系连接起来。 - 一个可以处理这些数据的 SLAM 算法节点,例如 ROS 2 中常用的
slam_toolbox。
因此,建图时应该按下面这条链路理解:
机器人底盘与传感器
-> /scan、/odom、TF
-> SLAM 建图节点
-> /map 与 map -> odom
-> RViz2 检查地图
-> map_saver_cli 保存地图
本文以 ROSLander多模态机器人(Orin Nano 版本) ROS2 工程为例,介绍二维激光 SLAM 的基本原理、建图节点的启动方式、启动前检查、实际建图流程以及常见故障排查。
适用范围说明:本文对应
ROSLander多模态机器人(Orin Nano 版本)的 ROS2 工程。该工程已经按slam、peripherals、controller和roslander_description等功能包组织底盘、IMU、里程计、激光雷达、机器人模型和 SLAM 启动链路。若使用的是其他 ROSLander 版本,或当前系统没有安装二维激光雷达,请以本机实际功能包、环境变量和话题名称为准。
2. 示例产品与软件环境
2.1 示例产品
| 项目 | 示例配置 |
|---|---|
| 产品 | ROSLander多模态机器人(Orin Nano 版本) |
| 建图任务 | 二维激光 SLAM 建立室内栅格地图 |
| 传感器 | 二维激光雷达、IMU |
| 底盘 | 麦克纳姆轮移动底盘 |
| 可视化工具 | RViz2 |
| 地图格式 | OccupancyGrid,保存为 .yaml + .pgm |
| 默认算法 | ROSLander 工程通过 slam.launch.py 启动同步 slam_toolbox |
2.2 软件环境
下面是一组常见的 ROS2 环境示例。实际使用时,以设备已经安装的 ROS2 发行版和功能包为准。
| 项目 | 示例 |
|---|---|
| 操作系统 | Ubuntu 22.04 |
| ROS2 | Humble |
| SLAM 软件包 | slam_toolbox |
| 地图保存工具 | nav2_map_server |
| 可视化工具 | rviz2 |
| 工作空间 | ~/ros2_ws |
| 建图参数文件 | ~/ros2_ws/src/slam/config/slam.yaml |
本文以 ROS 2 Humble 为例。如果你的系统使用其他发行版,请把下面命令中的 humble 替换成实际发行版名称。
如果系统中还没有相关软件包,可以先检查环境变量:
echo $ROS_DISTRO
在确认 ROS2 已正确安装后,再根据发行版安装依赖:
sudo apt update
sudo apt install ros-humble-slam-toolbox \
ros-humble-navigation2 \
ros-humble-nav2-bringup \
ros-humble-rviz2
安装完成后检查软件包是否可以被 ROS2 找到:
ros2 pkg prefix slam_toolbox
ros2 pkg prefix nav2_map_server
ros2 pkg prefix rviz2
如果命令返回了安装目录,说明对应软件包已经进入当前 ROS2 环境。若提示 Package not found,先不要启动建图节点,应先处理安装或环境加载问题。
3. SLAM 建图到底在解决什么问题
机器人要在陌生环境中移动,必须同时回答两个问题:
- 机器人此刻位于什么位置,朝向是什么方向。
- 周围哪些位置是空闲区域,哪些位置被墙体或障碍物占据。
第一个问题是定位,第二个问题是建图。SLAM 的难点在于,机器人一开始既没有完整地图,也没有完全准确的自身位置,只能利用传感器观测和运动估计不断互相校正。
可以把 SLAM 的工作过程概括为三个阶段。
3.1 传感器数据预处理
激光雷达每一帧都会输出一组距离值。距离值通常按照角度顺序排列,每个数据点表示雷达在某个方向上测得的距离。SLAM 节点首先要检查这些数据是否满足使用条件,例如:
- 距离值是否在雷达的有效量程内。
- 是否存在大量
NaN、无穷大或超量程数据。 - 扫描时间戳是否连续。
- 雷达坐标系是否已经通过 TF 连接到底盘。
- 雷达安装方向是否与实际坐标轴约定一致。
如果输入数据本身不稳定,后面的算法再复杂也无法得到可靠地图。建图过程中地图突然拉伸、墙体变成重影或走廊逐渐弯曲,很多时候首先要检查传感器数据和 TF,而不是立即修改 SLAM 参数。
3.2 当前扫描与已有信息匹配
机器人移动后,当前帧扫描到的墙体和障碍物会与上一帧有所重叠。SLAM 算法会尝试把当前扫描与已有扫描或当前地图对齐,估计机器人在这段时间内发生了多少平移和旋转。
这一步可以理解为“扫描匹配”。里程计提供运动变化的初始猜测,激光扫描提供环境几何约束。两者配合时,算法既能知道机器人大概走了多远,也能利用墙体等稳定结构纠正轮子打滑带来的误差。
3.3 地图融合与回环优化
得到当前位姿后,SLAM 节点把这一次扫描投影到地图坐标系中,更新占据栅格。机器人沿着环境继续移动时,地图不断扩展。
当机器人重新回到之前经过的位置,算法可以识别出“当前位置和历史位置是同一个地方”,这就是回环约束。回环检测成功后,SLAM 后端会优化一组历史位姿,让之前累计的误差得到整体调整。大环境建图时,回环质量通常直接影响最终地图是否闭合。
slam_toolbox 的建图过程可以概括为:订阅激光和里程计,使用扫描匹配维护位姿图,在发现回环时优化位姿图,再根据优化后的位姿发布地图和 map -> odom 变换。
4. 建图节点在 ROS2 中扮演什么角色
4.1 ROSLander 工程中的 slam_toolbox 节点
ROSLander 工程不是直接启动 slam_toolbox 官方的 online_async_launch.py,而是使用工程自己的总入口:
ros2 launch slam slam.launch.py
这个总入口会先引入 slam/launch/include/robot.launch.py,启动底盘、IMU、里程计、雷达和相关外围节点;等待约 5 秒后,再引入 slam/launch/include/slam_base.launch.py。slam_base.launch.py 读取 slam/config/slam.yaml,最终启动的核心可执行程序是:
sync_slam_toolbox_node
节点名为 slam_toolbox。因此,本文后面的命令都以 ROSLander 工程的 slam.launch.py 为准,不再把 online_async_launch.py 当作 ROSLander 的默认入口。
ROSLander 的 SLAM 节点通常完成以下工作:
- 订阅雷达过滤后的
LaserScan数据。 - 查询
odom、base_footprint和lidar_frame之间的 TF。 - 使用
/odom作为运动估计,并结合激光扫描进行匹配。 - 维护位姿图并进行回环检测。
- 发布二维地图
/map或当前命名空间下的地图话题。 - 发布地图坐标系到里程计坐标系的 TF。
4.2 ROSLander 底盘、IMU 与里程计节点
底盘驱动节点不是 SLAM 节点,但它是建图能否运行的前提。ROSLander 的 controller.launch.py 会组织多条底盘数据链路,主要包括:
odom_publisher.launch.py:根据底盘运动和 IMU 数据生成里程计相关信息。imu_filter.launch.py:处理 IMU 数据。robot_localization/ekf_node:融合里程计和 IMU,并把odometry/filtered重映射为/odom。- 舵机控制、运动学和底盘控制节点:接收速度指令并驱动机器人运动。
因此,ROSLander 建图前不能只确认“底盘能动”,还要确认 /odom 正在更新,并且 odom -> base_footprint 的 TF 连续、方向正确。里程计依赖 IMU,若要获得更好的建图效果,应先完成 IMU、线速度和角速度校准。
4.3 ROSLander 雷达与过滤节点
ROSLander 通过 peripherals/launch/lidar.launch.py 统一启动雷达。该 launch 根据环境变量 LIDAR_TYPE 选择雷达驱动,支持 LD19、A1、G4 和 S2L 等类型,然后通过 laser_filters 对原始扫描进行过滤。
其数据链路可以概括为:
雷达驱动 -> scan_raw -> laser_filters -> scan -> slam_toolbox
其中常见消息类型是:
sensor_msgs/msg/LaserScan
lidar.launch.py 暴露了 lidar_frame、scan_raw 和 scan_topic 参数。ROSLander 默认使用过滤后的 scan 作为 SLAM 输入;如果 robot_name 不为空,实际话题可能带机器人命名空间,例如 /<robot_name>/scan。不要只凭经验写死 /scan,应先用 ros2 topic list 查看当前系统真正发布的名称。
4.4 TF 发布节点
ROSLander 单机器人建图至少需要建立下面的坐标关系:
map -> odom -> base_footprint -> lidar_frame
其中:
| 坐标系 | 作用 | 常见发布者 |
|---|---|---|
map |
全局地图坐标系 | SLAM 节点 |
odom |
连续但会漂移的里程计坐标系 | 底盘驱动或里程计节点 |
base_link / base_footprint |
机器人底盘坐标系 | 底盘控制和机器人模型 |
lidar_frame |
激光雷达坐标系 | 雷达 launch、URDF 或静态 TF |
建图时通常由 SLAM 节点发布 map -> odom,底盘和 EKF 链路提供 odom -> base_footprint,雷达与机器人模型提供 base_footprint -> lidar_frame。ROSLander 的 slam.launch.py 会根据 robot_name 生成 map、odom 和 base_footprint 的前缀;若设置了 robot_name,实际 TF 可能显示为 <robot_name>/map、<robot_name>/odom 和 <robot_name>/base_footprint。坐标系名称必须以 tf2_echo 和 RViz 的实际结果为准。
5. 建图前必须确认的四个条件
5.1 激光雷达有稳定输出
先单独启动雷达驱动,再检查话题:
ros2 topic list | grep scan
查看 /scan 的消息类型:
ros2 topic type /scan
正常情况下应看到:
sensor_msgs/msg/LaserScan
检查扫描频率:
ros2 topic hz /scan
短时间查看一帧内容:
ros2 topic echo /scan --once
重点观察 header.frame_id、angle_min、angle_max、range_min、range_max 和 ranges。如果 header.frame_id 为空,或者所有 ranges 都是无效值,SLAM 节点无法完成有效匹配。
5.2 里程计正在更新
检查 /odom 是否存在:
ros2 topic list | grep odom
ros2 topic type /odom
ros2 topic hz /odom
让小车缓慢向前移动,观察里程计消息中的位置是否变化:
ros2 topic echo /odom --once
如果每次读到的消息时间戳在变化,但位置始终为零,说明底盘驱动可能只发布了空消息,或编码器数据没有接入。若前进时 x 方向变化、旋转时角度变化完全相反,也要在启动建图前修正底盘坐标约定。
5.3 TF 链路完整
查看 TF 结构:
ros2 run tf2_tools view_frames
也可以直接检查雷达和底盘之间的变换:
ros2 run tf2_ros tf2_echo base_footprint lidar_frame
如果当前 ROSLander 工程启用了机器人命名空间,实际坐标系可能带有 robot_name 前缀,应以 ros2 run tf2_tools view_frames 生成的 TF 图为准。命令能够持续输出变换,说明这两个坐标系之间至少存在可查询的 TF。
注意:在 SLAM 启动前,不要要求系统已经存在 map -> odom。这段变换通常正是由 SLAM 节点在建图过程中发布的。启动前需要确认的是 odom -> base_footprint 和 base_footprint -> lidar_frame,启动后再检查完整链路。
5.4 机器人时间源一致
真实机器人使用系统时间,仿真机器人使用 /clock。ROSLander 真实硬件启动时,slam.launch.py 的 sim 参数应保持为 false;源码会据此把 use_sim_time 传给底盘和 SLAM 节点。如果误设为 true,而系统没有 /clock,节点可能看似已经启动,但消息时间戳无法正常推进。
检查是否存在仿真时钟:
ros2 topic list | grep clock
真实硬件没有 /clock 并不代表系统有问题,关键是所有相关节点都要使用同一种时间模式。
6. 参数文件怎么读
使用 slam_toolbox 时,ROSLander 将参数集中放在 ~/ros2_ws/src/slam/config/slam.yaml。slam_base.launch.py 会根据 launch 参数重写时间源和坐标系。下面只展示阅读参数文件时需要关注的结构,不替代 ROSLander 工程中的完整 YAML:
slam_toolbox:
ros__parameters:
use_sim_time: false
mode: mapping
map_frame: map
odom_frame: odom
base_frame: base_footprint
scan_topic: scan
resolution: 0.05
transform_publish_period: 0.02
map_update_interval: 5.0
minimum_time_interval: 0.5
use_scan_matching: true
use_scan_barycenter: true
do_loop_closing: true
minimum_travel_distance: 0.5
minimum_travel_heading: 0.5
这份示例只列出理解启动链路所需的关键项,不能替代 ROSLander 工程中的完整 slam.yaml。如果 HOST 或 robot_name 非空,实际话题和 TF 可能带命名空间;不要仅凭这段示意 YAML 判断最终名称。
6.1 map_frame
地图全局坐标系,通常写成 map。它应与 RViz 的 Fixed Frame 以及导航系统的全局坐标系保持一致。
6.2 odom_frame
里程计坐标系,通常写成 odom。这个坐标系应该连续、平滑,但允许随着时间产生漂移。SLAM 节点通过发布 map -> odom 对漂移进行全局修正。
6.3 base_frame
机器人底盘坐标系。常见值是 base_link 或 base_footprint,必须以实际 TF 树为准。参数名填写正确但坐标系不存在时,日志里通常会出现 TF lookup 失败。
6.4 scan_topic
激光扫描话题名称。不要只根据雷达驱动的产品名称猜测话题名,应通过 ros2 topic list 和 ros2 topic type 实际确认。
6.5 use_sim_time
真实 ROSLander 设置 sim:=false,仿真环境才设置 sim:=true。ROSLander 的 slam.launch.py 默认 sim 为 false,并会把对应的 use_sim_time 传给 robot.launch.py 和 slam_base.launch.py。
7. 建图节点的标准启动顺序
启动顺序的核心原则是:先让输入数据可用,再启动依赖这些数据的 SLAM 节点,最后启动可视化和保存工具。
7.1 第一步:打开终端并加载 ROS2 环境
每个终端都要加载 ROS2 环境。若使用自建工作空间,还要加载工作空间的 install 目录。
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
如果工作空间还没有编译过,先进入工作空间编译:
cd ~/ros2_ws
colcon build --symlink-install
source install/setup.bash
7.2 第二步:启动底盘、雷达和机器人模型
ROSLander 建图时应先关闭 APP 自启服务,避免自启程序重复占用底盘、雷达或相关话题:
sudo systemctl stop start_app_node.service
然后启动 ROSLander 的 SLAM 总入口:
ros2 launch slam slam.launch.py
这个 launch 文件会先引入 slam/launch/include/robot.launch.py,启动底盘控制、IMU、里程计、雷达和机器人模型等外围节点;等待约 5 秒后,再引入 slam/launch/include/slam_base.launch.py,启动同步 slam_toolbox 节点。键盘控制通过终端单独启动,正常使用时不需要另外手动启动 controller 和 peripherals lidar,否则容易重复启动节点。
ROSLander 的总 launch 支持以下常用参数:
ros2 launch slam slam.launch.py \
sim:=false \
slam_method:=slam_toolbox \
enable_save:=true
其中 sim:=false 表示真实机器人,slam_method:=slam_toolbox 表示使用默认的二维建图算法,enable_save:=true 保留地图保存服务。
启动后再确认输入数据:
ros2 node list
ros2 topic list
ros2 topic hz /scan
ros2 topic hz /odom
如果需要单独调试雷达或底盘,才使用对应的子 launch。例如雷达调试入口是:
ros2 launch peripherals lidar.launch.py
底盘调试入口是:
ros2 launch controller controller.launch.py
单独启动这些子 launch 前,应先结束正在运行的 slam.launch.py,避免同一节点被启动两次。
7.3 第三步:确认 launch 文件参数
查看 ROSLander 工程总 launch 暴露的参数:
ros2 launch slam slam.launch.py --show-args
重点确认是否包含 sim、slam_method、enable_save、master_name 和 robot_name。其中 slam_method 默认是 slam_toolbox,sim 默认是 false。
7.4 第四步:确认同步建图节点已经启动
执行 ros2 launch slam slam.launch.py 后,slam_base.launch.py 会读取下面的参数文件:
~/ros2_ws/src/slam/config/slam.yaml
源码中真正启动的节点为:
package: slam_toolbox
executable: sync_slam_toolbox_node
name: slam_toolbox
ROSLander 的默认建图方式是同步模式。它会按扫描顺序处理有效传感器测量,适合稳定的建图流程。启动成功后,终端中应能看到 /slam_toolbox 或当前机器人命名空间下的 slam_toolbox 节点。
ROSLander 工程也保留了直接启动官方 slam_toolbox launch 的可能性,但这不是工程的默认操作。除非你明确知道参数文件、话题 remap 和坐标系已经与工程一致,否则不要用下面这种通用命令替代 ros2 launch slam slam.launch.py:
ros2 launch slam_toolbox online_sync_launch.py
这条命令只用于理解官方工具接口,不能作为 ROSLander 的首选启动命令。
7.5 第五步:启动 RViz2
可以直接启动 RViz2:
rviz2
ROSLander 工程提供了对应的 RViz 启动文件:
ros2 launch slam rviz_slam.launch.py
在 RViz2 中至少检查以下内容:
| RViz 项目 | 建议设置 |
|---|---|
| Fixed Frame | map 或当前机器人命名空间下的地图坐标系 |
| Map | 当前 SLAM 节点发布的 map 话题 |
| LaserScan | 过滤后的 scan 话题 |
| TF | 开启显示 |
| RobotModel | 如果工程提供 URDF,则开启 |
| Grid | 可选,用于观察坐标轴方向 |
如果 Fixed Frame 设置成 base_link,有时仍能看到激光,但无法直观看到地图坐标系是否正确。建图阶段通常使用 map 作为 Fixed Frame 更容易判断全局地图是否在更新。
8. 第一次启动后如何确认节点真的工作
节点启动没有报错,不代表建图链路已经完整。建议按下面顺序检查。
8.1 检查节点列表
ros2 node list
至少应能看到底盘驱动、雷达驱动和 SLAM 节点。SLAM 节点名称可能是 /slam_toolbox,也可能因为命名空间而显示成其他形式。
8.2 检查地图话题
ros2 topic list | grep -E 'map|scan|odom|tf'
查看地图消息类型:
ros2 topic type /map
正常情况下应为:
nav_msgs/msg/OccupancyGrid
查看一帧地图消息:
ros2 topic echo /map --once
如果 /map 不存在,优先检查 SLAM 节点是否启动、参数文件是否加载成功,以及节点是否处于 active 状态。
8.3 检查地图是否随机器人运动更新
让 ROSLander 先原地小角度旋转,再沿直线缓慢移动。建议使用键盘控制节点,并适当降低机器人移动速度。观察 RViz2 中:
- 激光点是否随机器人运动。
- 地图边缘是否逐渐扩展。
- 已知墙体是否保持清晰,而不是不断产生重影。
- 机器人底盘模型或 TF 是否跟随运动。
刚启动时地图只有局部区域是正常的。真正需要关注的是机器人移动后地图是否继续更新,以及回到起点附近时是否能形成闭环。
8.4 检查 TF
ros2 run tf2_ros tf2_echo map odom
ros2 run tf2_ros tf2_echo odom base_footprint
ros2 run tf2_ros tf2_echo base_footprint lidar_frame
如果 robot_name 非空,按实际命名空间补全坐标系名称。map -> odom 只有在 SLAM 节点运行后才应出现;另外两段通常应在 SLAM 启动前就已经存在。
9. 正确的建图操作方法
9.1 建图前整理环境
建图的目标不是把当时看到的所有物体都画进地图,而是建立一张后续可以重复定位和导航的静态环境地图。开始前建议:
- 给 ROSLander 充足电量,避免建图中途电压下降导致底盘运动异常。
- 将容易移动的箱子、椅子和人员尽量移开。
- 避免让人员长时间站在雷达扫描范围内。
- 确认雷达安装牢固,不能在车体运动时晃动。
- 确认小车周围有足够空间,不要刚启动就贴着墙体或障碍物。
9.2 选择合适的起点
起点最好面对一面直墙或结构稳定的环境,让雷达在第一批扫描中获得足够多的几何特征。不要把雷达正对大面积玻璃、薄布帘或频繁移动的物体作为主要参考。
9.3 先闭环,再细扫
在较大的场地中,建议先沿主要通道移动一圈,让机器人回到起点附近,优先建立完整的整体轮廓。确认闭环后,再补充角落、房间入口和细节区域。
如果一开始就只在一个小区域反复转圈,局部地图可能很清晰,但全局坐标关系没有建立;如果只沿直线走很远而没有回到已知区域,累计误差可能越来越明显。
9.4 控制运动速度
建图阶段以低速、匀速为主。建议遵守下面几条原则:
- 直线移动不要突然加速或急停。
- 转弯时降低线速度,避免雷达扫描期间底盘姿态变化过快。
- 不要连续做高速原地旋转。
- 麦克纳姆底盘进行横移时,尽量观察地图是否出现侧向拉伸。
- 如果地图明显跟不上运动,先降低速度和扫描负载,再调参数。
9.5 检查地图质量
建图完成后,至少检查以下内容:
| 检查项 | 合格表现 |
|---|---|
| 墙体边缘 | 轮廓清晰,重影较少 |
| 闭环 | 回到起点后整体地图没有明显错位 |
| 通行区域 | 后续需要行走的区域没有大块未知灰区 |
| 动态物体 | 没有把人员、移动箱子等长期障碍固化到地图 |
| 覆盖范围 | 机器人可能到达的位置已被雷达充分扫描 |
| 比例尺度 | 地图中的通道宽度与实际环境大致一致 |
如果地图边缘模糊、墙体重叠或整个场景发生弯曲,不建议直接保存作为导航地图。先找出数据、TF、速度或回环问题,再重新建图。
10. 保存地图
当地图已经闭环并且机器人停稳后,再执行保存。建议将地图统一保存在工作空间的地图目录中:
mkdir -p ~/ros2_ws/src/slam/maps
cd ~/ros2_ws/src/slam/maps
使用 Nav2 的地图保存工具:
ros2 run nav2_map_server map_saver_cli \
-f $HOME/ros2_ws/src/slam/maps/map_01 \
--ros-args -p map_subscribe_transient_local:=true
-f 后面填写的是文件前缀,不要手动添加 .yaml 或 .pgm。正常完成后,目录中应出现:
map_01.yaml
map_01.pgm
保存后立即检查:
ls -lh ~/ros2_ws/src/slam/maps/map_01.*
也可以查看 YAML 文件内容:
sed -n '1,40p' ~/ros2_ws/src/slam/maps/map_01.yaml
重点确认以下字段:
image: map_01.pgm
resolution: 0.05
origin: [-1.0, -1.0, 0.0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.25
实际 origin 和 resolution 以保存工具生成的内容为准,不要为了让地图“看起来居中”而随意改动。YAML 中的 image 路径必须能找到对应的图像文件;移动地图时,最好把 .yaml 和 .pgm 一起移动。
ROSLander 工程还提供了 map_save.py 节点。启用 slam.launch.py 的 enable_save:=true 后,可以检查保存服务:
ros2 service list | grep slam_toolbox/save_map
ros2 service type /slam_toolbox/save_map
该节点内部调用的保存命令仍然是 map_saver_cli -f map_01,所以最终文件名固定为 map_01.yaml 和 map_01.pgm。如果需要保存不同场地,先复制或重命名上一份地图,再按工程约定修改 map_save.py 中的文件前缀。
11. 一个完整的终端操作示例
下面给出 ROSLander 的一套完整命令顺序。每个终端都要加载 ROS2 和工作空间环境。
终端 1:停止 APP 自启并启动 SLAM 总入口
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
sudo systemctl stop start_app_node.service
ros2 launch slam slam.launch.py \
sim:=false \
slam_method:=slam_toolbox \
enable_save:=true
终端 2:确认输入数据
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 topic type /scan
ros2 topic type /odom
ros2 topic hz /scan
ros2 run tf2_ros tf2_echo base_footprint lidar_frame
终端 3:启动 RViz2
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 launch slam rviz_slam.launch.py
终端 4:启动键盘控制
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 launch peripherals teleop_key_control.launch.py
使用键盘控制 ROSLander 低速移动,完成闭环并确认 RViz2 中地图质量合格后,再在终端 5 保存地图。
终端 5:保存地图
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 run nav2_map_server map_saver_cli \
-f $HOME/ros2_ws/src/slam/maps/map_01 \
--ros-args -p map_subscribe_transient_local:=true
不要在建图还没有闭环时保存地图,也不要在机器人快速移动时执行保存。保存工具只负责把当前 /map 写入文件,无法替你判断地图质量。
12. ROSLander 的 launch 文件结构
ROSLander 已经提供了完整的 slam 功能包,不需要另外创建 mapping.launch.py。工程的启动关系可以简化为:
slam.launch.py
├── include/robot.launch.py
│ ├── controller.launch.py
│ ├── peripherals/lidar.launch.py
│ ├── 机器人模型与 TF
│ └── IMU、里程计和 EKF
└── 等待约 5 秒
└── include/slam_base.launch.py
├── 读取 config/slam.yaml
├── sync_slam_toolbox_node
└── enable_save 时启动 map_save.py
这种组织方式的关键点是:robot.launch.py 先把传感器输入和 TF 准备好,slam_base.launch.py 再启动算法节点。总入口已经处理了启动参数、命名空间和启动延迟,日常使用应优先调用:
ros2 launch slam slam.launch.py
12.1 如何查看 ROSLander 的 launch 参数
ros2 launch slam slam.launch.py --show-args
重点查看 sim、slam_method、enable_save、master_name 和 robot_name。如果修改了源码或参数文件,重新编译 slam 功能包并重新加载工作空间:
cd ~/ros2_ws
colcon build --packages-select slam --symlink-install
source install/setup.bash
ros2 pkg prefix slam
12.2 为什么调试时仍要分层检查
虽然日常可以使用一个总 launch,但排查问题时仍然应按数据链路分层检查:
- 雷达没有
/scan,是雷达驱动或设备连接问题。 /scan正常但 TF 缺失,是 URDF 或静态 TF 问题。/scan、/odom和 TF 都正常但/map不更新,是 SLAM 参数、时间戳或数据匹配问题。- 地图正常但保存失败,是地图保存工具或 QoS 配置问题。
ROSLander 的工程 launch 已经把这些层次组合起来。单独启动子 launch 只用于定位问题,确认后应回到 slam.launch.py 作为正式入口。
13. ROSLander 的算法入口边界
本文采用 ROSLander 工程中的 slam_toolbox 建图方案,默认通过 slam.launch.py 启动 sync_slam_toolbox_node。不要把通用 ROS2 教程中的异步 launch、GMapping launch 或其他小车工程的参数文件直接复制到 ROSLander 中。
如果使用的是另一份 ROSLander 源码或自行移植的工程,先检查以下三项,再决定是否更换算法入口:
ros2 pkg list | grep -E 'slam|gmapping|rtabmap'
ros2 launch slam slam.launch.py --show-args
ros2 topic list | grep -E 'scan|odom|map'
无论使用哪一种二维 SLAM 算法,都不能同时启动两个会发布 /map 或 map -> odom 的节点。ROSLander 默认链路已经能满足本篇建图任务,不需要额外启动 GMapping。
14. 常见问题与排查顺序
14.1 找不到 slam_toolbox 包
现象:
Package 'slam_toolbox' not found
排查:
echo $ROS_DISTRO
ros2 pkg list | grep slam
ros2 pkg prefix slam_toolbox
如果没有输出,安装对应发行版的软件包,并重新加载 ROS2 环境。不要只在一个终端安装或加载环境后,直接认为其他终端也已经生效。
14.2 节点启动了,但 /map 不存在
按下面顺序检查:
ros2 node list中是否有/slam_toolbox。- SLAM 终端是否有参数文件解析错误。
/scan是否存在且消息类型正确。scan_topic是否与实际话题相同。base_frame、odom_frame和map_frame是否拼写一致。- 雷达坐标系到机器人底盘的 TF 是否可查询。
use_sim_time是否与真实运行环境匹配。
14.3 /scan 存在,但地图不更新
常见原因包括:
- 机器人实际上没有移动。
/scan的时间戳不更新或时间基准不一致。- 雷达数据全部无效或有效范围过小。
- SLAM 节点无法查询
odom -> base_footprint或base_footprint -> lidar_frame。 scan_topic填写成了错误的话题。- 机器人运动速度过快,扫描匹配跟不上。
- 底盘里程计方向与实际运动方向相反。
- 两个 SLAM 节点同时运行,输出相互覆盖。
建议先降低小车速度,手动查看一帧 /scan,再检查 TF,而不是直接提高地图更新频率。
14.4 地图出现重影
地图重影通常说明同一面墙在不同时间被投影到了不同位置。重点排查:
- 雷达支架是否松动。
base_footprint -> lidar_frame的静态变换是否填写正确。/odom是否存在明显跳变。- 底盘是否在原地打滑。
- 雷达扫描频率是否与运动速度匹配。
- 环境中是否有大量移动人员或移动物体。
- 是否在没有回环的情况下走了过长距离。
如果重影只在急转弯时出现,先降低角速度并重新建图;如果整个地图都沿固定方向偏移,优先检查雷达外参和坐标轴方向。
14.5 地图整体弯曲或回不到起点
这通常与累计里程计误差、扫描匹配失败或回环未建立有关。可以尝试:
- 让机器人沿包含稳定墙体的路线移动。
- 避免大面积空旷区域和重复纹理区域。
- 先完成一圈闭环,再补充局部细节。
- 减小运动速度和原地旋转速度。
- 检查雷达量程和数据有效率。
- 保证
do_loop_closing没有被关闭。
不要把“地图范围变大”当作“建图成功”。地图范围扩大但结构逐渐弯曲,说明算法在继续接收数据,却没有得到足够稳定的几何约束。
14.6 RViz 中看不到激光
先检查 LaserScan 的 Topic 是否选择正确,再检查 Fixed Frame 是否可以通过 TF 连接到雷达坐标系。常见问题是:
- RViz 订阅了
/scan,实际数据在/scan_raw。 header.frame_id写成了不存在的坐标系。- 雷达消息的
header.frame_id不是lidar_frame,或 TF 中没有对应的雷达坐标系。 - Fixed Frame 设置成了
map,但 SLAM 尚未发布map -> odom。
为了区分 RViz 问题和数据问题,可以先执行:
ros2 topic echo /scan --once
如果命令能读到有效数据,问题更可能在 RViz 话题选择或 TF 显示配置。
14.7 保存地图超时
先确认 /map 正在发布:
ros2 topic type /map
ros2 topic echo /map --once
然后确认使用的是 Nav2 的 map_saver_cli:
ros2 run nav2_map_server map_saver_cli -h
如果地图由瞬态本地 QoS 发布,保存工具需要以兼容的订阅方式接收最后一张地图,可以尝试:
ros2 run nav2_map_server map_saver_cli \
-f $HOME/ros2_ws/src/slam/maps/map_01 \
--ros-args -p map_subscribe_transient_local:=true
不同 ROS2 发行版的 map_saver_cli 参数可能略有差异,若本机帮助信息不包含该参数,应以 -h 输出和当前 Nav2 版本的配置为准。
14.8 地图保存成功,但重新加载失败
重点检查:
.yaml和.pgm是否同时存在。- YAML 中
image路径是否仍然有效。 - 文件名大小写是否完全一致。
- 地图目录是否有读取权限。
- 是否在复制地图时只复制了 YAML,漏掉了 PGM。
可以从地图目录直接查看文件:
cd ~/ros2_ws/src/slam/maps
ls -lh map_01.yaml map_01.pgm
15. 一组快速体检命令
建图前可以把下面的命令依次执行一遍:
ros2 node list
ros2 topic list | grep -E 'scan|odom|map|tf'
ros2 topic type /scan
ros2 topic type /odom
ros2 topic hz /scan
ros2 topic hz /odom
ros2 topic echo /scan --once
ros2 topic echo /odom --once
ros2 run tf2_ros tf2_echo odom base_footprint
ros2 run tf2_ros tf2_echo base_footprint lidar_frame
如果启用了 robot_name 命名空间,替换最后两条命令中的参数。下面这份检查表也可以直接放在 ROSLander 实验记录中:
| 项目 | 检查结果 |
|---|---|
| ROS2 环境已加载 | 通过 / 未通过 |
slam_toolbox 可找到 |
通过 / 未通过 |
/scan 类型正确 |
通过 / 未通过 |
/scan 频率稳定 |
通过 / 未通过 |
/odom 正在更新 |
通过 / 未通过 |
odom -> base_footprint 存在 |
通过 / 未通过 |
base_footprint -> lidar_frame 存在 |
通过 / 未通过 |
/map 正常发布 |
通过 / 未通过 |
RViz Fixed Frame 为 map |
通过 / 未通过 |
| 地图闭环且无明显重影 | 通过 / 未通过 |
.yaml 与 .pgm 成对保存 |
通过 / 未通过 |
16. 参数调整建议
16.1 先调运行条件,再调算法参数
地图质量问题经常被误判为算法参数问题。推荐的调整顺序是:
- 确认雷达数据有效。
- 确认雷达安装不晃动。
- 确认 TF 外参正确。
- 确认里程计方向和尺度正确。
- 降低机器人运动速度。
- 再根据日志和地图表现调整 SLAM 参数。
如果输入数据和坐标变换没有问题,才有必要进一步调整扫描匹配距离、角度阈值、回环搜索范围或地图更新周期。
16.2 resolution
分辨率表示一个栅格像素对应的实际距离,常见值是 0.05 米。分辨率越小,地图细节越多,同时需要更多内存和计算资源。对于室内小车,不要为了追求“更清晰”就盲目设置极小分辨率。
16.3 minimum_travel_distance
该参数控制机器人移动到一定距离后才处理新的建图节点。设置过大,地图更新会显得不及时;设置过小,可能增加计算负载并把底盘微小抖动带入地图。
16.4 minimum_travel_heading
该参数控制机器人旋转到一定角度后才加入新的姿态节点。ROSLander 原地旋转时如果角度变化很快,应配合实际雷达频率和底盘稳定性调整,不要只依据单次实验结果修改。
16.5 do_loop_closing
大多数室内建图都需要回环检测。关闭回环后,机器人长距离运动时的累计误差更容易保留在最终地图中。只有在明确知道工程需要特殊模式时,才修改该参数。
17. 建图与导航不要同时误启动
建图和使用既有地图导航是两个不同模式。
建图阶段:
SLAM 节点发布 /map
SLAM 节点发布 map -> odom
机器人移动,地图持续更新
加载已有地图导航阶段:
map_server 发布静态 /map
AMCL 发布 map -> odom
导航栈根据地图规划路径
如果建图节点和导航定位节点同时运行,可能会有两个节点同时发布 /map 或 map -> odom。这会导致地图跳动、机器人位姿跳变或 RViz 中出现两套结果。
切换模式前,先确认旧节点已经退出:
ros2 node list
也可以在终端中使用 Ctrl+C 结束旧的 launch,再启动新的模式。不要只关闭 RViz,以为后端节点也已经退出。
18. 建图完成后的文件管理
建议为不同场地和不同版本使用可识别的名称:
~/ros2_ws/src/slam/maps/
├── map_01.yaml
├── map_01.pgm
├── roslander_lab_clean.yaml
├── roslander_lab_clean.pgm
├── roslander_office_01.yaml
└── roslander_office_01.pgm
不要只保留一个名为 new_map 或 final_map 的文件。地图通常会反复重建和优化,名称中包含场景和版本,后续排查时更容易确认导航使用的是哪一份地图。
如果地图需要复制到另一台机器,至少同时复制 YAML 和图像文件,并检查 YAML 中的 image 路径。更稳妥的做法是让 YAML 使用与地图文件同目录的相对图像路径。
19. 最短操作版
只保留核心操作时,可以按下面的顺序执行:
加载 ROS2 和工作空间环境
-> 停止 start_app_node.service
-> 启动 ROSLander 的 slam.launch.py
-> 检查 /scan、/odom 与 TF
-> 启动 rviz_slam.launch.py
-> 启动 teleop_key_control.launch.py
-> RViz2 将 Fixed Frame 设为 map
-> 缓慢移动机器人并完成回环
-> 检查地图质量
-> map_saver_cli 保存 map_01.yaml + map_01.pgm
对应的最小命令组合如下:
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 topic type /scan
ros2 topic type /odom
sudo systemctl stop start_app_node.service
ros2 launch slam slam.launch.py \
sim:=false \
slam_method:=slam_toolbox \
enable_save:=true
另开终端启动可视化和键盘控制:
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 launch slam rviz_slam.launch.py
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 launch peripherals teleop_key_control.launch.py
建图结束后保存地图:
ros2 run nav2_map_server map_saver_cli \
-f $HOME/ros2_ws/src/slam/maps/map_01 \
--ros-args -p map_subscribe_transient_local:=true
20. 小结
SLAM 建图的核心不是“打开 RViz 后看到一张图”,而是让激光扫描、里程计和 TF 在同一个时间与坐标体系下稳定工作。SLAM 算法利用里程计估计运动,再用激光扫描进行匹配和校正,通过回环优化减少累计误差,最终输出可供导航使用的二维栅格地图。
启动建图节点时,最重要的顺序是:
- 先加载 ROS2 和工作空间环境。
- 启动底盘、雷达和机器人模型。
- 检查
/scan、/odom、odom -> base_footprint和base_footprint -> lidar_frame。 - 通过
ros2 launch slam slam.launch.py启动 ROSLander 默认的同步slam_toolbox节点。 - 在 RViz2 中用
map作为 Fixed Frame 检查地图更新。 - 低速移动并完成闭环。
- 确认地图质量后保存
.yaml和.pgm。
对于 ROSLander,建图前必须先关闭 start_app_node.service,再使用 slam.launch.py 启动工程已经组织好的底盘、IMU、里程计、雷达、TF 和 slam_toolbox 链路。确认 /scan、/odom、odom -> base_footprint 和 base_footprint -> lidar_frame 都正常后,再使用 rviz_slam.launch.py、teleop_key_control.launch.py 完成建图,最后保存 map_01.yaml 和 map_01.pgm。后续问题可以按照“输入话题 → TF → 时间戳 → SLAM 参数 → 地图保存”的顺序逐层定位。





