本课路线 ↗
SLAM CORE · CONCEPT FOUNDATION · 约 45–60 分钟

Mapping、Localization 与 SLAM

先不安装算法。使用已经验证的 course_bot,亲眼确认“传感器和里程计都在工作”仍不等于“机器人拥有地图或知道自己在地图中的位置”。

SLAM CORE SC01/ SAMPLE LESSON
开始前

你只需要 Gate A 的 ROS 2 基础,不需要安装 slam_toolbox 或 Nav2。

本课不生成地图,也不运行定位算法。course_bot 只是观察对象:我们先把现有数据能回答什么、不能回答什么说清楚。准备两个终端;页面会让两个终端都进入 ROS_DOMAIN_ID=31,避免其他机器人实验污染本课 Graph。

30 秒自检export ROS_DOMAIN_ID=31 source /opt/ros/jazzy/setup.bash source ~/robot_ws/install/setup.bash ros2 node list --no-daemon ros2 pkg prefix robot_bringup ros2 launch robot_bringup robot.launch.py --show-args

第一条 ROS 命令应不输出节点,随后应找到 robot_bringup,并列出 rviz、bridge、drive_controller 等 Launch 参数。若节点清单非空,先结束 ROS_DOMAIN_ID=31 中的遗留实验;两个终端必须使用同一个空闲 Domain。若包不存在,检查是否 source 了 ~/robot_ws/install/setup.bash。

01 · 从机器人问题出发

同一台机器人启动后,可能面对两个完全不同的问题

把 course_bot 放进 lab.sdf。雷达可以看到墙,轮子转动后里程计也会变化,但机器人仍可能回答不了两个关键问题:这个环境以前从未见过,应该怎样留下可复用的空间记录?如果地图已经存在,机器人现在又位于地图中的哪里?

UNKNOWN WORLD

环境长什么样?

没有现成地图。机器人必须一边移动,一边判断每次扫描应放在什么位置,再逐步形成地图。

这是 Mapping 的工作场景
KNOWN WORLD

我在地图中的哪里?

静态地图已经保存。机器人要把当前雷达观测与地图对齐,估计自己的全局位姿,而不是重新画一张地图。

这是 Localization 的工作场景
为什么不能只靠里程计?

里程计擅长描述短时间内相对怎样移动,但轮胎打滑、轮径误差和离散积分会持续累积误差。它能提供连续的局部运动线索,却不能凭空告诉机器人哪面墙位于全局地图的哪个位置。

02 · 三个词,三个边界

Mapping 是任务目标,Localization 是状态估计,SLAM 是联合求解问题

MAPPING

建立空间记录

输入连续传感器观测和运动线索,输出可复用地图。因为每帧扫描都必须放到正确位置,建图过程中也必须不断估计机器人位姿。

地图未知,位姿也未知
LOCALIZATION

在已有地图中找自己

地图被当作固定参考。算法根据当前扫描、里程计、TF 和初始位姿,持续估计机器人相对 map 的位置。

地图已知,只求位姿
SLAM

同时估计位姿与地图

Simultaneous Localization and Mapping 的难点在于互相依赖:不知道位姿就放不准扫描,没有地图又缺少修正位姿的全局参照。

联合让两类未知量彼此约束

工程中常把运行 SLAM 系统去生成地图简称为 Mapping。这不表示定位消失了:在线建图仍在估计轨迹,只是最终产品目标是地图。已有静态地图阶段则不再修改地图,主问题变成 Localization。

一句话心智模型Mapping:边找自己边画世界;Localization:世界不再改,只在其中找自己;SLAM:解释为什么边找边画必须联合进行。
03 · 为什么它们不是同一种运行状态

同样看到 /scan,不代表系统正在做同一件事

运行阶段地图状态主要问题全局关系的责任方
只有机器人底座没有 /map传感与局部运动是否正常不存在 map → odom
Mapping未知、持续增长和修正联合估计轨迹与地图后续由 slam_toolbox 发布 map → odom
Localization已保存、固定在静态地图中估计位姿Beginner 主线由 AMCL 发布 map → odom
Nav2继续使用静态地图基于已定位状态规划并控制沿用 Map Server + AMCL 定位链

建图阶段允许地图随着新证据改变;静态定位阶段必须把地图当作稳定参照。如果同时让 Mapping 和 AMCL 都发布 map → odom,系统会出现两个全局位姿责任方。初学者看到的可能是 TF 抖动或地图与激光错位,根因却是运行模式没有切干净。

