返回专辑
·Johan·4 分钟阅读

Nav2 Costmap 层:世界观错了就不要拧控制器

层职责单一、膨胀与足迹一致;先证明地图诚实再调跟踪。

Nav2 Costmap 层:世界观错了就不要拧控制器

1. 残影封窄门时,降 max_vel 没用

Planner 报无解、控制器在门洞前画圈——RViz 里 local_costmap 仍有一圈橙色残影,把 90 cm 门封成 60 cm。团队第一反应是降 controller_serverFollowPath.max_vel_x;通道仍过不去,因为世界观错了。Costmap 是 Nav2 唯一的世界模型:层叠顺序、障碍持久化、膨胀与足迹共同决定「门是否存在」。控制器再强,也只能在错误的栅格里找路。

2. 层职责与插件顺序

Nav2 costmap 是插件层叠加,典型顺序:静态层 → 障碍层 →(可选)体素层 → 膨胀层。每层只做一件事:

  • StaticLayer:加载 map_server 的 PGM/YAML,提供先验占用。
  • ObstacleLayer / VoxelLayer:订阅 /scan 或点云,标记与清除障碍;observation_sources 决定传感器列表。
  • InflationLayer:把占用格向外扩散代价,inflation_radiuscost_scaling_factor 决定势场形状。

重复启用同类层(两个 ObstacleLayer 吃同一 /scan)会双计代价,窄道凭空变 fatal。plugins 数组顺序即合成顺序——改顺序等于改语义,必须带截图验收。

yaml
local_costmap:
  ros__parameters:
    plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
    inflation_layer:
      plugin: "nav2_costmap_2d::InflationLayer"
      inflation_radius: 0.55
      cost_scaling_factor: 3.0

3. 膨胀与足迹必须同审

膨胀不是装饰,是把占用距离变换成碰撞代价。有效通过宽度近似:

footprintnav2_costmap_2d 里用 [[x,y], ...] 多边形定义;膨胀半径单独调大而不改 footprint,会出现「RViz 看起来能过、控制器判 fatal」的分裂。global_costmaplocal_costmap 的膨胀可不同:全局保守(绕路)、局部略紧(贴障跟踪),但二者 footprint 必须一致。

4. 残影的时间学

残影常来自四类根因,调 obstacle_max_range 或缩短持久时间只能掩盖:

  1. stamp 错误:驱动用到达时间而非采集时间,clearing 射线几何与真实观测错位。
  2. TF 跳变map→odom 跳变瞬间,旧障碍投影到新位姿留下幽灵格。
  3. clearing 不匹配:激光 raytrace_max_range 小于 obstacle_max_range,标记远、清近,残影累积。
  4. 双计:静态层已有墙,障碍层又标记同一反射点。

诊断顺序:ros2 topic echo /scan --field header.stamp 对比 /tf 最新 stamp;RViz 逐层开关看残影来自哪一层。修 TF 前后残影面积应下降——这是回归袋,不是手感。

5. global vs local 分工

global_costmaplocal_costmap
范围全图或大范围机器人周围滚动窗口
更新可较慢(1–5 Hz)须匹配控制频率(≥10 Hz)
用途全局规划拓扑实时避障与局部跟踪

局部窗口半径应大于制动距离 。高速下窗口太小,障碍刚进窗口就来不及减速——这是几何问题,不是 critics 权重问题。

6. 参数变更的验收

  • RViz 分层可视化:静态/障碍/膨胀/合并四层截图,附 PR。
  • 窄门袋:同一初始位姿重复 20 次,记录通过/无解/碰撞,改参数前后对比。
  • 玻璃/反光场景袋:残影面积量化(占用格计数),不是「看起来还行」。
  • 膨胀与 footprint 同审:改 inflation_radius 必须同步检查 footprint 多边形。
  • 无地图证据的「跟踪优化」PR 打回——先证明地图诚实。

代价地图诚实,控制器才是最后一公里;地图撒谎时,所有增益调整都只是在噪声上作画。

7. observation_sources 与 clearing 几何

ObstacleLayer 的 observation_sources 列出传感器名,每个 source 独立配置 topicmax_obstacle_heightraytrace_max_rangeobstacle_max_range。常见错误:obstacle_max_range: 3.0 标记三米内障碍,但 raytrace_max_range: 2.0 只清除两米内——第三米格永不清除,形成环形残影。两者应相等或 raytrace 更大。inf_is_valid: false 时 NaN/Inf 点云会被丢弃;玻璃后的无效点若未滤,会随机标记。

yaml
obstacle_layer:
  observation_sources: scan
  scan:
    topic: /scan
    max_obstacle_height: 2.0
    clearing: true
    marking: true
    raytrace_max_range: 4.0
    obstacle_max_range: 3.5

8. 案例:双 ObstacleLayer 封死通道

某项目 local 与 global 各配 ObstacleLayer,又共用 voxel_layer 订阅同一点云——窄门通过率从 90% 跌到 40%。RViz 只看合并层,看不出双计;逐层关闭后障碍层代价翻倍才定位。合并为单一 ObstacleLayer + 独立 inflation 配置后,控制器参数未动,通过率恢复。层职责单一,比多拧三个 critic 权重有效。

← 全部文章

johan's blog