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 四种地图的对比与选型
说了这么多,到底该用哪种?我个人的经验是:
- 做路径规划 → 栅格地图(简单、可靠)
- 做三维重建 → 点云地图(精度高、细节丰富)
- 做大范围导航 → 拓扑地图(轻量、高效)
- 做人机交互 → 语义地图(智能、可理解)
当然,实际项目中往往是混合使用。比如我最近做的一个机械臂项目,底层用栅格地图做避障,上层用拓扑地图做全局规划,再叠加语义地图做任务理解。三种地图各司其职,配合得很好。
最后说一句:地图只是工具,不是目的。别为了用某种地图而用某种地图,先想清楚你的机器人要干什么,再选合适的地图。这个顺序,千万别搞反了。