4、代价地图参数:膨胀层、障碍物层、静态地图层与更新频率

代价地图,说白了就是机器人对周围环境的“风险认知图”。它把激光雷达、深度相机这些传感器数据,加上我们事先画好的静态地图,融合成一张带权重的网格图。每个格子都有一个代价值,值越高,机器人就越不敢靠近。

我刚开始调导航的时候,总觉得代价地图是个黑盒子。后来踩的坑多了,才明白它的三个核心层——膨胀层、障碍物层、静态地图层——各自管什么事,参数该怎么调。今天咱们就把这三层掰开揉碎,再聊聊更新频率这个容易被忽略的“隐形杀手”。

核心观点:代价地图不是一张图,而是三层图的叠加。每一层的参数都会直接影响机器人的避障行为和路径质量。

代价地图三层叠加结构 膨胀层(Inflation Layer) 对障碍物进行膨胀,生成安全缓冲区 障碍物层(Obstacle Layer) 实时传感器数据标记障碍物 静态地图层(Static Map Layer) 预先建好的SLAM地图 叠加规则 1. 取各层最大值 2. 静态地图为基底 3. 障碍物层实时更新 4. 膨胀层最后叠加 5. 最终代价值 = max(各层值) 最终代价地图 = 静态地图层 + 障碍物层 + 膨胀层(逐层叠加,取最大值)

4.1 静态地图层:你的地图就是地基

静态地图层,就是咱们用SLAM建好的那张地图。它告诉机器人:哪些地方是墙,哪些地方是空地,哪些地方是未知区域。这张图一般不会变,除非你重新建图。

我个人习惯把静态地图层看作“底线”。不管传感器怎么变,墙的位置是固定的。这个层的参数不多,但有一个特别关键——map_topic,它决定了代价地图订阅哪个话题的地图数据。

参数名 默认值 说明
map_topic /map 静态地图的话题名称
subscribe_to_updates false 是否订阅地图更新(一般不用)
track_unknown_space false 是否将未知区域视为障碍物
lethal_cost_threshold 100 地图中多少灰度值算致命障碍

我的经验:track_unknown_space 这个参数,我建议在室内场景设为 false。如果设为 true,机器人会把没探索过的区域也当成障碍,路径规划会变得非常保守,甚至卡在门口出不去。

4.2 障碍物层:实时感知的“眼睛”

障碍物层负责把激光雷达、深度相机这些传感器的数据实时投影到代价地图上。说白了,它告诉机器人:“你左边有个箱子,前面有个人,别撞上去。”

这个层的参数最多,也最容易调出问题。我遇到过最典型的坑是:激光雷达数据频率太高,障碍物层更新不过来,导致地图出现“鬼影”——明明障碍物已经移走了,地图上还留着它的痕迹。

核心参数一览

参数名 默认值 作用
observation_sources 空 定义传感器源,如 laser_scan_sensor
~source/marking true 是否在代价地图上标记障碍物
~source/clearing true 是否清除已消失的障碍物
~source/data_type LaserScan 传感器数据类型
~source/obstacle_range 2.5 障碍物最大检测距离(米)
~source/raytrace_range 3.0 射线追踪清除障碍物的距离(米)
max_obstacle_height 2.0 障碍物最大高度(米)
min_obstacle_height 0.0 障碍物最小高度(米)

注意:obstacle_range 和 raytrace_range 这两个参数,我见过很多人设成一样的值。其实 raytrace_range 应该比 obstacle_range 大 0.5~1.0 米,这样才能保证障碍物移走后,地图能及时清除残留的标记。

多传感器配置示例

如果你同时用了激光雷达和深度相机,可以这样配置:

observation_sources: laser_scan_sensor depth_camera_sensor

laser_scan_sensor:
  sensor_frame: laser_frame
  data_type: LaserScan
  topic: /scan
  marking: true
  clearing: true
  obstacle_range: 2.5
  raytrace_range: 3.0

depth_camera_sensor:
  sensor_frame: camera_depth_frame
  data_type: PointCloud2
  topic: /camera/depth/points
  marking: true
  clearing: true
  obstacle_range: 1.5
  raytrace_range: 2.0

嗯,这里要注意:深度相机的 obstacle_range 我一般设得比激光雷达小。因为深度相机在远距离时精度下降很快,设太大反而会引入噪声。

4.3 膨胀层:给机器人留点“面子”

膨胀层,说白了就是把障碍物“吹胖”一圈。为什么要这么做?因为机器人不是质点,它有自己的尺寸。如果不膨胀,路径规划可能会让机器人贴着墙走,结果一转弯就卡住。

