3、Costmap调试:全局/局部代价地图可视化、膨胀层参数调优、障碍物层传感器融合问题排查

代价地图(Costmap)是导航系统的“眼睛”。它告诉机器人哪里能走、哪里不能走。说白了,全局代价地图负责宏观路径规划,局部代价地图负责微观避障。这两张图要是出了问题,机器人要么撞墙,要么原地打转。

我个人习惯,调试导航的第一步就是打开代价地图可视化。别急着调参数,先看看地图长什么样。很多时候,问题一眼就能看出来。

3.1 代价地图可视化:先看再调

ROS2 里,代价地图默认会发布可视化话题。你只需要在 RViz2 里添加对应显示类型就行。

  • 全局代价地图:话题名通常是 /global_costmap/costmap,显示类型选 Map。
  • 局部代价地图:话题名是 /local_costmap/costmap,同样用 Map 显示。
  • 膨胀层:话题名 /global_costmap/inflated_costmap 或 /local_costmap/inflated_costmap,显示类型也是 Map。
  • 障碍物层:话题名 /global_costmap/obstacle_layer 或 /local_costmap/obstacle_layer,显示类型 Map。

嗯,这里要注意:如果你看不到任何地图,先检查话题是否存在。用 ros2 topic list | grep costmap 看一眼。如果话题都没有,那就是代价地图节点没启动,或者配置错了。

我的小技巧:在 RViz2 里同时打开全局和局部代价地图,把全局地图的透明度调到 0.5,局部地图透明度 0.8。这样能一眼看出两张图的重叠区域是否一致。我曾经遇到过全局地图和局部地图的坐标系对不上的问题,就是靠这个发现的。

3.2 膨胀层参数调优:别让机器人变成“胆小鬼”

膨胀层的作用,是把障碍物“扩大”一圈。这样机器人规划路径时,会主动避开障碍物边缘,而不是贴着走。但膨胀半径设得太大,机器人会变成“胆小鬼”——明明能过去的地方,它偏说过不去。

膨胀层的核心参数就几个,我列个表给你看:

参数名 默认值 作用 我的建议
inflation_radius 0.55 m 膨胀半径,障碍物向外扩展的距离 根据机器人尺寸调。小车 0.3-0.5m,大车 0.6-1.0m
cost_scaling_factor 10.0 代价衰减系数,值越大衰减越快 默认 10 通常够用。如果路径太贴墙,可以降到 5-8
inflate_unknown false 是否对未知区域也做膨胀 建议设为 false,否则地图边缘会变成禁区

你想想看,inflation_radius 设成 0.55 米,意味着机器人中心距离障碍物至少要保持 0.55 米。如果你的机器人半径只有 0.2 米,那它离障碍物还有 0.35 米的余量,其实挺安全的。但如果你在狭窄走廊里,这个余量可能就太大了,机器人会卡在中间不敢动。

调参口诀:
先调 inflation_radius,再调 cost_scaling_factor。
半径决定“能不能过”,衰减系数决定“贴着走还是绕着走”。

我曾经在一个仓库项目里,机器人死活过不了一个 0.8 米宽的门口。明明车宽才 0.5 米,就是过不去。后来一看,inflation_radius 设了 0.6 米,加上车体半径 0.25 米,总共需要 0.85 米的空间。门口才 0.8 米,当然过不去。把膨胀半径降到 0.3 米,问题立刻解决。

3.3 障碍物层传感器融合:多传感器打架怎么办?

大多数机器人不止一个传感器。激光雷达、深度相机、声纳……它们各自上报障碍物信息。障碍物层的任务,就是把这些数据融合到一张代价地图里。

但融合过程经常出问题。我遇到过最典型的情况是:激光雷达扫到了桌腿,深度相机也扫到了同一根桌腿,结果代价地图上出现了“双重障碍物”——明明只有一个障碍物,地图上却显示两个,而且位置还有偏差。

