第一个导航地图:使用SLAM Toolbox构建静态地图,地图格式(YAML + PGM/PNG)详解
各位同学,欢迎来到第三章。
上一章我们把Nav2的环境搭好了,也跑通了第一个demo。但说实话,那个demo用的是官方自带的仿真地图,跟咱们实际要用的场景差得远。做机器人导航,第一步就是得有一张靠谱的地图。地图不对,后面所有路径规划、避障都是白搭。
这一章,我就带你亲手建一张静态地图。我们用到的工具是SLAM Toolbox,它是ROS2里最主流的建图工具之一。建完之后,我会详细拆解地图文件里那些YAML和PGM/PNG到底在说什么。
核心知识点:SLAM Toolbox建图流程 + 地图文件格式(YAML + PGM/PNG)深度解析
为什么选SLAM Toolbox?
市面上建图工具不少,比如gmapping、cartographer。我个人习惯用SLAM Toolbox,原因有三:
- 原生支持ROS2:不用折腾各种依赖,装好就能用。
- 支持在线/离线建图:你可以一边遥控机器人一边建,也可以录好bag包回来慢慢优化。
- 自带地图保存功能:建完图直接调用服务就能保存,不用自己写脚本。
我在项目里遇到过好几次,用gmapping建图到一半程序崩了,地图没保存,白干。换了SLAM Toolbox之后,这种糟心事少了很多。
建图前的准备工作
别急着跑命令,先检查三件事:
- 机器人能正常发布/odom和/scan话题。没有里程计和激光数据,SLAM就是无米之炊。
- 激光雷达的安装高度和角度正确。我见过有人把雷达装歪了,建出来的地图全是斜的。
- 控制方式就绪。你可以用键盘、手柄或者写好的导航节点来控制机器人移动。
小技巧:建图前最好让机器人原地转几圈,让SLAM初始化好位姿。不然一上来就猛冲,地图容易飘。
启动SLAM Toolbox建图
假设你的机器人已经准备好了,打开终端,启动SLAM Toolbox:
ros2 launch slam_toolbox online_async_launch.py
这条命令会启动异步SLAM模式。说白了,就是一边接收激光数据一边建图,不会阻塞你的控制指令。
然后,你需要控制机器人把整个环境走一遍。我个人习惯先走外围,再走内部,最后补一些死角。你想想看,如果只走中间,墙角的信息就丢了。
注意:建图过程中不要急转弯,也不要走太快。SLAM算法需要连续的数据来匹配特征点。我曾经为了省时间,推着机器人快跑,结果地图直接扭曲了,重来一遍更费时间。
保存地图
走完一圈,感觉地图差不多了,就可以保存了。SLAM Toolbox提供了一个服务接口:
ros2 service call /slam_toolbox/save_map slam_toolbox/srv/SaveMap "{name: 'my_map'}"
执行后,你会在当前目录下看到两个文件:
- my_map.yaml:地图的元数据文件
- my_map.pgm:地图的像素图像文件
有些版本也会生成PNG格式,但PGM更常见。PGM是灰度图,每个像素的值代表该位置被占用的概率。
地图格式深度解析
好,地图建好了,咱们来拆开看看里面到底有什么。
1. YAML文件
用文本编辑器打开my_map.yaml,你会看到类似这样的内容:
image: my_map.pgm
resolution: 0.05
origin: [-10.0, -10.0, 0.0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196
我来逐行解释:
| 字段 | 含义 | 我的经验 |
|---|---|---|
| image | 指向像素地图文件的路径 | 建议用相对路径,方便移植 |
| resolution | 每个像素代表的实际距离(米/像素) | 0.05米是常用值,精度够用,文件也不太大 |
| origin | 地图左下角在世界坐标系中的位置 [x, y, yaw] | 这个值决定了地图的偏移量,建图时如果机器人起点不在原点,这里会变 |
| negate | 是否反转像素值(0或1) | 一般保持0,除非你的地图颜色是反的 |
| occupied_thresh | 像素值大于此阈值视为障碍物 | 默认0.65,我一般不动 |
| free_thresh | 像素值小于此阈值视为自由空间 | 默认0.196,介于两者之间的视为未知区域 |
重点:occupied_thresh和free_thresh决定了地图的“软硬”程度。如果你发现机器人老往墙上撞,可以适当调高occupied_thresh;如果地图上很多噪点被当成障碍,可以调低free_thresh。
2. PGM/PNG文件
PGM文件是灰度图,每个像素的值范围是0~255。在Nav2里:
- 0:完全自由(白色)
- 100:未知区域(灰色)
- 255:完全占用(黑色)
嗯,这里要注意:PGM文件本身不直接存概率,而是存灰度值。Nav2加载时会根据YAML里的阈值来判定每个格子是障碍、自由还是未知。
说白了,地图文件就是一张图片,YAML文件就是这张图片的说明书。两者缺一不可。
地图坐标系与像素坐标的转换
这个知识点容易让人迷糊,我简单说一下。
假设地图的origin是[-10, -10, 0],resolution是0.05。那么地图中第i行第j列的像素,对应的世界坐标是:
world_x = origin_x + j * resolution
world_y = origin_y + (height - i) * resolution
注意:图像的行是从上往下数的,而世界坐标的y是朝上的,所以要用height - i来翻转。
我在调试定位时经常手动算这个,确保地图原点跟机器人实际位置对得上。有一次怎么都对不上,最后发现是origin写错了,折腾了我一下午。
避坑指南
最后,分享几个我踩过的坑:
- 地图文件路径不对:YAML里的image路径是相对于YAML文件本身的。如果你把地图挪了位置,记得改路径。
- 分辨率不匹配:建图时用0.05,导航时如果用了0.1的地图,定位会不准。保持一致性。
- 地图边界有黑边:建图时如果机器人没走到边界,地图边缘会有大片的未知区域(灰色)。Nav2会把未知区域当成障碍,导致路径规划失败。解决办法是建图时尽量走遍每个角落。
我的习惯:建完图后,我会用图像查看器打开PGM文件,肉眼检查一遍。如果看到不该有的黑点或白点,说明建图过程中有干扰,需要重来。
本章知识体系
为了让你更直观地理解本章内容,我画了一张流程图:
这张图把整个流程串起来了。从启动工具到控制移动,再到保存文件,最后生成两个核心文件。你只要按这个流程走,基本不会出错。