SC03–SC05slam_toolbox Mapping地图与轨迹共同形成
SC06保存静态地图冻结可复用空间资产
SC07–SC08Map Server + AMCL地图固定,只估计位姿
GATE B进入 Nav2定位已经真实验收
04 · 四类信息各自负责什么

不要把有数据误读成已经知道全局位置

course_bot 接口它回答的问题它不能单独回答的问题后续角色
/scanLaserScan · laser_link雷达此刻在各方向看到了多远墙在 map 中哪里;机器人全局位姿是多少Mapping 的环境证据;Localization 与地图比较的观测
/diff_drive_controller/odomnav_msgs/msg/Odometry机器人相对 odom 的连续局部运动估计无漂移全局位置;环境结构给扫描匹配和 AMCL 提供短时运动线索
/tf 与 /tf_staticodom → base_link → laser_link怎样把雷达观测变换到机器人和里程计坐标地图内容;不存在的全局校正把数据接入同一坐标关系,后续再接上 map
/mapnav_msgs/msg/OccupancyGrid哪些区域未知、空闲或占用机器人当前必然在哪里Mapping 时是输出;静态 Localization 时是输入
OBSERVE/scan环境几何
MOVEodom → base_link局部连续运动
MOUNTbase_link → laser_link传感器安装关系
TIME/clock统一采集时刻
LATERMapping / Localization按运行状态解释同一批输入
05 · course_bot 观察实验

证明:底座数据完整,仍然没有地图和全局位姿

实验只运行现有 course_bot,不安装任何 SLAM/Nav2 包。终端 A 保持 Launch,终端 B 负责观察和短距离低速命令。每一步先预测,再执行,再用输出修正自己的解释。

STEP 1

启动统一课程机器人

不要另建机器人、世界或接口。默认入口会启动 lab.sdf、course_bot 控制链、桥接和 RViz。

TERMINAL A · 保持运行
export ROS_DOMAIN_ID=31
source /opt/ros/jazzy/setup.bash
source ~/robot_ws/install/setup.bash
ros2 launch robot_bringup robot.launch.py

为什么现在执行?建立后续所有观察共享、且与其他 ROS 实验隔离的真实系统边界。

预期现象Gazebo Harmonic 打开 lab.sdf 并出现 course_bot;控制器完成加载。RViz 的 Fixed Frame 为 odom,能看到 LaserScan;此时没有 Map 显示。终端保持运行。

实验说明了什么?现在运行的是机器人底座,不是 Mapping 或 Localization。看到 RViz 和扫描点并不表示已经有地图。若 RViz 出现其他机器人的 frame,说明 Domain 未隔离干净,应先清场而不是继续。

STEP 2

列出合同中的核心接口

先看名称和类型,不根据画面猜系统状态。

TERMINAL B · 接口清单
export ROS_DOMAIN_ID=31
source /opt/ros/jazzy/setup.bash
source ~/robot_ws/install/setup.bash
ros2 topic list -t | grep -E '^/(clock|scan|tf|tf_static) |^/diff_drive_controller/(cmd_vel|odom) '

为什么现在执行?确认本课讨论的输入确实由 course_bot 提供。

预期现象至少看到: /clock [rosgraph_msgs/msg/Clock] /scan [sensor_msgs/msg/LaserScan] /diff_drive_controller/odom [nav_msgs/msg/Odometry] /diff_drive_controller/cmd_vel [geometry_msgs/msg/TwistStamped] /tf 与 /tf_static [tf2_msgs/msg/TFMessage]

实验说明了什么?机器人已有时间、激光观测、局部里程计、控制入口与坐标关系。接口完整不等于存在 /map。

STEP 3

读取观测与局部运动样本

这里不追求记住每个字段,只找 frame 和这条消息描述谁相对谁。

TERMINAL B · 逐条执行
ros2 topic echo /clock --once
ros2 topic echo /scan --once --field header
ros2 topic echo /scan --once --field ranges --csv | cut -d, -f176-183
ros2 topic echo /diff_drive_controller/odom --once --field header
ros2 topic echo /diff_drive_controller/odom --once --field child_frame_id
ros2 topic echo /diff_drive_controller/odom --once --field pose.pose

