3. 地图服务:地图数据类型(OccupancyGrid)、地图服务器(map_server)、静态地图与动态地图

聊到导航,地图是绕不开的核心。没有地图,机器人就是个睁眼瞎。今天咱们就深入聊聊ROS2里的地图服务。说白了,就是搞清楚地图长什么样、怎么加载、怎么用。

3.1 地图数据类型:OccupancyGrid

在ROS2里,地图不是一张JPG图片,而是一种叫OccupancyGrid的消息类型。我刚开始接触时也纳闷,为什么不用图片?后来才明白,机器人需要的是“可通行性”数据,不是像素颜色。

OccupancyGrid本质上是一个二维网格。每个格子代表地图上的一小块区域。格子的值表示这个区域被占用的概率。0表示完全空闲,100表示完全被占用,-1表示未知区域。

核心字段:
  • header:包含时间戳和坐标系ID,通常为"map"
  • info:地图元信息,包括分辨率、宽度、高度、原点位置
  • data:一维数组,按行存储所有格子的占用值

举个例子,一个10x10米的地图,分辨率0.05米,那就有200x200=40000个格子。每个格子占1字节,整张地图也就40KB左右。嗯,这个数据量对嵌入式设备来说很友好。

我的经验:分辨率不是越高越好。0.05米是常用值,室内导航足够了。分辨率太高,地图文件会很大,处理也慢。我在一个仓库项目里试过0.01米分辨率,结果地图文件直接飙到几百MB,得不偿失。

3.2 地图服务器:map_server

map_server是ROS2官方提供的地图管理节点。它的工作很简单:把保存好的地图文件加载成OccupancyGrid消息,然后发布到/map话题上。

启动方式也很直接:

ros2 run nav2_map_server map_server --ros-args -p yaml_filename:=/path/to/map.yaml

等等,为什么参数是yaml文件?因为地图需要两个文件:一个图片文件(如.pgm或.png),一个yaml描述文件。yaml文件告诉系统图片的元数据。

一个典型的map.yaml长这样:

image: map.pgm
resolution: 0.05
origin: [-10.0, -10.0, 0.0]
occupied_thresh: 0.65
free_thresh: 0.196
negate: 0

这里要注意几个参数:

  • origin:地图左下角在真实世界中的坐标。很多人搞错这个,导致机器人定位偏移
  • occupied_thresh:像素值高于此阈值视为障碍物
  • free_thresh:像素值低于此阈值视为空闲区域
我曾经踩过的坑:有一次我换了张地图,忘记更新yaml里的origin参数。结果机器人一启动,定位直接飞到地图外面去了。排查了半天才发现是origin没改。所以每次换地图,第一件事就是检查yaml文件。

3.3 静态地图 vs 动态地图

静态地图,就是预先建好的、不会变化的地图。比如你办公室的布局图。一旦建好,基本不变。动态地图则是在运行过程中不断更新的地图,比如激光雷达实时扫描到的障碍物。

在Nav2里,这两者是怎么配合的?

静态地图由map_server加载,发布到/map话题。动态地图则由costmap_2d层来处理。Nav2的全局代价地图和局部代价地图,都是基于静态地图,再叠加动态障碍物信息生成的。

我画了一张图,帮你理清这个关系:

静态地图 map_server / 预建地图 动态地图 激光雷达 / 实时更新 代价地图 costmap_2d 层 路径规划器 静态地图层 动态障碍物层 融合后的代价地图

你看,静态地图提供基础环境信息,动态地图负责实时更新。两者缺一不可。静态地图保证了全局路径规划的稳定性,动态地图则让机器人能应对突发障碍。

3.4 实际使用中的注意事项

在实际项目中,我总结了几条经验:

  1. 地图坐标系必须统一:静态地图的坐标系通常是"map",里程计是"odom",机器人本体是"base_link"。这三个坐标系的关系必须正确,否则地图加载后定位会乱掉。
  2. 地图更新频率要合理:动态地图更新太快会消耗大量CPU,太慢又无法及时避障。我一般设置激光雷达的更新频率为10Hz,代价地图的更新频率为5Hz。
  3. 地图边界处理:机器人走到地图边界时,代价地图会失效。所以建图时一定要留出足够的边界区域,至少比实际工作区域大1-2米。
一个小技巧:如果你发现机器人总是撞到静态地图上没有的障碍物,比如临时摆放的箱子,可以调整obstacle_layer的参数。把observation_sources配置好,让激光雷达的数据能及时更新到代价地图上。

嗯,地图服务这部分内容就这些。记住一句话:地图是导航的基石,静态地图管全局,动态地图管局部。两者配合好了,导航就成功了一半。