为什么会这样?说白了,就是传感器之间的坐标变换(TF)没对齐。两个传感器各自发布障碍物,但它们的坐标系关系不对,导致同一个物体被映射到了地图上的不同位置。

排查步骤我总结了一下:

  1. 检查 TF 树:用 ros2 run tf2_tools view_frames 生成 TF 树 PDF,确认所有传感器坐标系都正确连接到 base_link。
  2. 查看传感器话题时间戳:用 ros2 topic echo /scan --once 和 ros2 topic echo /depth/points --once,对比时间戳是否同步。时间差超过 100ms 就可能出问题。
  3. 可视化传感器数据:在 RViz2 里同时显示 LaserScan 和 PointCloud2,看它们是否重合。如果不重合,那就是 TF 或标定问题。
  4. 检查障碍物层配置:打开障碍物层的 YAML 配置文件,确认 observation_sources 列表里包含了所有传感器,并且每个传感器的 topic、sensor_frame、data_type 都正确。
避坑指南:我曾经在调试时发现,深度相机的点云数据量太大,导致代价地图更新频率跟不上。机器人已经撞上障碍物了,地图还没更新。解决方案是给深度相机加上 voxel_filter,降低点云密度。在配置里加一行 obstacle_layer.voxel_filter.enabled: true 就行。

3.4 传感器融合配置示例

下面是一个典型的障碍物层配置,同时使用激光雷达和深度相机:

obstacle_layer:
  enabled: true
  observation_sources: laser_scan_sensor depth_sensor
  
  laser_scan_sensor:
    topic: /scan
    sensor_frame: laser_frame
    data_type: LaserScan
    clearing: true
    marking: true
    inf_is_valid: false
  
  depth_sensor:
    topic: /depth/points
    sensor_frame: camera_depth_frame
    data_type: PointCloud2
    clearing: true
    marking: true
    min_obstacle_height: 0.0
    max_obstacle_height: 2.0
    voxel_filter: true
    voxel_size: 0.05

注意看 clearing 和 marking 这两个参数。marking 表示这个传感器负责“标记”障碍物,clearing 表示它负责“清除”障碍物。如果一个传感器只标记不清除,那障碍物被移走后,地图上还会残留痕迹。我建议所有传感器都同时开启 clearing 和 marking,除非你有特殊需求。

3.5 知识体系总览

下面这张图总结了代价地图调试的核心逻辑:

代价地图调试知识体系 代价地图调试核心逻辑 全局代价地图 局部代价地图 静态地图层 障碍物层 膨胀层 可视化检查 话题存在?坐标系正确? 传感器融合 TF对齐?时间同步? 膨胀参数调优 半径 vs 衰减系数 调试顺序:先可视化 → 再传感器融合 → 最后调膨胀参数 不要跳过任何一步,否则你可能在错误的方向上浪费时间

3.6 常见问题速查表

最后,我整理了一份常见问题速查表。调试时遇到问题,先对照这张表看看:

现象 可能原因 排查方法 解决方案
代价地图全黑 没有收到传感器数据 ros2 topic echo /scan 看是否有数据 检查传感器驱动是否启动
障碍物位置偏移 TF 树错误 view_frames 查看 TF 树 修正 TF 发布源
机器人不敢走窄道 膨胀半径太大 测量通道宽度,对比膨胀半径+车体半径 减小 inflation_radius
路径贴着障碍物走 膨胀半径太小或衰减系数太大 可视化膨胀层,看代价分布 增大 inflation_radius 或减小 cost_scaling_factor
障碍物移走后地图还有残留 传感器未开启 clearing 检查障碍物层配置 在配置中添加 clearing: true
代价地图更新卡顿 传感器数据量太大 查看 CPU 占用率 启用 voxel_filter 或降低传感器频率

代价地图调试,说白了就是“看图说话”。你看到的图不对,那路径规划肯定不对。别急着改代码,先打开 RViz2,把每一层地图都看一遍。很多时候,问题就藏在那些你忽略的细节里。