为什么现在执行?把 /clock、/scan 与 Odometry 的名称变成可解释的运行证据。

预期现象/clock 给出持续推进的仿真时间;/scan.header.frame_id 为 laser_link,中间八束 ranges 给出距离。Odometry 的 header.frame_id 为 odom、child_frame_id 为 base_link,pose 给出局部位姿。

实验说明了什么?/scan 没有 map 坐标;Odometry 也没有 map 父坐标。它们提供同一时间域中的观测和局部运动,但都没有声明机器人在全局地图中的位置。

Jazzy CLI 提示:新启动的 echo 偶尔会先打印一次 A message was lost。如果紧接着仍输出 header、frame 或 pose 样本,说明本次读取已成功;不要把这行提示误当成 Topic 不存在。

STEP 4A

观察动态 TF

运行后看两到三组输出,再按 Ctrl+C。

TERMINAL B · 观察后 Ctrl+C
ros2 run tf2_ros tf2_echo odom base_link

为什么现在执行?验证里程计估计以动态 TF 形式持续描述底座运动。

预期现象刚启动的第一行可能短暂提示等待 TF;随后持续输出 odom 到 base_link 的平移与旋转,时间戳随 /clock 推进。

实验说明了什么?这是局部连续关系。它的原点由启动时刻和里程计积分决定,不是 lab.sdf 的全局真值。

STEP 4B

观察传感器安装 TF

同样看一到两组输出后按 Ctrl+C。

TERMINAL B · 观察后 Ctrl+C
ros2 run tf2_ros tf2_echo base_link laser_link

为什么现在执行?确认每一束扫描都能从雷达坐标变换到机器人底座坐标。

预期现象刚启动时可能短暂等待 TF;随后在 time 0.0 重复输出固定平移约 [0.120, 0.000, 0.200] 和单位旋转,数值不随机器人移动而改变。

实验说明了什么?time 0.0 表示静态变换,不是时间没有运行。这是安装关系,不是定位结果;TF 本身不会从距离数组创造地图。

STEP 5

让 course_bot 低速前进约两秒

在 Gazebo 中确认机器人前方有空间。命令只发布 10 次,随后显式发送零速度。

TERMINAL B · 有限次数安全命令
ros2 topic pub -s -r 5 -t 10 /diff_drive_controller/cmd_vel geometry_msgs/msg/TwistStamped "{header: auto, twist: {linear: {x: 0.08}, angular: {z: 0.0}}}"
ros2 topic pub -s -1 /diff_drive_controller/cmd_vel geometry_msgs/msg/TwistStamped "{header: auto, twist: {linear: {x: 0.0}, angular: {z: 0.0}}}"
ros2 topic echo /diff_drive_controller/odom --once --field pose.pose
ros2 topic echo /diff_drive_controller/odom --once --field twist.twist

为什么现在执行?让里程计与扫描产生可观察变化,检验变化的数据是否会自动生成地图。

预期现象TwistStamped 以 0.08 m/s 发布 10 次,随后显式零速;Odometry 的 pose.x 发生变化,最后的 twist.linear.x 应接近 0。若画面异常,立即回到终端 A 按 Ctrl+C。

实验说明了什么?机器人移动、里程计变化、扫描变化都是真的;但没有算法把多帧扫描对齐并写入 OccupancyGrid,所以地图仍不会凭空出现。

STEP 6

寻找故意尚不存在的全局层

第一条命令会正常结束;第二条会持续报告 map frame 不存在,观察几秒后按 Ctrl+C。

TERMINAL B · 预期失败实验
ros2 topic list | grep -E '^/map$' || echo 'EXPECTED: /map is absent before Mapping'
ros2 run tf2_ros tf2_echo map odom

为什么现在执行?用缺失证据证明当前系统只到 odom 层,而不是把看不到地图误判为雷达故障。

预期现象先输出 EXPECTED: /map is absent before Mapping;tf2_echo 随后反复报告 map frame 不存在。按 Ctrl+C 结束第二条命令。这是预期结果,不是程序坏了。

实验说明了什么?实测 TF 树只有 odom → base_link,并从 base_link 分出 laser_link、车轮和脚轮;/scan、Odometry、局部 TF 与 /clock 健康,但还没有建立全局层的算法。

