Mapping、Localization 与 SLAM
先不安装算法。使用已经验证的 course_bot,亲眼确认“传感器和里程计都在工作”仍不等于“机器人拥有地图或知道自己在地图中的位置”。
你只需要 Gate A 的 ROS 2 基础,不需要安装 slam_toolbox 或 Nav2。
本课不生成地图,也不运行定位算法。course_bot 只是观察对象:我们先把现有数据能回答什么、不能回答什么说清楚。准备两个终端;页面会让两个终端都进入 ROS_DOMAIN_ID=31,避免其他机器人实验污染本课 Graph。
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。
同一台机器人启动后,可能面对两个完全不同的问题
把 course_bot 放进 lab.sdf。雷达可以看到墙,轮子转动后里程计也会变化,但机器人仍可能回答不了两个关键问题:这个环境以前从未见过,应该怎样留下可复用的空间记录?如果地图已经存在,机器人现在又位于地图中的哪里?
环境长什么样?
没有现成地图。机器人必须一边移动,一边判断每次扫描应放在什么位置,再逐步形成地图。
这是 Mapping 的工作场景我在地图中的哪里?
静态地图已经保存。机器人要把当前雷达观测与地图对齐,估计自己的全局位姿,而不是重新画一张地图。
这是 Localization 的工作场景里程计擅长描述短时间内相对怎样移动,但轮胎打滑、轮径误差和离散积分会持续累积误差。它能提供连续的局部运动线索,却不能凭空告诉机器人哪面墙位于全局地图的哪个位置。
Mapping 是任务目标,Localization 是状态估计,SLAM 是联合求解问题
建立空间记录
输入连续传感器观测和运动线索,输出可复用地图。因为每帧扫描都必须放到正确位置,建图过程中也必须不断估计机器人位姿。
地图未知,位姿也未知在已有地图中找自己
地图被当作固定参考。算法根据当前扫描、里程计、TF 和初始位姿,持续估计机器人相对 map 的位置。
地图已知,只求位姿同时估计位姿与地图
Simultaneous Localization and Mapping 的难点在于互相依赖:不知道位姿就放不准扫描,没有地图又缺少修正位姿的全局参照。
联合让两类未知量彼此约束工程中常把运行 SLAM 系统去生成地图简称为 Mapping。这不表示定位消失了:在线建图仍在估计轨迹,只是最终产品目标是地图。已有静态地图阶段则不再修改地图,主问题变成 Localization。
同样看到 /scan,不代表系统正在做同一件事
| 运行阶段 | 地图状态 | 主要问题 | 全局关系的责任方 |
|---|---|---|---|
| 只有机器人底座 | 没有 /map | 传感与局部运动是否正常 | 不存在 map → odom |
| Mapping | 未知、持续增长和修正 | 联合估计轨迹与地图 | 后续由 slam_toolbox 发布 map → odom |
| Localization | 已保存、固定 | 在静态地图中估计位姿 | Beginner 主线由 AMCL 发布 map → odom |
| Nav2 | 继续使用静态地图 | 基于已定位状态规划并控制 | 沿用 Map Server + AMCL 定位链 |
建图阶段允许地图随着新证据改变;静态定位阶段必须把地图当作稳定参照。如果同时让 Mapping 和 AMCL 都发布 map → odom,系统会出现两个全局位姿责任方。初学者看到的可能是 TF 抖动或地图与激光错位,根因却是运行模式没有切干净。
不要把有数据误读成已经知道全局位置
| 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 时是输入 |
证明:底座数据完整,仍然没有地图和全局位姿
实验只运行现有 course_bot,不安装任何 SLAM/Nav2 包。终端 A 保持 Launch,终端 B 负责观察和短距离低速命令。每一步先预测,再执行,再用输出修正自己的解释。
启动统一课程机器人
不要另建机器人、世界或接口。默认入口会启动 lab.sdf、course_bot 控制链、桥接和 RViz。
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 未隔离干净,应先清场而不是继续。
列出合同中的核心接口
先看名称和类型,不根据画面猜系统状态。
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。
读取观测与局部运动样本
这里不追求记住每个字段,只找 frame 和这条消息描述谁相对谁。
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 不存在。
观察动态 TF
运行后看两到三组输出,再按 Ctrl+C。
ros2 run tf2_ros tf2_echo odom base_link为什么现在执行?验证里程计估计以动态 TF 形式持续描述底座运动。
预期现象刚启动的第一行可能短暂提示等待 TF;随后持续输出 odom 到 base_link 的平移与旋转,时间戳随 /clock 推进。
实验说明了什么?这是局部连续关系。它的原点由启动时刻和里程计积分决定,不是 lab.sdf 的全局真值。
观察传感器安装 TF
同样看一到两组输出后按 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 本身不会从距离数组创造地图。
让 course_bot 低速前进约两秒
在 Gazebo 中确认机器人前方有空间。命令只发布 10 次,随后显式发送零速度。
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,所以地图仍不会凭空出现。
寻找故意尚不存在的全局层
第一条命令会正常结束;第二条会持续报告 map frame 不存在,观察几秒后按 Ctrl+C。
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
雷达能观测环境,因此 /scan 存在。
- 2
底座能估计局部运动,因此 Odometry 与 odom → base_link 存在。
- 3
雷达安装关系已知,因此 base_link → laser_link 存在。
- 4
没有 Mapping/Localization 节点,因此 /map 与 map → odom 不存在。
为什么先 Mapping,再保存地图,再 Localization,最后 Nav2
先获得可用地图
slam_toolbox 消费 /scan、局部 TF 与时间,联合估计轨迹和地图;此时它负责 /map 与 map → odom。
把变化中的结果冻结成资产
保存静态 YAML 与图像。它不再依赖本次 Mapping 进程存活,之后可以被重新加载和版本管理。
换成固定地图中的位姿估计
Map Server 提供 /map,AMCL 将当前 /scan 与静态地图比较,并负责 map → odom。不同时运行 Mapping 主线。
在知道自己在哪之后行动
Nav2 消费地图、定位结果、TF 和传感器,规划路径并输出速度命令。导航不是替代定位,而是建立在定位之上。
Topic 存在,不代表算法能在正确坐标中使用它
保持 course_bot 运行,故意查询一个拼错的雷达 frame。观察几秒后按 Ctrl+C;这个命令不会修改机器人。
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 证明雷达有频率,不能证明扫描能被变换和融合。
没有环境观测
Mapping 无法形成墙体证据;Localization 无法把当前场景与已有地图比较。
Topic、类型、Publisher、QoS、frame_id。
局部初值逐渐偏离
短时仍连续,但转弯、打滑或轮径错误会让扫描匹配与定位更困难。
轮子反馈、频率、方向、尺度、协方差。
数据无法进入共同坐标
LaserScan 有值也不能被放进 base_link、odom 或 map 中。
消息 frame_id、TF 树、时间戳、责任方。
先判断当前模式
Mapping 开始前是正常状态;静态 Localization 中则是阻断故障。
是在建图,还是应该由 Map Server 加载地图。
先判断系统在解决什么问题,再判断接口是否正确
Mapping 只负责画地图,不需要估计机器人位姿。
course_bot 的 Odometry 持续变化,说明机器人已经知道自己在 map 中的位置。
Beginner 主线在已有静态地图中由谁承担 Localization?
/map 在 Mapping 与静态 Localization 中分别是什么?
如果已有地图,机器人被搬到另一个房间,应重新 Mapping 吗?
先说出你的判断,再展开
默认不应该重新建图。地图仍有效时,问题是机器人当前位姿未知,应通过初始位姿、粒子分布和观测匹配重新完成 Localization。只有环境结构已经显著变化、地图本身不再适用时,才考虑重新建图或更新地图。
为什么给一个初始位姿不等于定位成功?
初始位姿只是先验猜测。AMCL 还需要利用后续 LaserScan 与地图的匹配让估计收敛,并通过粒子分布、激光重合、/amcl_pose 和 map → odom 的稳定性验证结果。
不用 SLAM 软件,交付一份系统现在知道什么报告
保持 course_bot 运行,独立完成下面的记录。不要只写定义,要引用你亲眼看到的 Topic、消息字段和 TF 输出。
- 01
为 /scan、Odometry、/tf、/tf_static 写出消息类型、关键 frame 和一句职责说明。
- 02
画出当前实际存在的 odom → base_link → laser_link,并明确指出缺少哪一段全局关系。
- 03
用运行证据证明机器人移动后 /scan 与 Odometry 会变化,但 /map 仍不存在。
- 04
分别画出 Mapping 与静态 Localization 的输入、输出和 map → odom 责任方。
- 05
用自己的话解释为什么课程不能直接从 LaserScan 跳到 Nav2。
- 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 必须建立在地图与已验证定位之上。
把机器人明确停下,并把 ROS_DOMAIN_ID=31 还原为空房间
先在终端 B 再发送一次零速度。即使 STEP 5 已经停车,这一步也让结束状态不依赖之前是否完整执行。
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 执行下面的检查。
ros2 node list --no-daemon为什么现在执行?确认本课不会污染下一次 SLAM 实验。
预期现象不输出任何节点。若仍有节点,先不要继续下一课;检查终端 A 是否已经完整退出。
实验说明了什么?空 Graph 是本课真正结束的技术条件,而不只是关闭了可见窗口。
先判断地图是否已知,再判断系统应该 Mapping 还是 Localization
Mapping 的产品目标是建立地图,但过程必须同时估计轨迹;Localization 把已保存地图当作固定参考,只持续估计机器人位姿;SLAM 描述地图与位姿必须联合求解的核心问题。/scan 提供环境观测,Odometry 提供局部运动,TF 让数据在正确时间进入共同坐标,Map 保存全局空间结构。
不看正文也能画出当前 course_bot 数据链、Mapping 数据链和静态 Localization 数据链,并为每条箭头说出责任方。
如果仍把有 /scan、有 Odometry、有 /map、已经定位视为同一件事,请回到 STEP 2–6,用真实输出重新回答每条数据能证明什么、不能证明什么。