核心课程 / SLAM 与自主导航 / EXP-01
EXP-01 · 第 1 章 · 验证性 · 4 学时

SLAM 原理与 URDF 模型

这节课不写一行复杂代码,目标就两件事——① 用"蒙眼逛新家"的比喻听懂 SLAM 到底在干什么;② 用 URDF + RViz 把 car1 机器人"搬"到电脑屏幕上,看看它长什么样、坐标怎么连。

难度 ⭐ · 4 学时 SLAM 概念URDF / Xacro TF 树rviz2

01问题引入:没地图的"鸡生蛋"困局

你要让扫地机器人打扫房间,它得先知道自己"在房间哪一格";可要判断自己在哪,它又得先有一张地图。定位需要地图,建图又需要知道自己的位置——这就是 SLAM 面对的经典鸡生蛋问题。再看另一个现实:

1雷达每秒扫 10 圈,每次只看到"眼前这一环"墙壁
2车轮里程计会打滑(麦轮原地转时航向漂移可达 45%),走着走着就"记错"自己挪了多远
3没有地图 → 无法修正漂移;不修正漂移 → 地图拼歪(墙变双影、直角变钝角)
4SLAM 把两件事绑在一起同时解:边走边画图,边画图边锁定自己的位置
💡 一句话读懂这一课只做两件事:① 用"蒙眼逛新家"的比喻听懂 SLAM 在算什么;② 用 URDF + RViz 把 car1 机器人"搬"到屏幕上,看清它的连杆、关节与坐标树怎么连。

02原理通俗讲解:SLAM 与 URDF 是什么

🏠 蒙眼逛新家把你蒙上眼睛送进一套新房:你伸手摸到"左边 2 米有墙",就边走边在脑子里画户型图;同时因为你记得"刚才向右转了 90°、走了 3 步",也能在图上标出自己此刻站哪——画图与定位互相喂料,这就是 SLAM(Simultaneous Localization And Mapping,同时定位与建图)。雷达 = 你的手,里程计 = 你的步感,脑子 = SLAM 算法。
感知
激光雷达 /scan

COIN-D6 每秒 10 圈、量程 0.05~12 m,把周围墙壁变成一圈"距离点",相当于蒙眼人的手。

定位
里程计 + 扫描匹配

轮速与 IMU 经 EKF 融合推算走了多远(仍会漂),再拿新一圈扫描与上一圈对齐修正——相当于步感 + 摸墙校正。

构图
占据栅格地图

世界切成 0.05 m 的小格子:100 = 占据、0 = 空闲、-1 = 没扫到的未知区。手电筒照到哪,哪格就"亮"。

URDF(Unified Robot Description Format,统一机器人描述格式)则是机器人的"身份证 + 骨架说明书":用 XML 写清每个连杆(link)的外观尺寸与惯性、每个关节(joint)怎么连、能怎么动。TF 坐标树再把 base_link、雷达、机械臂这些坐标系串成一棵树——没有这棵树,雷达数据不知道该画在车体哪一侧,后面建图全是乱的。

概念生活比喻本课程对应
SLAM蒙眼逛新家:边摸边画图,边画图边记自己站哪EXP-02 用 SLAM Toolbox 真正跑起来
占据栅格户型图的方格纸:每格标"墙 / 空 / 未勘探"话题里是 100/0/-1,存成 .pgm 后变成 0/254/205 灰度
URDF机器人的乐高拼装说明书car1.urdf:底盘 + arm_link1~6 + 夹爪
joint骨头之间的关节:有的焊死、有的能转fixed / continuous / revolute 三类
TF 树全家福站位图:谁站在谁旁边、朝向哪根是 base_footprint,树断一则全乱

03关联知识点

知识点 1
SLAM 鸡生蛋

定位要地图、建图要定位,SLAM 把两者联合求解,是移动机器人自主性的起点。

知识点 2
建图三要素

感知(雷达)→ 定位(里程计+匹配)→ 构图(栅格),缺一则地图不可用。

知识点 3
link 三重属性

visual 看的、collision 撞的、inertial 算动力学的——三者可以引用不同精度的几何模型。