实验后的证据链
  1. 1

    雷达能观测环境,因此 /scan 存在。

  2. 2

    底座能估计局部运动,因此 Odometry 与 odom → base_link 存在。

  3. 3

    雷达安装关系已知,因此 base_link → laser_link 存在。

  4. 4

    没有 Mapping/Localization 节点,因此 /map 与 map → odom 不存在。

前三条是 SLAM 输入前提;它们不会自动推导出第四条的全局层。
06 · 把实验放回后续主线

为什么先 Mapping,再保存地图,再 Localization,最后 Nav2

1 · MAPPING

先获得可用地图

slam_toolbox 消费 /scan、局部 TF 与时间,联合估计轨迹和地图;此时它负责 /map 与 map → odom。

2 · SAVE

把变化中的结果冻结成资产

保存静态 YAML 与图像。它不再依赖本次 Mapping 进程存活,之后可以被重新加载和版本管理。

3 · LOCALIZATION

换成固定地图中的位姿估计

Map Server 提供 /map,AMCL 将当前 /scan 与静态地图比较,并负责 map → odom。不同时运行 Mapping 主线。

4 · NAV2

在知道自己在哪之后行动

Nav2 消费地图、定位结果、TF 和传感器,规划路径并输出速度命令。导航不是替代定位,而是建立在定位之上。

Beginner v1 唯一主线slam_toolbox Mapping → 保存静态地图 → Map Server 提供地图 → AMCL Localization → Gate B → Nav2
07 · 故障推理实验

Topic 存在,不代表算法能在正确坐标中使用它

保持 course_bot 运行,故意查询一个拼错的雷达 frame。观察几秒后按 Ctrl+C;这个命令不会修改机器人。

TERMINAL B · 故意使用错误 FRAME
ros2 run tf2_ros tf2_echo base_link laser_typo

为什么现在执行?模拟 LaserScan 在发布,但消息引用的传感器 frame 没接入 TF 这一类真实故障。

预期现象第一行可能在 TF 缓冲区建立前短暂提到 base_link;随后稳定报告 laser_typo frame 不存在。/scan 仍在发布,正确的 laser_link TF 没有被破坏。

实验说明了什么?SLAM 需要同时满足数据、时间和坐标关系。只用 topic hz /scan 证明雷达有频率,不能证明扫描能被变换和融合。

缺 /SCAN

没有环境观测

Mapping 无法形成墙体证据;Localization 无法把当前场景与已有地图比较。

优先检查

Topic、类型、Publisher、QoS、frame_id。

ODOM 漂移

局部初值逐渐偏离

短时仍连续,但转弯、打滑或轮径错误会让扫描匹配与定位更困难。

优先检查

轮子反馈、频率、方向、尺度、协方差。

缺 TF

数据无法进入共同坐标

LaserScan 有值也不能被放进 base_link、odom 或 map 中。

优先检查

消息 frame_id、TF 树、时间戳、责任方。

缺 /MAP

先判断当前模式

Mapping 开始前是正常状态;静态 Localization 中则是阻断故障。

优先检查

是在建图,还是应该由 Map Server 加载地图。

模式先于排错同一个没有 /map 的现象:在 course_bot 底座阶段是预期,在 Mapping 运行后是故障,在 Localization 阶段则说明静态地图链未建立。
08 · 知识检查

先判断系统在解决什么问题,再判断接口是否正确

概念判断

Mapping 只负责画地图,不需要估计机器人位姿。

接口判断

course_bot 的 Odometry 持续变化,说明机器人已经知道自己在 map 中的位置。

流程判断

Beginner 主线在已有静态地图中由谁承担 Localization?

角色判断

/map 在 Mapping 与静态 Localization 中分别是什么?

系统阅读题

如果已有地图,机器人被搬到另一个房间,应重新 Mapping 吗?

先说出你的判断,再展开

默认不应该重新建图。地图仍有效时,问题是机器人当前位姿未知,应通过初始位姿、粒子分布和观测匹配重新完成 Localization。只有环境结构已经显著变化、地图本身不再适用时,才考虑重新建图或更新地图。

为什么给一个初始位姿不等于定位成功?

初始位姿只是先验猜测。AMCL 还需要利用后续 LaserScan 与地图的匹配让估计收敛,并通过粒子分布、激光重合、/amcl_pose 和 map → odom 的稳定性验证结果。

09 · MINI CHALLENGE

不用 SLAM 软件,交付一份系统现在知道什么报告

