4、代价地图参数:膨胀层、障碍物层、静态地图层与更新频率
代价地图,说白了就是机器人对周围环境的“风险认知图”。它把激光雷达、深度相机这些传感器数据,加上我们事先画好的静态地图,融合成一张带权重的网格图。每个格子都有一个代价值,值越高,机器人就越不敢靠近。
我刚开始调导航的时候,总觉得代价地图是个黑盒子。后来踩的坑多了,才明白它的三个核心层——膨胀层、障碍物层、静态地图层——各自管什么事,参数该怎么调。今天咱们就把这三层掰开揉碎,再聊聊更新频率这个容易被忽略的“隐形杀手”。
核心观点:代价地图不是一张图,而是三层图的叠加。每一层的参数都会直接影响机器人的避障行为和路径质量。
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 里打开代价地图的可视化,看看膨胀层是不是把通道堵死了,障碍物层有没有漏掉东西。肉眼观察比看日志快多了。
代价地图的参数调优,说白了就是找平衡。静态地图层给你基础,障碍物层给你实时性,膨胀层给你安全余量。更新频率则决定了这三层能不能协调工作。调好了,机器人走起来行云流水;调不好,它要么撞墙,要么原地转圈。