膨胀层的核心参数就两个:膨胀半径和代价缩放因子。但就是这两个参数,能玩出花来。

参数名 默认值 说明
inflation_radius 0.55 膨胀半径(米),一般设为机器人半径的1.2~1.5倍
cost_scaling_factor 10.0 代价衰减速度,值越大衰减越快

调参口诀:膨胀半径决定“能不能过”,代价缩放因子决定“愿不愿意过”。半径太小容易撞,半径太大过不了窄门。缩放因子太大,机器人贴着障碍物走;太小,机器人绕大圈。

我记得有一次在仓库项目里,机器人要穿过一个只有0.7米宽的通道。机器人本体宽0.5米,按理说能过。但膨胀半径设了0.55米,结果代价地图上通道被堵死了。后来我把膨胀半径降到0.35米,同时把cost_scaling_factor从10.0调到15.0,让代价衰减更快,机器人就顺利通过了。

代价衰减曲线

膨胀层生成的代价值不是均匀的,而是从障碍物边缘向外指数衰减。公式大概是这样:

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

其中 distance 是当前格子到障碍物的距离,inscribed_radius 是机器人内切圆半径。cost_scaling_factor 越大,代价下降越快,机器人就越敢靠近障碍物。

4.4 更新频率:别让地图“卡顿”

更新频率这个参数,很多人不重视。但我要说,它可能是影响导航稳定性的“隐形杀手”。

代价地图有两个更新频率:

  • update_frequency:代价地图整体更新的频率(Hz)
  • publish_frequency:代价地图发布到可视化话题的频率(Hz)

我见过一个案例:有人把 update_frequency 设成 20Hz,但激光雷达只有 10Hz。结果代价地图一直在“空转”,CPU 占用率飙升,机器人反而反应变慢。其实 update_frequency 跟传感器频率匹配就好,一般 5~10Hz 就够用。

场景 推荐 update_frequency 推荐 publish_frequency
室内低速(0.3m/s) 5 Hz 2 Hz
室内中速(0.6m/s) 10 Hz 5 Hz
室外动态场景 15 Hz 10 Hz
多传感器融合 10 Hz 5 Hz

避坑指南:我曾经把 publish_frequency 设得比 update_frequency 还高,结果 RViz 上看到的地图一直在“闪烁”,因为可视化数据比实际地图更新得快,显示的是中间状态。记住:publish_frequency 不要超过 update_frequency。

4.5 三层协同:一个完整的配置示例

最后,我给大家一个我常用的完整配置模板。这个配置在室内轮式机器人上跑过几百公里,稳定性不错。

global_costmap:
  global_frame: map
  robot_base_frame: base_footprint
  update_frequency: 5.0
  publish_frequency: 2.0
  static_map: true
  rolling_window: false
  plugins:
    - {name: static_layer, type: "costmap_2d::StaticLayer"}
    - {name: obstacle_layer, type: "costmap_2d::ObstacleLayer"}
    - {name: inflation_layer, type: "costmap_2d::InflationLayer"}

local_costmap:
  global_frame: odom
  robot_base_frame: base_footprint
  update_frequency: 10.0
  publish_frequency: 5.0
  static_map: false
  rolling_window: true
  width: 4.0
  height: 4.0
  resolution: 0.05
  plugins:
    - {name: obstacle_layer, type: "costmap_2d::ObstacleLayer"}
    - {name: inflation_layer, type: "costmap_2d::InflationLayer"}

obstacle_layer:
  observation_sources: laser_scan_sensor
  laser_scan_sensor:
    sensor_frame: laser_frame
    data_type: LaserScan
    topic: /scan
    marking: true
    clearing: true
    obstacle_range: 2.5
    raytrace_range: 3.0

inflation_layer:
  inflation_radius: 0.4
  cost_scaling_factor: 12.0

你看,全局代价地图用了静态地图层,局部代价地图没用。因为局部规划器只关心机器人周围4米×4米的范围,用滚动窗口就够了,不需要全局地图。这样能省不少计算资源。

我的习惯:每次改完参数,我都会在 RViz 里打开代价地图的可视化,看看膨胀层是不是把通道堵死了,障碍物层有没有漏掉东西。肉眼观察比看日志快多了。

代价地图的参数调优,说白了就是找平衡。静态地图层给你基础,障碍物层给你实时性,膨胀层给你安全余量。更新频率则决定了这三层能不能协调工作。调好了,机器人走起来行云流水;调不好,它要么撞墙,要么原地转圈。