知识点 4
joint 三类型

fixed 焊死(底盘→雷达)、continuous 无界旋转(4 个麦轮)、revolute 有界旋转(臂与夹爪,必须带 limit)。

知识点 5
TF 树根节点

根是 base_footprint(车体在地面的投影点),base_joint 以 fixed 抬升 0.029 m 接 base_link,保证 2D 导航的"地面基准"。

知识点 6
mimic 联动关节

夹爪 5 个指节里只有 r_joint 是主动的,其余用 <mimic> 跟随它——滑条只拖一根,五根指节一起动。

04实验目标

  • 能用一句生活比喻向他人讲清 SLAM = 边走边画地图 + 同时定位
  • 能指认 car1 URDF 中 底盘 / 机械臂 / 夹爪 三部分及各自 joint 类型
  • 跑通 display.launch.py 可视化三件套,在 rviz2 中看到模型并拖动关节滑条
  • 用 view_frames 导出 TF 树,说清 base_footprint → base_link → laser_link 链路
  • 解释栅格地图三值 -1 / 0 / 100 与分辨率 0.05 m 的含义

05实验环境与准备

本实验纯软件即可完成(有实物更佳):只需 Ubuntu 22.04 + ROS2 Humble 与已编译的 car1_description 功能包。

项目要求
系统Ubuntu 22.04 + ROS2 Humble,source 工作空间并编译通过
软件包car1_description(含 urdf/car1.urdf、launch/display.launch.py、rviz/car1.rviz)、joint_state_publisher_gui、robot_state_publisher、rviz2
源码位置ros2_ws/src/xflz_eai_car1/simulations/car1_description/
可选硬件小车 C1:COIN-D6 雷达(设备节点 /dev/lidar、话题 /scan、10 Hz、Best Effort QoS)
验证命令ros2 topic hz /scan 约 10 Hz(接实物时)

06动画讲解:激光扫描与栅格地图

左:雷达扇形束一圈圈扫过,量出到墙面的距离;右:扫到的格子被逐格"点亮"——100(墙)、0(空闲)、-1(灰色未知)。小车走到哪,亮的格子就铺到哪,这就是建图的最小过程。

激光雷达 · 扇形扫描一圈圈量距离 占据栅格 · 格子逐格填充 car1 · /scan 10Hz 每一根射线 = 一个距离读数,一圈覆盖 360° 0 空闲(绿) · 100 墙(白线) · -1 未知(深灰) 分辨率 0.05 m:一格 = 5 厘米见方
动画 1-1|左:雷达扇形逐束测距(1.4 秒循环);右:小车驶过,空闲格逐格点亮成"户型图"(3 秒填充 + 6 秒往返)

07互动讲解:joint 类型 × TF 树拼装台

点击按钮给两个 link 选一种 joint,看 TF 父子关系与车体行为如何变化:

TF 树拼装台 · Joint Lab
// 点击上方按钮,观察 joint 类型如何决定 TF 行为 car1 默认结构:base_footprint →(fixed)→ base_link →(fixed)→ laser_link
💡 判断口诀焊死不动选 fixed;转起来没完没了(轮子)选 continuous;有最大最小角度限制、必须带 <limit>(机械臂与夹爪)选 revolute。类型选错,rviz 里要么拖不动、要么转飞了。

08实操步骤:display 三件套看模型

1source 环境后启动可视化:joint_state_publisher_gui(关节滑条)+ robot_state_publisher(算 TF)+ rviz2(显示),rviz 已自带 car1.rviz 配置
2确认 Fixed Frame 为 base_footprint、RobotModel 已加载;拖动滑条观察机械臂与夹爪联动
3导出 TF 树核对父子关系,并数一数 URDF 里到底有多少 link 与 joint
# ① 启动模型可视化(本实验核心命令,一条命令拉起三个节点)
ros2 launch car1_description display.launch.py

# ② 导出 TF 坐标树:生成 frames_<时间戳>.pdf 与 frames.gv(在命令执行的当前目录)
ros2 run tf2_tools view_frames

