4、代价地图配置:全局代价地图与局部代价地图、膨胀层参数、障碍物层与静态层

代价地图,说白了就是机器人对周围环境的「风险认知图」。你想想看,机器人要导航,它得知道哪里能走、哪里不能走、哪里最好绕着走。代价地图就是干这个的。

ROS 里把代价地图分成了两种:全局代价地图和局部代价地图。我刚开始接触时也觉得麻烦,后来才明白——这俩分工完全不同。

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

全局代价地图,负责「宏观规划」。它覆盖整个已知环境,分辨率可以低一些,更新频率也不用太快。全局路径规划器(比如 A*、Dijkstra)就在这张图上找出一条从起点到终点的最优路径。

局部代价地图,负责「微观避障」。它只关注机器人周围一小块区域(通常是 3-5 米半径),分辨率高,更新快。局部规划器(比如 DWA、TEB)在这张图上实时调整速度,避开突然出现的障碍物。

我做过一个项目,在仓库里跑 AGV。一开始我把全局地图分辨率设得很高,结果规划一次路径要算好几秒,机器人走走停停。后来我明白了——全局地图不需要那么精细,够用就行。

对比项 全局代价地图 局部代价地图
覆盖范围 整个已知地图 机器人周围(3-5m)
分辨率 较低(0.05-0.1m) 较高(0.02-0.05m)
更新频率 低(1-5Hz) 高(10-50Hz)
主要用途 全局路径规划 局部避障与速度控制
数据来源 静态地图 + 长期障碍物 实时传感器 + 短期障碍物
我的习惯:全局地图分辨率设 0.05m,局部地图设 0.025m。如果环境特别拥挤,局部分辨率可以再提到 0.02m,但别低于 0.015m,否则计算量会爆炸。

4.2 代价地图的三层结构

代价地图不是一张简单的灰度图。它由三层叠加而成:静态层、障碍物层、膨胀层。每一层都有自己的职责。

我画了张图,帮你理清这三层的关系:

代价地图三层结构 静态层(Static Layer) 数据来源:预先建立的地图(SLAM 生成) 内容:墙壁、固定家具、永久结构 障碍物层(Obstacle Layer) 数据来源:激光雷达、深度相机、声纳 内容:动态障碍物、行人、临时物体 膨胀层(Inflation Layer) 作用:在障碍物周围生成梯度代价 目的:让机器人远离障碍物,避免碰撞

这三层叠加在一起,最终生成一张完整的代价地图。静态层提供基础环境信息,障碍物层实时更新动态物体,膨胀层给所有障碍物加上「安全距离」。

4.3 静态层配置

静态层是最简单的一层。它直接从预先建立的地图(通常是 pgm/yaml 文件)加载数据。配置时主要关注两个参数:

  • map_topic:地图话题名称,默认是 /map
  • first_map_only:是否只加载第一张地图。设为 true 可以避免地图被后续数据覆盖

我建议把 first_map_only 设为 true。有一次我在调试时忘了设这个,结果机器人跑着跑着,静态地图被传感器数据覆盖了,导航直接崩了。

静态层典型配置:
static_layer:
  enabled: true
  map_topic: "/map"
  first_map_only: true
  unknown_cost_value: 0
  lethal_cost_threshold: 100

4.4 障碍物层配置

障碍物层是实时避障的核心。它订阅传感器数据,把检测到的障碍物标记到代价地图上。

配置时要注意几个关键参数:

参数 默认值 说明
observation_sources 空 传感器列表,如 "laser_scan_sensor"
clearing true 是否清除传感器视野内的旧障碍物
marking true 是否标记传感器检测到的新障碍物
obstacle_range 2.5 障碍物标记的最大距离(米)
raytrace_range 3.0 障碍物清除的最大距离(米)

这里有个坑:obstacle_range 和 raytrace_range 的差值。如果 raytrace_range 比 obstacle_range 大太多,传感器视野边缘的障碍物会被错误清除。我建议差值控制在 0.5m 以内。

我曾经踩过的坑:有一次我把 obstacle_range 设成 5m,raytrace_range 设成 6m。结果机器人把远处的墙壁当成「已清除」,直接撞上去了。后来我把差值缩到 0.5m,问题解决。

4.5 膨胀层参数详解

膨胀层,说白了就是给障碍物「加一圈安全区」。它把障碍物周围的代价从 0 逐渐增加到 255(致命代价),让规划器主动避开。

膨胀层有三个核心参数:

  • inflation_radius:膨胀半径(米)。障碍物周围这个半径内的区域都会产生代价
  • cost_scaling_factor:代价衰减因子。值越大,代价衰减越快
  • inflation_layer:是否启用膨胀层

代价的计算公式是这样的:

cost = 255 * exp(-(distance - inscribed_radius) * cost_scaling_factor)

其中 distance 是当前点到障碍物的距离,inscribed_radius 是机器人内切圆半径。

我一般这样调:

  1. 先把 inflation_radius 设成机器人半径的 1.5 倍
  2. 再把 cost_scaling_factor 设成 10.0
  3. 如果机器人离障碍物太近,增大 cost_scaling_factor
  4. 如果路径太绕,减小 inflation_radius
我的经验:在狭窄走廊里,inflation_radius 设太大机器人会卡住。我一般设 0.3-0.5m,配合 cost_scaling_factor 10-15,效果不错。

4.6 参数调优实战

调参不是一蹴而就的。我总结了一套流程,你可以试试:

  1. 先调静态层:确保地图加载正确,没有空洞或错位
  2. 再调障碍物层:让机器人原地转一圈,看传感器数据是否正确映射到代价地图
  3. 最后调膨胀层:让机器人走一段直路,观察它离障碍物的距离

调参时可以用 rviz 实时查看代价地图。把 /move_base/global_costmap/costmap 和 /move_base/local_costmap/costmap 话题显示出来,颜色越红代表代价越高。

完整配置示例(global_costmap_params.yaml):
global_costmap:
  global_frame: map
  robot_base_frame: base_footprint
  update_frequency: 5.0
  publish_frequency: 2.0
  width: 20.0
  height: 20.0
  resolution: 0.05
  static_map: true
  plugins:
    - {name: static_layer, type: "costmap_2d::StaticLayer"}
    - {name: obstacle_layer, type: "costmap_2d::ObstacleLayer"}
    - {name: inflation_layer, type: "costmap_2d::InflationLayer"}
完整配置示例(local_costmap_params.yaml):
local_costmap:
  global_frame: odom
  robot_base_frame: base_footprint
  update_frequency: 20.0
  publish_frequency: 10.0
  width: 4.0
  height: 4.0
  resolution: 0.025
  static_map: false
  rolling_window: true
  plugins:
    - {name: obstacle_layer, type: "costmap_2d::ObstacleLayer"}
    - {name: inflation_layer, type: "costmap_2d::InflationLayer"}

注意看,全局代价地图用了 static_map: true,局部代价地图用了 rolling_window: true。这就是我前面说的——全局地图覆盖整个环境,局部地图跟着机器人走。

嗯,代价地图的配置就这些。记住一个原则:全局地图求稳,局部地图求快。稳是指地图稳定、不频繁更新;快是指响应快、能及时避障。把握好这个度,你的导航系统就不会出大问题。