第1章:代价地图(Costmap)——静态层、障碍物层、膨胀层的原理与配置

大家好,欢迎来到《机器人局部路径规划与避障策略实战》的第一章。

今天咱们聊聊代价地图。嗯,这玩意儿是机器人导航的基石。你想想看,机器人要走路,它得知道哪里能走、哪里不能走。代价地图就是干这个的。

我个人习惯把代价地图比作一张「热力图」。越红的地方越危险,越绿的地方越安全。机器人规划路径时,会尽量走绿色的区域,绕着红色的地方走。

1.1 代价地图是什么?

说白了,代价地图就是一个二维网格。每个格子(我们叫它cell)都有一个数值,代表这个位置的风险程度。

  • 0:绝对安全,随便走
  • 1-99:有风险,数值越大越危险
  • 100:致命障碍,绝对不能走

我在项目中遇到过一个问题:有次机器人死活绕不过一个柱子,后来发现是代价地图的膨胀半径设得太小了。机器人以为自己能挤过去,结果卡住了。嗯,这就是代价地图没配好的典型例子。

1.2 三层结构:静态层、障碍物层、膨胀层

ROS的代价地图是分层的。为什么要分层?因为不同来源的信息更新频率不一样。你想想看,墙壁是固定的,但行人会动。如果混在一起处理,效率会很低。

1.2.1 静态层(Static Layer)

静态层,顾名思义,就是那些不会动的东西。比如墙壁、柱子、家具。这些信息通常来自预先建好的地图(比如SLAM建出来的栅格地图)。

配置参数很简单:

static_layer:
  enabled: true
  map_topic: "/map"
  first_map_only: false
  subscribe_to_updates: true

这里有个坑:first_map_only 如果设为 true,地图就只加载一次。如果后面地图更新了,它不会重新订阅。我建议设成 false,除非你确定地图永远不会变。

1.2.2 障碍物层(Obstacle Layer)

障碍物层处理的是动态障碍物。比如行人、其他机器人、突然出现的箱子。它通过传感器数据(激光雷达、深度相机)实时更新。

配置示例:

obstacle_layer:
  enabled: true
  observation_sources: laser_scan_sensor
  laser_scan_sensor:
    sensor_frame: base_laser
    data_type: LaserScan
    topic: /scan
    marking: true
    clearing: true
    min_obstacle_height: 0.0
    max_obstacle_height: 2.0

我曾经踩过一个坑:min_obstacle_heightmax_obstacle_height 没设对。结果机器人把地面上的反光点当成了障碍物,原地打转。后来我把最小高度设成了0.1米,问题就解决了。

1.2.3 膨胀层(Inflation Layer)

膨胀层是干嘛的?它把障碍物「吹大」一圈。为什么要这么做?因为机器人有体积,不能贴着障碍物走。膨胀层就是给机器人留出安全距离。

核心参数就两个:

参数 说明 建议值
inflation_radius 膨胀半径,影响范围 机器人半径的1.5倍
cost_scaling_factor 代价衰减速度 10.0(默认)

你想想看,inflation_radius 设得太大,机器人会绕远路;设得太小,容易撞墙。我个人习惯是先设成机器人半径的1.5倍,然后根据实际效果微调。

核心要点:膨胀层不是真的把障碍物变大了,而是给障碍物周围的格子赋值。离障碍物越近,代价越高。这样路径规划算法就会自动避开。

1.3 三层如何协同工作?

这三层是叠加的。最终代价地图 = 静态层 + 障碍物层 + 膨胀层。

举个例子:

  1. 静态层告诉你「这里有一堵墙」
  2. 障碍物层告诉你「墙前面有个人」
  3. 膨胀层告诉你「墙和人周围1米内别靠近」

机器人看到的就是一张完整的风险地图。

小技巧:调试时可以用 rvizCostmap 显示插件。把三层分别显示出来,看看哪一层出了问题。我调试时经常这么干,一眼就能看出问题在哪。

1.4 配置实战:一个完整的代价地图配置

下面是一个完整的YAML配置,我直接贴出来:

global_costmap:
  global_frame: map
  robot_base_frame: base_footprint
  update_frequency: 5.0
  publish_frequency: 2.0
  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:
  global_frame: odom
  robot_base_frame: base_footprint
  update_frequency: 10.0
  publish_frequency: 5.0
  static_map: false
  rolling_window: true
  width: 6.0
  height: 6.0
  resolution: 0.05
  plugins:
    - {name: obstacle_layer, type: "costmap_2d::ObstacleLayer"}
    - {name: inflation_layer, type: "costmap_2d::InflationLayer"}

注意看,全局代价地图用了静态层,局部代价地图没用。因为局部地图是滚动窗口,不需要全局地图。嗯,这里要记住:局部地图只关心周围几米的情况。

警告:局部代价地图的 rolling_window 如果设为 true,必须设置 widthheight。我见过有人忘了设,结果地图大小变成0,机器人直接罢工。

1.5 避坑指南

最后分享几个我踩过的坑:

  • 地图更新频率太高:我曾经把 update_frequency 设成50Hz,结果CPU跑满,机器人卡成PPT。后来改成10Hz,一切正常。
  • 传感器帧不对sensor_frame 必须和TF树里的一致。否则障碍物位置会偏移,机器人会对着空气绕圈。
  • 膨胀半径和机器人尺寸不匹配:如果机器人是圆形,膨胀半径设成半径的1.2倍就够了。如果是矩形,要按对角线算。

好了,这一章就到这里。代价地图是路径规划的基础,理解透了三层结构,后面学路径规划算法会轻松很多。下一章我们聊聊全局路径规划,到时候见。