第三章:地图服务与代价地图

各位同学好,我是你们的导航实战讲师。今天我们来聊聊导航里最核心的数据结构——代价地图。说实话,我见过太多人在导航参数上栽跟头,最后发现是代价地图没配好。这一章,我会把地图数据类型、三个核心图层以及参数调优的坑,一次性讲清楚。

3.1 地图数据类型:OccupancyGrid

先说说地图长什么样。在ROS2里,地图的数据类型叫nav_msgs::msg::OccupancyGrid。说白了,它就是一个二维数组,每个格子存一个值,表示这个格子被占用的概率。

值的范围是0到100:

  • 0:完全空闲,可以走
  • 100:完全被占,不能走
  • -1:未知区域,没探测到

嗯,这里要注意:-1这个值特别容易坑人。我刚开始做项目时,以为未知区域就是空闲,结果机器人直接往墙里钻。后来才明白,未知区域要当作障碍物处理才安全。

地图的元信息也很关键:

# 地图元信息示例
origin: [-10.0, -10.0, 0.0]  # 地图左下角坐标
resolution: 0.05              # 每个格子5厘米
width: 400                    # 宽度400格
height: 400                   # 高度400格

分辨率选多少?我个人习惯室内用0.05米,室外用0.1米。分辨率太高,计算量爆炸;太低,机器人过不了窄门。

3.2 静态地图层

静态地图层,就是加载你事先建好的SLAM地图。它不会动态变化,除非你重新加载。

配置起来很简单:

static_layer:
  map_topic: "/map"
  subscribe_to_updates: true
  map_subscribe_transient_local: true

这里有个坑:subscribe_to_updates要不要开?我建议开着。为什么呢?因为有些场景下,你可能会通过地图服务器动态更新地图(比如有人手动修正了地图)。不开的话,更新了你也看不到。

还有map_subscribe_transient_local,这个参数的意思是:订阅时如果地图还没发布,就等一等。嗯,这个一定要设成true,否则启动顺序不对时,代价地图就收不到地图数据了。

3.3 障碍物层

障碍物层负责处理传感器数据。激光雷达、深度相机、声呐,这些数据都会汇到这里。

核心配置:

obstacle_layer:
  observation_sources: laser_scan_sensor
  laser_scan_sensor:
    topic: /scan
    sensor_frame: laser_frame
    data_type: LaserScan
    clearing: true
    marking: true
    inf_is_valid: true

我来解释几个关键参数:

  • clearing:是否清除障碍物。比如障碍物移走了,传感器扫不到,就应该把原来的障碍物标记清除。
  • marking:是否标记障碍物。检测到障碍物就标记为占用。
  • inf_is_valid:无限远距离是否有效。有些激光雷达在测不到时返回inf,这个参数要设成true,否则会报错。

我在项目中遇到过一个问题:机器人明明看到障碍物了,但就是不避障。查了半天,发现是clearing和marking都设成了false。说白了,你既不让它标记障碍物,又不让它清除,那这层就废了。

3.4 膨胀层

膨胀层,就是把障碍物往外扩一圈。为什么要扩?因为机器人有体积,你不能让机器人的中心点贴着障碍物走。

膨胀半径怎么算?我一般这样算:

inflation_layer:
  inflation_radius: 0.55  # 机器人半径 + 安全余量
  cost_scaling_factor: 10.0

inflation_radius设成机器人半径加上0.1~0.2米的安全余量。比如机器人半径0.35米,我就设0.55米。

cost_scaling_factor这个参数很有意思。它控制代价从障碍物边缘到膨胀边界的衰减速度。值越大,衰减越快,路径越靠近障碍物。值越小,衰减越慢,路径越远离障碍物。

我曾经在一个仓库项目里,把cost_scaling_factor设成了5.0,结果机器人绕路绕得离谱。后来改成10.0,路径就合理多了。你想想看,这个值其实就是个权衡:安全 vs 效率。

3.5 代价地图配置与参数调优

好了,三个图层都讲完了。现在把它们组合起来,就是完整的代价地图配置。

一个典型的配置长这样:

global_costmap:
  global_frame: map
  robot_base_frame: base_footprint
  update_frequency: 1.0
  publish_frequency: 1.0
  width: 20.0
  height: 20.0
  resolution: 0.05
  plugins:
    - {name: static_layer, type: "nav2_costmap_2d::StaticLayer"}
    - {name: inflation_layer, type: "nav2_costmap_2d::InflationLayer"}

local_costmap:
  global_frame: odom
  robot_base_frame: base_footprint
  update_frequency: 5.0
  publish_frequency: 2.0
  width: 3.0
  height: 3.0
  resolution: 0.05
  plugins:
    - {name: obstacle_layer, type: "nav2_costmap_2d::ObstacleLayer"}
    - {name: inflation_layer, type: "nav2_costmap_2d::InflationLayer"}

注意看,全局代价地图和局部代价地图的配置不一样:

  • 全局地图用map坐标系,更新频率低(1Hz),范围大(20米)
  • 局部地图用odom坐标系,更新频率高(5Hz),范围小(3米)

为什么会这样?因为全局规划不需要实时更新,但需要知道整个地图。局部规划需要快速响应,但只看机器人周围几米就够了。

参数调优的几个要点:

参数 推荐值 说明
update_frequency 全局1~2Hz,局部5~10Hz 太高CPU吃不住,太低反应慢
resolution 室内0.05,室外0.1 分辨率越高,计算量越大
inflation_radius 机器人半径+0.1~0.2米 太小容易撞,太大绕路
cost_scaling_factor 8.0~12.0 根据场景微调

核心原则:代价地图的配置没有银弹。每个项目都要根据机器人尺寸、传感器类型、运行环境来调。我建议你先用默认参数跑起来,然后观察机器人的行为,再逐步微调。

小技巧:调试时可以用rviz2打开代价地图的可视化。红色区域是障碍物,蓝色区域是膨胀层。如果机器人路径贴着红色走,说明膨胀半径太小;如果绕大圈,说明膨胀半径太大。

警告:千万不要把inflation_radius设成0。我曾经犯过这个错,结果机器人直接往墙上怼。膨胀层是安全底线,必须保留。

3.6 知识体系总览

最后,我用一张图把本章的知识点串起来。这张图展示了地图数据、三个图层以及它们之间的关系:

代价地图知识体系 OccupancyGrid 0=空闲 100=占用 -1=未知 静态地图层 加载SLAM地图 不动态变化 障碍物层 处理传感器数据 动态标记/清除 膨胀层 障碍物向外扩张 保证安全距离 代价地图(Costmap) 全局代价地图 + 局部代价地图 路径规划与导航 A* / DWA 等算法

这张图很直观:地图数据是基础,三个图层各司其职,最终合成代价地图,供路径规划算法使用。你配置的时候,要确保每个图层都正常工作,缺一个都不行。

好了,这一章就到这里。记住,代价地图是导航的基石,配好了事半功倍,配不好寸步难行。下一章我们讲路径规划算法,到时候你会看到代价地图是怎么被用起来的。

可可精品资料库