几乎所有 SLAM 系统(gmapping、cartographer、hector……)最终都把地图存成一对文件:map.pgm + map.yaml。PGM 本身是一个比 PNG 还古老的极简图像格式——正因为简单,它成了机器人领域的事实标准。理解它的结构,你就能解释修图时遇到的大多数"玄学问题"。
一、Netpbm 家族与 PGM 定位
PGM 属于 Netpbm 图像家族:
- PBM(P1/P4):黑白二值图,每像素 1 bit
- PGM(P2/P5):灰度图,每像素 0~maxval
- PPM(P3/P6):彩色图,RGB 三通道
编号为奇数(P1/P2/P3)是 ASCII 文本编码,体积大但肉眼可读;偶数(P4/P5/P6)是二进制编码,紧凑高效。机器人地图几乎都使用 P5(二进制灰度)。
二、文件结构逐字节拆解
一个 P5 文件 = 魔数 + 宽度 + 高度 + 最大值 + 一个空白字符 + 原始灰度数据。用十六进制工具看 ROS 导出的地图,开头通常长这样:
P5
400 400
255
<此后是 400×400 字节的原始灰度数据>
P5:魔数,两个字节 + 换行400 400:宽 × 高(像素),空格分隔255:maxval,灰度上限(0~65535 一般取 255)- 数据段:行优先、自上而下,每像素 1 字节(maxval ≤ 255 时)
头部中 # 开头是注释行,可以出现多次;各字段之间用空白分隔,解析器需要容忍换行/空格混用。
三、ROS map_server 的占用约定
PGM 格式本身并不知道什么是"墙"。真正定义语义的是 ROS 的 map_server 与 nav2_map_server,其约定是:
| PGM 灰度值 | 占用概率 | 语义 |
|---|---|---|
| 0(黑) | ≈1.0 | 障碍物(occupied) |
| 254~255(近白) | ≈0 | 可自由通行(free) |
| 205 | −1 | 未知(no information) |
map_server 读取 YAML 时还有三个关键参数会改变这套映射:
mode: trinary(默认):按固定阈值把灰度量化为 0 / 0.65 / 1 三档negate: 0/1:是否反转黑白(建图源图有时是"白墙黑地",需要 negate=1)free_threshold: 0.196/occupied_threshold: 0.651:trinary 模式下的分档门限(对应灰度 50 与 166 附近)
四、YAML 元数据与坐标还原
PGM 只存像素,不存物理尺寸,必须靠 YAML 还原到米制坐标系:
image: map.pgm
mode: trinary
resolution: 0.05 # 每像素 5 厘米
origin: [-13.6, -5.4, 0] # 图像左上角像素在地图系中的 (x, y, 弧度)
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196
地图系中任一点 (x, y) 对应的像素坐标为:col = x/res + ox_px,row = 高度 − y/res + oy_px(图像 y 轴向下、地图 y 轴向上)。origin 记录的是左上角像素中心的地图坐标——这也是为什么 origin 常常是负数。
五、日常操作工具箱
在命令行生态里处理 PGM 非常轻松,作为在线编辑器的补充:
# PGM → PNG(方便在聊天软件里传阅)
convert map.pgm map.png # ImageMagick
pnmtopng map.pgm > map.png # Netpbm
# PNG → P5 PGM
convert map.png -colorspace Gray -compress none map.pgm 2>/dev/null
# 快速查看头部
head -c 20 map.pgm | xxd | head -2
# 批量查看分辨率
identify map.pgm
注意 ImageMagick 转出 PGM 时可能写成 P2 且行间带换行,个别老解析器只认 P5,遇到"能显示但加载失败"可以先用 pnmtopgm 规范化一遍。pgmXT 编辑器对 P2 / P5 两种头部都能识别,导出时统一写为标准 P5。
六、常见坑速查
- 地图上下翻转:图像坐标系与地图坐标系 y 方向相反,对位软件里"上下颠倒"通常是某环节多做了一次翻转
- 改完图机器人定位漂移:改动画布尺寸(增减像素)却没有同步改
origin - PNG 转来的地图整体偏灰:带 Alpha 通道的 PNG 转灰度时透明区被合成为灰色,被误判为未知/半占据
- maxval 不是 255:少见但合法,解析器忘记读 maxval 会导致整图阈值全错