4. 环境表示方法:栅格地图、点云地图、拓扑地图、语义地图

做机器人这么多年,我越来越觉得——地图就是机器人的「眼睛」和「记忆」。没有地图,机械臂就是个盲人,连自己站在哪都不知道,更别提规划路径了。

但地图不是只有一种。你想想看,不同的任务需要不同的地图。扫地机器人要的是栅格地图,自动驾驶要的是点云地图,仓库搬运要的是拓扑地图,而人机协作——嗯,语义地图才是王道。

这一章,我就带你把这四种地图掰开揉碎,讲清楚它们各自适合什么场景,又有什么坑。

核心观点:没有最好的地图,只有最合适的地图。选对地图类型,比调参更重要。

环境表示方法 栅格地图 点云地图 拓扑地图 语义地图 四种地图各有千秋,选型看任务需求

4.1 栅格地图(Occupancy Grid Map)

栅格地图,说白了就是把空间切成一个个小格子。每个格子要么是「空闲」,要么是「占用」,要么是「未知」。就像下围棋一样,棋盘上的每个交叉点都有明确的状态。

我个人习惯用栅格地图做路径规划,因为它简单、直观、好算。A*算法、Dijkstra算法,都是基于栅格地图跑的。

我的经验:栅格地图的分辨率很关键。分辨率太高,内存爆炸;分辨率太低,路径不精确。我一般设 5cm/格,室内场景够用了。

举个例子,一个 10m×10m 的房间,用 5cm 分辨率,就是 200×200 = 40000 个格子。每个格子存一个字节,也就 40KB 左右。嗯,这点内存,嵌入式设备完全扛得住。

// 栅格地图的简单表示(C语言风格)
typedef struct {
    int width;      // 格子数(宽)
    int height;     // 格子数(高)
    float resolution; // 每个格子代表的实际尺寸(米)
    uint8_t *data;  // 0=空闲, 100=占用, 255=未知
} OccupancyGrid;

// 坐标转换:世界坐标 → 栅格坐标
int worldToGrid(float x, float resolution) {
    return (int)(x / resolution);
}

不过要注意,栅格地图有个硬伤——它假设每个格子是独立的。实际上,一个格子里的物体可能只有一半,但栅格地图只能要么全占、要么全空。这就会带来误差。

避坑指南:我曾经在一个项目中,因为栅格分辨率设得太粗(20cm),导致机械臂抓取时总是差那么几厘米。后来改成 2cm 分辨率,问题就解决了。代价是内存涨了 100 倍——嗯,这就是 trade-off。

4.2 点云地图

点云地图,就是一堆三维点的集合。每个点有 x、y、z 坐标,有时候还带颜色或强度信息。激光雷达扫出来的就是点云。

说实话,点云地图看起来最「像」真实世界。你想想看,一个房间里的桌子、椅子、墙壁,在点云里都能看到轮廓。但问题也来了——数据量太大了。

一个 64 线激光雷达,每秒能产生 130 万个点。存一帧还行,存整个地图?分分钟几个 GB。

地图类型 数据量(10m×10m) 精度 适用场景
栅格地图 ~40 KB 中等(取决于分辨率) 路径规划、导航
点云地图 ~50 MB 高 三维重建、定位
拓扑地图 ~1 KB 低(只保留拓扑关系) 大范围路径规划
语义地图 ~100 KB 中(带语义标签) 人机交互、任务规划

点云地图最大的用处是定位。我记得有一次做机械臂的自主导航,用点云地图做 ICP(迭代最近点)匹配,定位精度能达到毫米级。但代价是计算量大,跑在工控机上还行,嵌入式就吃力了。

实用技巧:点云地图可以降采样。用体素滤波器(Voxel Grid Filter),把 1cm³ 内的点合并成一个,数据量能减少 90% 以上,精度损失却很小。

4.3 拓扑地图

拓扑地图,说白了就是一张「地铁图」。它不关心具体的几何形状,只关心「哪些地方能到哪些地方」。节点代表位置,边代表路径。

我建议在大范围场景用拓扑地图。比如一个工厂有 100 个工位,你不需要知道每个工位的精确坐标,只需要知道「从 A 工位到 B 工位,经过 C 走廊」就够了。

拓扑地图的好处是轻量。一个节点就存一个 ID 和几个邻居的指针,内存消耗几乎可以忽略不计。

// 拓扑地图的简单表示
class TopologicalNode {
public:
    int id;                          // 节点ID
    std::vector<int> neighbors;      // 邻居节点ID列表
    std::vector<float> edgeCosts;    // 到邻居的代价(距离/时间)
};

class TopologicalMap {
public:
    std::map<int, TopologicalNode> nodes;
    
    // 添加节点
    void addNode(int id) {
        nodes[id] = TopologicalNode{id, {}, {}};
    }
    
    // 添加边
    void addEdge(int from, int to, float cost) {
        nodes[from].neighbors.push_back(to);
        nodes[from].edgeCosts.push_back(cost);
    }
};

不过拓扑地图有个缺点——它不包含几何信息。如果你需要精确避障,拓扑地图帮不了你。它只能告诉你「走哪条路」,不能告诉你「路上有没有障碍物」。

我的做法:实际项目中,我经常把拓扑地图和栅格地图结合起来。拓扑地图做全局规划,栅格地图做局部避障。这样既快又准。

4.4 语义地图

语义地图,是这几年比较火的方向。它不只是告诉机器人「这里有障碍物」,而是告诉机器人「这里有一张桌子」。

你想想看,如果机械臂知道面前是一张桌子,它就知道「桌面上可以放东西」「桌腿不能撞」「桌角要小心」。这些信息,栅格地图和点云地图都给不了。

语义地图的构建,通常需要深度学习。用目标检测网络识别出物体,然后给地图中的对应区域打上标签。

核心思路:语义地图 = 几何信息 + 语义标签。几何信息告诉机器人「在哪」,语义标签告诉机器人「这是什么」。

我记得有一次做家庭服务机器人,用户说「把杯子放到餐桌上」。如果没有语义地图,机器人得先找到杯子,再找到餐桌,然后规划路径。有了语义地图,机器人直接查地图——「杯子在厨房台面上,餐桌在餐厅中央」,路径规划就快多了。

但语义地图也有坑。深度学习模型不是 100% 准确的,万一它把椅子识别成桌子,那机器人就乱套了。

避坑指南:我曾经遇到过语义地图中把垃圾桶识别成「圆柱形物体」,结果机械臂试图抓取垃圾桶——嗯,场面一度很尴尬。后来我加了置信度阈值,低于 0.8 的标签不写入地图,才解决了这个问题。

4.5 四种地图的对比与选型

说了这么多,到底该用哪种?我个人的经验是:

  • 做路径规划 → 栅格地图(简单、可靠)
  • 做三维重建 → 点云地图(精度高、细节丰富)
  • 做大范围导航 → 拓扑地图(轻量、高效)
  • 做人机交互 → 语义地图(智能、可理解)

当然,实际项目中往往是混合使用。比如我最近做的一个机械臂项目,底层用栅格地图做避障,上层用拓扑地图做全局规划,再叠加语义地图做任务理解。三种地图各司其职,配合得很好。

最后说一句:地图只是工具,不是目的。别为了用某种地图而用某种地图,先想清楚你的机器人要干什么,再选合适的地图。这个顺序,千万别搞反了。

可可精品资料库