4. 代价地图(Costmap)深度解析:全局代价地图与局部代价地图的配置,膨胀层、障碍物层、静态层的作用与参数调优

代价地图,说白了就是机器人对周围环境的“风险评分图”。每个栅格都有一个代价值,值越高,机器人越不敢走。我刚开始接触 Nav2 时,觉得这玩意儿就是个简单的黑白图——能走和不能走。后来被现实狠狠教育了一顿,才发现里面的门道深着呢。

今天咱们就把代价地图拆开揉碎,从全局到局部,从三层核心插件到参数调优,一次性讲透。

核心认知: 代价地图不是一张静态图片,而是一个实时更新的“风险热力图”。它由多个层叠加而成,每一层负责一种信息来源。
代价地图三层架构与数据流 静态地图层 Static Layer 障碍物层 Obstacle Layer 膨胀层 Inflation Layer 层叠加(Layer Stacking) 静态层 + 障碍物层 + 膨胀层 → 最终代价地图 每个栅格取各层最大值(max模式) 全局代价地图 Global Costmap 用于全局路径规划 局部代价地图 Local Costmap 用于局部避障与轨迹优化 输出:带代价的栅格地图 → 规划器/控制器使用 代价值范围:0(自由)→ 255(致命障碍)

4.1 全局代价地图 vs 局部代价地图

这两个东西,名字像,但职责完全不同。我见过不少新手把它们混为一谈,结果调参调得怀疑人生。

对比维度 全局代价地图 局部代价地图
作用范围 整个已知地图 机器人周围(通常 3-5 米)
更新频率 低(几秒一次或手动触发) 高(每个控制周期更新)
分辨率 可低一些(0.05-0.1m) 通常较高(0.02-0.05m)
主要用途 全局路径规划(A* / Dijkstra) 局部轨迹规划(DWA / TEB)
滚动窗口 否,固定地图 是,跟随机器人移动

我个人习惯把全局代价地图想象成“战略地图”,局部代价地图则是“战术地图”。战略上要看到全局,但不用太精细;战术上必须看清脚下,反应要快。

我的经验: 全局代价地图的分辨率不要设得太高。0.05m 已经够用,再高只会增加计算量,对路径质量提升有限。局部代价地图倒是可以设到 0.025m,尤其是当机器人需要穿过窄门时。

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:代价衰减因子。值越大,代价随距离衰减越快

这两个参数怎么调?我一般这样操作:

  1. 先把 inflation_radius 设为机器人半径 + 0.1m 的余量
  2. 然后调 cost_scaling_factor,默认 10.0。如果机器人太靠近障碍物,就降低这个值(让代价衰减更平缓)
  3. 如果机器人路径太保守、绕远路,就适当减小 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 是否够高,以及传感器数据是否正常发布。
我曾经遇到的一个诡异问题: 机器人每次经过某个区域都会突然绕大圈。排查了半天,发现是那个区域的地图上有几个“幽灵障碍物”——SLAM 建图时留下的噪点。解决方案:在静态层中把 map_topic 换成经过滤波处理的地图,或者在障碍物层中增加 filter 插件。

代价地图的调参,说白了就是平衡“安全”和“效率”。太保守,机器人走不动;太激进,容易撞。我的建议是:先让机器人能安全跑起来,再一点点放开参数,直到找到那个平衡点。