4. 代价地图(Costmap)深度解析:全局代价地图与局部代价地图的配置,膨胀层、障碍物层、静态层的作用与参数调优
代价地图,说白了就是机器人对周围环境的“风险评分图”。每个栅格都有一个代价值,值越高,机器人越不敢走。我刚开始接触 Nav2 时,觉得这玩意儿就是个简单的黑白图——能走和不能走。后来被现实狠狠教育了一顿,才发现里面的门道深着呢。
今天咱们就把代价地图拆开揉碎,从全局到局部,从三层核心插件到参数调优,一次性讲透。
4.1 全局代价地图 vs 局部代价地图
这两个东西,名字像,但职责完全不同。我见过不少新手把它们混为一谈,结果调参调得怀疑人生。
| 对比维度 | 全局代价地图 | 局部代价地图 |
|---|---|---|
| 作用范围 | 整个已知地图 | 机器人周围(通常 3-5 米) |
| 更新频率 | 低(几秒一次或手动触发) | 高(每个控制周期更新) |
| 分辨率 | 可低一些(0.05-0.1m) | 通常较高(0.02-0.05m) |
| 主要用途 | 全局路径规划(A* / Dijkstra) | 局部轨迹规划(DWA / TEB) |
| 滚动窗口 | 否,固定地图 | 是,跟随机器人移动 |
我个人习惯把全局代价地图想象成“战略地图”,局部代价地图则是“战术地图”。战略上要看到全局,但不用太精细;战术上必须看清脚下,反应要快。
4.2 三层核心插件:静态层、障碍物层、膨胀层
代价地图的“层”机制,是 Nav2 最优雅的设计之一。每一层只负责一件事,最后叠加出最终结果。咱们一层一层说。
4.2.1 静态层(Static Layer)
静态层,说白了就是“先验地图”。它读取预先建好的 SLAM 地图(通常是 occupancy grid),把已知的墙壁、固定障碍物标记出来。
关键参数就几个:
- map_topic:地图话题名,默认
map - first_map_only:是否只加载第一张地图。设为
false时,地图更新会实时反映到代价地图上 - subscribe_to_updates:是否订阅地图更新。如果你在动态环境中,建议打开
嗯,这里要注意:静态层不是一成不变的。我记得有一次在仓库项目里,货架布局经常调整,我们就把 subscribe_to_updates 设为 true,配合一个地图更新服务,效果很好。
4.2.2 障碍物层(Obstacle Layer)
这是最“活”的一层。它从激光雷达、深度相机、声纳等传感器实时读取数据,把检测到的障碍物标记到代价地图上。
配置时,我建议重点关注这几个参数:
| 参数 | 说明 | 推荐值 |
|---|---|---|
observation_sources |
传感器源列表,如 scan、pointcloud |
根据实际传感器配置 |
obstacle_range |
障碍物最大检测距离 | 2.5 - 4.0 米 |
raytrace_range |
射线追踪清除距离(用于清除误报) | 3.0 - 5.0 米 |
track_unknown_space |
是否追踪未知区域 | true(推荐) |
obstacle_range 设得太大(比如 10 米),结果远处的噪点也被当成障碍物,机器人动不动就急刹车。后来我把它限制在 3 米以内,配合 raytrace_range 做清除,问题就解决了。
4.2.3 膨胀层(Inflation Layer)
膨胀层,就是给障碍物“加一圈安全距离”。它把障碍物周围的栅格按距离赋予不同的代价值,越靠近障碍物,代价值越高。
膨胀层的核心参数:
- inflation_radius:膨胀半径。机器人中心到障碍物的最小安全距离
- cost_scaling_factor:代价衰减因子。值越大,代价随距离衰减越快
这两个参数怎么调?我一般这样操作:
- 先把
inflation_radius设为机器人半径 + 0.1m 的余量 - 然后调
cost_scaling_factor,默认 10.0。如果机器人太靠近障碍物,就降低这个值(让代价衰减更平缓) - 如果机器人路径太保守、绕远路,就适当减小
inflation_radius
4.3 参数调优实战:一个典型配置
下面是我在一个差速轮机器人上用的配置,经过多次迭代,效果比较稳定。你可以拿它当起点,再根据实际情况微调。
# global_costmap_params.yaml
global_costmap:
global_frame: map
robot_base_frame: base_footprint
update_frequency: 1.0
publish_frequency: 0.5
resolution: 0.05
static_map: true
plugins:
- {name: static_layer, type: "nav2_costmap_2d::StaticLayer"}
- {name: inflation_layer, type: "nav2_costmap_2d::InflationLayer"}
inflation_layer:
inflation_radius: 0.55
cost_scaling_factor: 10.0
# local_costmap_params.yaml
local_costmap:
global_frame: odom
robot_base_frame: base_footprint
update_frequency: 5.0
publish_frequency: 2.0
resolution: 0.025
rolling_window: true
width: 4.0
height: 4.0
plugins:
- {name: obstacle_layer, type: "nav2_costmap_2d::ObstacleLayer"}
- {name: inflation_layer, type: "nav2_costmap_2d::InflationLayer"}
obstacle_layer:
observation_sources: scan
scan:
topic: /scan
max_obstacle_height: 2.0
clearing: true
marking: true
data_type: "LaserScan"
obstacle_range: 2.5
raytrace_range: 3.0
inflation_layer:
inflation_radius: 0.4
cost_scaling_factor: 8.0
你看,全局代价地图我用了静态层 + 膨胀层,没有障碍物层。为什么?因为全局规划不需要实时传感器数据,它只需要知道“哪里是墙”就够了。局部代价地图则用了障碍物层 + 膨胀层,实时响应周围动态障碍物。
width 和 height 不要设得太大。4x4 米对大多数室内机器人已经足够。设大了,计算量上去了,反应反而变慢。
4.4 常见问题与避坑指南
- 机器人贴着墙走: 检查
inflation_radius是不是太小了。我一般建议至少设到机器人半径的 1.2 倍。 - 机器人频繁急停: 大概率是障碍物层参数问题。试试减小
obstacle_range,或者增大raytrace_range来清除误报。 - 全局路径穿墙: 检查静态层是否加载了正确的地图,或者
first_map_only是否设为了false导致地图被覆盖。 - 局部代价地图不更新: 确认
update_frequency是否够高,以及传感器数据是否正常发布。
map_topic 换成经过滤波处理的地图,或者在障碍物层中增加 filter 插件。
代价地图的调参,说白了就是平衡“安全”和“效率”。太保守,机器人走不动;太激进,容易撞。我的建议是:先让机器人能安全跑起来,再一点点放开参数,直到找到那个平衡点。