# ③ 数一数 URDF 里有多少连杆与关节(真实值:17 link / 21 joint)
grep -c "<link name=" car1.urdf
grep -c "<joint name=" car1.urdf

# ④(接实物)确认雷达数据在发
ros2 topic hz /scan
# 约 10.0 Hz

# car1.urdf 关键片段:根节点贴地,fixed 抬升 0.029 m 接车体
<link name="base_footprint" />
<joint name="base_joint" type="fixed">
  <origin xyz="0 0 0.029" rpy="0 0 0" />
  <parent link="base_footprint" />
  <child link="base_link" />
</joint>
car1 三部分构成joint 类型
底盘主体base_link + 4 个麦轮(wheel_lf/rf/lb/rb)+ 雷达 / IMU 支架base_joint、laser_joint、imu_joint 全为 fixed;4 个轮子为 continuous
机械臂arm_base_link + arm_link1~6(臂体 6 段,其中 arm_link6 用 fixed 与 link5 固定)arm_joint1~5 为 revolute(各带 ±3.14 rad limit)→ 5 自由度
夹爪r_link / l_link / r_in_link / r_out_link / l_in_link / l_out_link 共 6 段指节全部 revolute;仅 r_joint 主动,其余 5 个用 <mimic> 跟随开合
🔍 为什么要"数一遍"URDF 里 <link> 数 = 17、<joint> 数 = 21,而自由度只有 4(轮子)+ 5(臂)+ 1(夹爪)= 10:因为 fixed 关节、mimic 从动关节都不贡献自由度。分清"节点数"与"自由度",后面算正逆运动学才不会乱。

09重点与易错点

⚠️ 重点看懂 TF 树 = 拿到全课程的"导航地图坐标基准"。只要是"数据该画在哪"的问题,一律先查 TF:根是不是 base_footprint、链路有没有断、雷达是不是挂在 base_link 下。
⚠️ 常见坑现象 → 解法
rviz 里没有模型画面空白 → 检查左侧 Displays 里 RobotModel 是否 Enabled、Description Topic 是否为 /robot_description
joint 滑条拖不动该关节是 fixed / mimic 从动关节(本来就拖不动)→ 改拖 arm_joint1~5 或 r_joint
找不到功能包Package 'car1_description' not found → 忘了 source 工作空间的 install/setup.bash,或未编译该包
模型扭曲/位置错origin 的 xyz/rpy 写错单位(米/弧度)→ 对照实物量一遍
TF 树断裂view_frames 里缺 laser_link → robot_state_publisher 没起,或 URDF parent/child 写反
  • display.launch.py 一键拉起 三件套,rviz2 能看到完整车体与机械臂
  • frames.pdf 中 root = base_footprint,laser_link 与 imu_link 挂在 base_link 之下
  • 能说出 -1 / 0 / 100 与 0.05 m 分辨率的含义
  • 能指出 car1 三部分及 fixed / continuous / revolute 各用在哪里,并说出自由度为何是 10

10总结与思考

✅ 完成成果
模型上屏 + TF 树落盘

display 三件套跑通,rviz2 见车、view_frames 见树、grep 数出 17 link / 21 joint。

🧠 原理收获
SLAM 鸡生蛋与三要素

感知 → 定位 → 构图闭环;蒙眼逛新家比喻讲清同时定位与建图。

🛠 技能收获
URDF 读写与可视化

link/joint/origin 三件套语法,三种 joint 类型辨识,mimic 联动关系。

❓ 排障思路
无模型四问

source 了吗 → RobotModel 加了吗 → Fixed Frame 对吗 → parent/child 反了吗。

📝 思考题 1SLAM 的"鸡生蛋"是怎么被打破的?提示从"猜测也是一组可能答案"出发——如果把"我可能在这 100 个位置"全部同时拿去和雷达扫描比对,会引出什么方法?(预告:EXP-04 粒子滤波)
📝 思考题 2栅格地图为什么用 -1 / 0 / 100 三个值而不是布尔值?-1(未知区)在导航时应该被当成"可以走"还是"不能走"?为什么没扫过的区域保留成"未知",反而比直接刷成"空闲"更安全?