地图服务与代价地图:从配置到实战
各位同学,欢迎来到第二章。这一章我们聊聊地图和代价地图。说实话,很多新手在 Nav2 里栽跟头,十有八九是代价地图没配好。机器人要么撞墙,要么原地转圈,要么路径诡异得像喝醉了酒。
我个人习惯把代价地图比作机器人的「安全距离感」。你想想看,一个人闭着眼走路,靠的是触觉和听觉。机器人呢?它靠的就是这张代价地图。地图服务器告诉它「世界长什么样」,代价地图告诉它「哪里能走、哪里危险、哪里要绕开」。
好,我们一个一个拆开讲。
2.1 地图服务器 (map_server) 配置
地图服务器说白了就干一件事:把存好的地图文件加载进来,发布给导航系统。它本身不复杂,但坑不少。
先看一个典型的 map_server 启动配置:
# map_server.yaml
map_server:
ros__parameters:
yaml_filename: "my_map.yaml"
use_sim_time: False
对应的地图 YAML 文件长这样:
# my_map.yaml
image: my_map.pgm
resolution: 0.05
origin: [-10.0, -10.0, 0.0]
occupied_thresh: 0.65
free_thresh: 0.196
negate: 0
这里有几个参数,我重点说一下:
| 参数 | 说明 | 我的建议 |
|---|---|---|
| resolution | 每个像素代表的实际距离(米) | 室内常用 0.05,室外可以放宽到 0.1 |
| origin | 地图左下角在世界坐标系中的位置 [x, y, yaw] | 这个最容易出错,我踩过坑 |
| occupied_thresh | 像素值大于此阈值视为障碍物 | 0.65 是安全值,别乱改 |
| free_thresh | 像素值小于此阈值视为自由空间 | 0.196 是经验值,够用 |
rviz2 看一眼地图原点是不是在合理位置。如果地图左下角坐标是 [-10, -10],那原点就在地图中心偏左下,别搞反了。
2.2 静态代价地图 (static_layer)
静态层,就是那张「不会变」的地图。它把 map_server 发布的地图数据转成代价地图。说白了,墙就是墙,门就是门,这些信息不会因为你机器人动一下就改变。
配置示例:
# static_layer.yaml
static_layer:
plugin: "nav2_costmap_2d::StaticLayer"
map_subscribe_transient_local: True
enabled: True
lethal_cost_threshold: 100
first_map_only: False
关键参数:
- lethal_cost_threshold:地图中像素值超过多少就视为「致命障碍」。默认 100,意思是地图里标记为障碍的像素,代价直接拉满。
- first_map_only:设为 True 的话,只加载第一张地图,后面地图更新就不管了。我一般设 False,万一你后面要动态更新地图呢?
- map_subscribe_transient_local:这个参数在 ROS2 里很重要。设为 True 可以保证订阅者晚到也能拿到地图数据。我建议一直开着。
lethal_cost_threshold 是不是设得太低了。我遇到过有人设成 50,结果地图上稍微有点灰的像素都被当成墙了。
2.3 障碍物代价地图 (obstacle_layer)
这才是动态世界的核心。激光雷达扫到的行人、突然出现的纸箱、临时摆放的锥桶——这些信息都靠 obstacle_layer 来更新。
配置示例:
# obstacle_layer.yaml
obstacle_layer:
plugin: "nav2_costmap_2d::ObstacleLayer"
enabled: True
observation_sources: scan
scan:
topic: /scan
sensor_frame: laser_frame
data_type: LaserScan
marking: True
clearing: True
min_obstacle_height: 0.0
max_obstacle_height: 2.0
obstacle_range: 2.5
raytrace_range: 3.0
这里我重点讲几个容易搞混的参数:
| 参数 | 作用 | 我的经验 |
|---|---|---|
| marking | 是否用该传感器标记障碍物 | 一般设为 True,除非你只用它来清除 |
| clearing | 是否用该传感器清除障碍物 | 必须 True,否则障碍物会「阴魂不散」 |
| obstacle_range | 多远距离内的点才被视为障碍物 | 2.5 米够用,太远容易误报 |
| raytrace_range | 多远距离内的射线用于清除障碍物 | 比 obstacle_range 大 0.5 米,这是标准做法 |
| min/max_obstacle_height | 只考虑这个高度范围内的障碍物 | 地面机器人一般设 0.0~2.0,别把天花板算进去 |
🔑 核心逻辑: marking 和 clearing 是一对兄弟。marking 负责「画」障碍物,clearing 负责「擦」障碍物。如果只 marking 不 clearing,机器人走过的地方会留下「鬼影」——明明障碍物已经没了,代价地图上还留着。我见过一个项目,机器人总是在同一个地方急刹车,排查了半天才发现是 clearing 没开。
2.4 膨胀层 (inflation_layer)
膨胀层,就是给障碍物「穿衣服」。机器人不是质点,它有尺寸。膨胀层的作用就是告诉规划器:「别靠太近,会撞到」。
配置示例:
# inflation_layer.yaml
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
enabled: True
inflation_radius: 0.55
cost_scaling_factor: 10.0
这两个参数是调参的重灾区:
- inflation_radius:膨胀半径(米)。机器人离障碍物多远就开始「感到危险」。一般设为机器人半径的 1.2~1.5 倍。
- cost_scaling_factor:代价衰减系数。值越大,代价衰减越快;值越小,代价影响范围越广。
我直接给个经验公式:
inflation_radius = 机器人半径 × 1.3
cost_scaling_factor = 10.0 # 起步值,后面微调
2.5 三层协作:一张图看懂
下面这张图展示了三个层如何协同工作:
流程很清楚:地图服务器提供底图 → 静态层加载固定障碍物 → 传感器实时更新障碍物层 → 两层合并成主代价地图 → 膨胀层加上安全距离 → 最终给规划器用。
2.6 调参实战建议
最后,我总结几条实战经验:
- 先调静态层,再调动态层。 静态层是基础,静态层没调好,后面都是白搭。
- 膨胀半径从机器人半径的 1.2 倍开始。 太大会堵死通道,太小会撞墙。
- obstacle_range 别设太大。 3 米以内足够,太远容易把远处的噪声当成障碍物。
- 用 rviz2 实时观察代价地图。 我调试时永远开着代价地图的可视化,一眼就能看出问题。
- 每次只改一个参数。 改完跑一遍,看效果。别一次改三个参数,出了问题你都不知道是哪个引起的。
💡 我的调试口诀: 静态层看墙对不对,障碍物层看人跟不跟,膨胀层看缝过不过。三关都过了,导航基本就稳了。
嗯,这一章的内容就到这。地图和代价地图是导航的「地基」,地基打牢了,后面的路径规划才能跑得稳。各位同学,动手试试吧。