3、环境建模:从栅格到八叉树

环境建模,说白了就是让机器人知道「我在哪,周围有什么」。

我刚开始做机器人那会儿,觉得这步挺简单的——不就是画个地图吗?后来踩了不少坑才明白,地图建不好,后面什么路径规划、避障全是白搭。今天咱们就聊聊几种主流的环境建模方法。

3.1 栅格地图(Grid Map)构建

栅格地图是最直观的一种方式。你把环境切成一个个小格子,每个格子要么是障碍物,要么是空地。

举个例子,一个 10m×10m 的房间,用 0.1m 的栅格分辨率,就是 100×100 = 10000 个格子。每个格子存一个值,0 表示空闲,1 表示占据。

核心要点:栅格大小决定了地图精度和内存消耗的平衡。我一般建议室内机器人用 0.05m,室外用 0.2m 起步。

代码实现其实很简单:

class GridMap:
    def __init__(self, width, height, resolution):
        self.width = width          # 格子数量(宽)
        self.height = height        # 格子数量(高)
        self.resolution = resolution # 每个格子的实际尺寸(米)
        self.data = [[0] * width for _ in range(height)]
    
    def world_to_grid(self, x, y):
        # 把真实世界坐标转成栅格索引
        gx = int(x / self.resolution)
        gy = int(y / self.resolution)
        return gx, gy
    
    def set_obstacle(self, x, y):
        gx, gy = self.world_to_grid(x, y)
        if 0 <= gx < self.width and 0 <= gy < self.height:
            self.data[gy][gx] = 1

嗯,这里要注意:栅格地图的索引顺序是 (行, 列),也就是 (y, x)。我见过不少新手在这上面栽跟头。

3.2 代价地图(Costmap)概念

光有栅格地图还不够。你想想看,机器人是有尺寸的,它不能贴着障碍物走。代价地图就是在栅格地图的基础上,给每个格子加一个「代价」值。

代价通常分几层:

  • 致命障碍:机器人中心不能进入的区域(比如墙)
  • 膨胀区域:靠近障碍物,代价较高(机器人边缘可能碰到)
  • 自由区域:代价为 0,随便走

我在项目中遇到过一个问题:代价地图的膨胀半径设得太小,机器人老是擦着墙走,稍微有点定位误差就撞上了。后来我改成机器人半径 + 5cm 的安全余量,才稳定下来。

个人经验:代价地图的膨胀半径不要小于机器人半径的 1.2 倍。如果你用的是差速轮,还要考虑转弯半径的影响。

3.3 占据栅格(Occupancy Grid)更新原理

这个知识点有点绕,我尽量说清楚。

机器人每扫一次激光雷达,都会得到一堆点云。这些点云告诉我们:这里有个障碍物。但激光是有噪声的,单次测量不可靠。怎么办?用概率。

每个格子存的不再是 0 或 1,而是一个概率值:

  • P=0.5:未知区域
  • P>0.5:越接近 1,越可能是障碍物
  • P<0.5:越接近 0,越可能是空闲

更新公式其实就一句话:

# 对数几率形式,避免概率值溢出
log_odds = log(p / (1 - p))

# 每次观测后更新
log_odds_new = log_odds_old + 观测值

# 观测值:击中障碍物 +0.85,穿过空闲区域 -0.4

为什么会这样设计?因为概率相乘容易下溢,用对数形式就变成了加法,稳定多了。

避坑指南:我曾经把更新步长设得太大,结果机器人扫了两圈地图就全白了(概率饱和)。建议更新步长控制在 ±0.5 以内,并且设置概率上下限(比如 0.1~0.9)。

3.4 八叉树地图(OctoMap)简介

栅格地图有个硬伤:内存消耗跟分辨率的三次方成正比。一个 100m×100m×10m 的空间,用 0.05m 分辨率,需要 8 亿个格子,光存数据就要 800MB。

八叉树地图就是来解决这个问题的。它的思路很简单:

  • 把空间递归分成 8 个立方体(八叉树节点)
  • 如果一个区域全是空地或全是障碍物,就不再细分
  • 只有边界区域才继续细分到最高分辨率

说白了,就是「该精细的地方精细,该粗糙的地方粗糙」。

我做过一个测试:同样一个室内场景,栅格地图用了 200MB,八叉树地图只用了 15MB,而且建图效果几乎一样。

核心优势:八叉树地图天然支持三维空间,而且可以动态更新。你想想看,如果房间里有人走动,八叉树只需要更新局部节点,而栅格地图要重算整个平面。

不过八叉树也有缺点:查询速度比栅格地图慢一点(需要遍历树结构),而且实现起来更复杂。我个人建议:二维场景用栅格地图,三维场景用八叉树地图。

知识体系总览

下面这张图把四种建模方法的关系梳理了一下:

环境建模方法对比 栅格地图 (Grid Map) • 二维平面,均匀网格 • 每个格子存 0/1 • 内存消耗大,但查询快 • 适合二维室内场景 代价地图 (Costmap) • 在栅格地图上叠加代价 • 考虑机器人尺寸膨胀 • 多层叠加(障碍+膨胀+边界) • 路径规划的核心输入 占据栅格 (Occupancy Grid) • 概率模型,抗噪声 • 对数几率更新公式 • 支持动态环境更新 • SLAM建图的标准方案 八叉树地图 (OctoMap) • 三维空间,递归细分 • 内存效率极高 • 支持概率更新 • 适合三维无人机/自动驾驶 叠加 概率化 三维化 选择建议:二维用栅格+代价地图,三维用八叉树,动态环境用占据栅格更新

好了,环境建模这块就聊到这儿。记住一句话:地图的质量决定了机器人能走多远。下一节咱们聊聊路径规划,那才是真正考验算法功底的地方。


公众号:蓝海资料掘金营,微信deep3321