保持 course_bot 运行,独立完成下面的记录。不要只写定义,要引用你亲眼看到的 Topic、消息字段和 TF 输出。

  1. 01

    为 /scan、Odometry、/tf、/tf_static 写出消息类型、关键 frame 和一句职责说明。

  2. 02

    画出当前实际存在的 odom → base_link → laser_link,并明确指出缺少哪一段全局关系。

  3. 03

    用运行证据证明机器人移动后 /scan 与 Odometry 会变化,但 /map 仍不存在。

  4. 04

    分别画出 Mapping 与静态 Localization 的输入、输出和 map → odom 责任方。

  5. 05

    用自己的话解释为什么课程不能直接从 LaserScan 跳到 Nav2。

  6. 06

    判断机器人被搬到已有地图的另一个位置主要是 Mapping 还是 Localization 问题,并写出理由。

需要提示时再展开

先写四列:观察对象、真实输出、它能回答的问题、它不能回答的问题。Mapping 图中让 slam_toolbox 同时输出 /map 与 map → odom;Localization 图中让 Map Server 输出 /map、AMCL 输出 map → odom。

完成后核对最低结论

当前 course_bot 只有局部链 odom → base_link → laser_link;/scan 是 laser_link 中的环境观测,Odometry 是局部运动估计,TF 提供变换关系,/map 尚不存在。Mapping 联合估计地图和轨迹;保存后地图固定,由 Map Server 提供,AMCL 在其中定位;Nav2 必须建立在地图与已验证定位之上。

10 · 实验清场

把机器人明确停下,并把 ROS_DOMAIN_ID=31 还原为空房间

先在终端 B 再发送一次零速度。即使 STEP 5 已经停车,这一步也让结束状态不依赖之前是否完整执行。

TERMINAL B · 最终零速
ros2 topic pub -s -1 /diff_drive_controller/cmd_vel geometry_msgs/msg/TwistStamped "{header: auto, twist: {linear: {x: 0.0}, angular: {z: 0.0}}}"

为什么现在执行?给控制器一个明确、可重复的停止状态。

预期现象只发布一条 TwistStamped,linear 与 angular 都为 0;机器人保持静止。

实验说明了什么?有限运动命令和最终零速共同避免机器人因中断步骤而继续运动。

最后

回到终端 A 按 Ctrl+C

等待 Launch 完整退出并重新出现 shell 提示符;不要直接关闭终端或遗留 Gazebo、RViz。然后回到终端 B 执行下面的检查。

TERMINAL B · Launch 完整退出后
ros2 node list --no-daemon

为什么现在执行?确认本课不会污染下一次 SLAM 实验。

预期现象不输出任何节点。若仍有节点,先不要继续下一课;检查终端 A 是否已经完整退出。

实验说明了什么?空 Graph 是本课真正结束的技术条件,而不只是关闭了可见窗口。

本节心智模型

先判断地图是否已知,再判断系统应该 Mapping 还是 Localization

Mapping 的产品目标是建立地图,但过程必须同时估计轨迹;Localization 把已保存地图当作固定参考,只持续估计机器人位姿;SLAM 描述地图与位姿必须联合求解的核心问题。/scan 提供环境观测,Odometry 提供局部运动,TF 让数据在正确时间进入共同坐标,Map 保存全局空间结构。

你现在应该能区分 Mapping 与 Localization解释 SLAM 的联合关系读出 course_bot 的局部数据链说明 /map 的输入输出身份复述 Beginner 默认定位路线

下一步:先实际学习并反馈本课。SC01 验证通过后,SC02 才会把这些概念升级为正式的 scan / odom / time / TF 输入预检。

本课验收

不看正文也能画出当前 course_bot 数据链、Mapping 数据链和静态 Localization 数据链,并为每条箭头说出责任方。

如果仍把有 /scan、有 Odometry、有 /map、已经定位视为同一件事,请回到 STEP 2–6,用真实输出重新回答每条数据能证明什么、不能证明什么。

/ 当前实施边界

先把第一节真正学透

本轮只开放 SC01,不用提纲占位伪装后续课程。通过实际学习验证后,才进入 course_bot 输入预检。

SLAM CORE · SC01

问题边界与系统心智模型

能区分建图、已有地图定位与联合估计,并说明 scan、Odometry、TF、Map 的职责。

AVAILABLE NOW