2. 环境感知与地图表示:栅格地图、拓扑地图、点云地图、语义地图

各位同学,咱们接着聊。上一节我们把传感器和感知算法过了一遍,现在手里有了原始数据——激光点云、图像、里程计信息。但这些东西,机器人看不懂啊。

它需要一种「地图」——说白了,就是让机器人理解周围环境的数据结构。我这些年做项目,换过好几种地图表示方式,每种都有它的脾气。今天我把四种主流地图掰开揉碎讲给你听。

2.1 栅格地图(Occupancy Grid Map)

这是最直观的一种。想象一下,你把地面切成一个个小方格,每个格子要么是「空闲」,要么是「占用」,要么是「未知」。这就是栅格地图。

我个人习惯用 Bayesian 更新 来处理每个格子的概率。每次激光打回来,我就更新对应格子的置信度。公式不复杂:

P(occupied | z) = P(z | occupied) * P(occupied) / P(z)

嗯,这里要注意:栅格大小很关键。我见过有人把格子设成 5cm,结果地图大得离谱,内存直接爆了。一般室内机器人用 10-20cm 就够了。

优点:

  • 实现简单,新手友好
  • 适合避障和局部路径规划
  • 容易融合多传感器数据

缺点:

  • 大场景下内存爆炸
  • 没有语义信息,就是一堆格子

我的经验:做扫地机器人时,我用了双层栅格——局部用 5cm 高精度,全局用 20cm 粗粒度。既保证了避障精度,又控制了内存。你可以试试这个思路。

2.2 拓扑地图(Topological Map)

栅格地图太「笨重」了。你想想看,一个 1000 平米的仓库,用 10cm 栅格就是 10 万个格子。机器人真的需要知道每个格子吗?

拓扑地图的思路是:只保留关键节点和连接关系。节点可以是走廊拐角、房间门口、电梯口。边就是路径。

我曾经在一个物流仓库里用拓扑地图做导航。节点数不到 200 个,但覆盖了整个 5000 平米的区域。路径规划用 Dijkstra 算法,毫秒级出结果。

class TopoNode:
    def __init__(self, id, position, neighbors):
        self.id = id
        self.position = position  # (x, y)
        self.neighbors = neighbors  # [(neighbor_id, cost), ...]

class TopoMap:
    def __init__(self):
        self.nodes = {}
    
    def add_node(self, node):
        self.nodes[node.id] = node
    
    def find_path(self, start_id, goal_id):
        # 用 Dijkstra 或 A* 搜索
        pass

避坑指南:我曾经在动态环境中吃过亏——拓扑地图更新节点连接关系太慢。后来我加了「动态边权重」,根据实时拥堵情况调整 cost。记住:拓扑地图不是静态的,要让它活起来。

2.3 点云地图(Point Cloud Map)

点云地图,说白了就是一堆三维点的集合。每个点有 (x, y, z) 坐标,可能还有强度值或颜色信息。

激光雷达扫一圈,几万到几十万个点就来了。直接存下来就是点云地图。但问题也来了——数据量太大。

我记得第一次做室外无人车,一公里路扫下来,点云文件 2 个 G。后来用了 体素滤波(Voxel Grid Filter),把 5cm 内的点合并成一个,数据量直接降到 1/10。

滤波方法 效果 适用场景
体素滤波 均匀降采样 通用场景
统计滤波 去除离群点 有噪声的传感器
半径滤波 保留密集区域 特征提取前

点云地图的用途:

  • 定位:ICP 匹配,算机器人位姿
  • 建图:SLAM 的核心输出
  • 障碍物检测:直接做碰撞检测

2.4 语义地图(Semantic Map)

这是目前最「高级」的地图表示。它不光告诉机器人「这里有障碍物」,还告诉它「这是一把椅子」、「这是一扇门」、「这是人行道」。

怎么做?把深度学习的目标检测结果融合到地图里。比如 YOLO 检测到「椅子」,我就把椅子对应的点云或栅格区域标记为「椅子」类别。

我去年做的一个项目,用语义地图实现了「找空椅子」的功能。机器人走到会议室,直接定位到没人坐的椅子位置。这在纯几何地图里根本做不到。

class SemanticCell:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.occupancy = 0.0  # 占用概率
        self.semantic_label = "unknown"  # 语义标签
        self.confidence = 0.0  # 置信度

# 更新语义信息
def update_semantic(cell, detection):
    if detection.confidence > 0.5:
        cell.semantic_label = detection.class_name
        cell.confidence = detection.confidence
        cell.occupancy = 1.0  # 有物体就标记为占用

我的建议:别一上来就搞全语义地图。先从「二分类」开始——只区分「可通行区域」和「障碍物」。等系统稳定了,再慢慢加入「门」、「楼梯」、「电梯」这些关键语义。步子大了容易扯着蛋。

2.5 四种地图怎么选?

你可能会问:那我到底用哪种?

我的经验是:没有银弹。实际项目中往往是混合使用。

  • 局部规划:用栅格地图,实时更新,精度高
  • 全局导航:用拓扑地图,轻量快速
  • 定位:用点云地图,精度高
  • 高级交互:用语义地图,理解环境

举个例子,我做的仓储机器人:底层用栅格地图做避障,上层用拓扑地图做路径规划,点云地图用来定位,语义地图用来识别货架和充电桩。四层叠加,各司其职。

最后提醒一句:地图表示不是越复杂越好。如果你的机器人只需要在走廊里跑直线,一个简单的拓扑地图就够了。别为了炫技把系统搞复杂,稳定才是王道。

好,这一节就到这儿。下一节我们讲「全局路径规划算法」,到时候会用到今天讲的地图表示。你先把这四种地图的优缺点记牢,后面用得上。