环境建模基础:栅格地图、拓扑地图、几何地图的构建与表示方法
做路径规划这么多年,我越来越觉得一个道理:地图建不好,算法跑不了。你想想看,再牛的A*算法、再快的Dijkstra,如果地图本身有问题,那规划出来的路径基本就是废的。
今天咱们就来聊聊环境建模这件事。说白了,就是把真实世界变成机器人能理解的数据结构。我习惯把地图分成三大类:栅格地图、拓扑地图、几何地图。每种都有它的脾气,咱们一个一个说。
一、栅格地图:最直观,也最常用
栅格地图,其实就是把环境切成一个个小格子。每个格子要么是空地(0),要么是障碍物(1)。就这么简单。
我记得刚入行那会儿,带我的老工程师跟我说:「小张,你记住,栅格地图就是机器人的像素画。」后来我做了几个项目,发现这话还真贴切。
举个例子,一个10米×10米的房间,如果每个格子是0.1米,那就是100×100=10000个格子。代码实现起来特别直接:
# 栅格地图的简单表示
import numpy as np
# 创建一个10x10的栅格地图,0表示空地,1表示障碍物
grid_map = np.zeros((10, 10), dtype=int)
# 设置障碍物
grid_map[2:5, 3:7] = 1 # 一个矩形障碍物
grid_map[7, 2:9] = 1 # 一堵墙
print("栅格地图:")
print(grid_map)
# 检查某个格子是否可通行
def is_free(x, y):
return grid_map[x, y] == 0
栅格地图最大的好处是——简单。A*算法、Dijkstra算法直接往上怼就行。但缺点也很明显:存储空间大。一个1000×1000的地图就是100万个格子,要是三维的...嗯,你懂的。
二、拓扑地图:只关心「怎么走」
拓扑地图就不一样了。它不关心具体的地理位置,只关心节点之间的连接关系。说白了,就是一张「路网图」。
我做过一个仓储机器人的项目,仓库有几千平米。如果用栅格地图,内存直接爆掉。后来改用拓扑地图,只记录货架之间的通道和交叉点,问题就解决了。
拓扑地图的构建通常分两步:
- 提取节点:比如走廊拐角、房间门口、岔路口
- 连接边:节点之间如果有直通路径,就加一条边
代码实现也很清晰:
# 拓扑地图的简单表示
class TopoMap:
def __init__(self):
self.nodes = {} # 节点字典:{id: (x, y)}
self.edges = [] # 边列表:[(node1, node2, cost)]
def add_node(self, node_id, x, y):
self.nodes[node_id] = (x, y)
def add_edge(self, node1, node2, cost=None):
if cost is None:
# 计算欧氏距离作为默认代价
x1, y1 = self.nodes[node1]
x2, y2 = self.nodes[node2]
cost = ((x1-x2)**2 + (y1-y2)**2)**0.5
self.edges.append((node1, node2, cost))
def get_neighbors(self, node_id):
neighbors = []
for n1, n2, cost in self.edges:
if n1 == node_id:
neighbors.append((n2, cost))
elif n2 == node_id:
neighbors.append((n1, cost))
return neighbors
# 构建一个简单的拓扑地图
topo = TopoMap()
topo.add_node('A', 0, 0)
topo.add_node('B', 5, 0)
topo.add_node('C', 5, 5)
topo.add_node('D', 0, 5)
topo.add_edge('A', 'B')
topo.add_edge('B', 'C')
topo.add_edge('C', 'D')
topo.add_edge('D', 'A')
print("从A出发可到达:", topo.get_neighbors('A'))
拓扑地图的优点很明显:轻量、高效。适合大范围、结构化的环境。但缺点也有——它丢失了环境的几何细节,不适合做精细的避障。
三、几何地图:精确到厘米级
几何地图,就是最「实在」的那种。它用点、线、面来精确描述环境。你可以把它想象成CAD图纸的简化版。
我参与过一个自动驾驶的早期项目,用的就是几何地图。每条车道线、每个路沿、每个交通标志,都用几何元素精确表示。精度嘛,厘米级。
几何地图的构建通常依赖传感器数据,比如激光雷达。下面是一个简单的示例:
# 几何地图的简单表示(2D线段形式)
class GeometryMap:
def __init__(self):
self.walls = [] # 墙壁线段列表
self.obstacles = [] # 障碍物多边形列表
def add_wall(self, x1, y1, x2, y2):
self.walls.append(((x1, y1), (x2, y2)))
def add_obstacle(self, points):
"""points: 多边形顶点列表 [(x1,y1), (x2,y2), ...]"""
self.obstacles.append(points)
def check_collision(self, x, y, radius=0.2):
"""检查圆形机器人是否与障碍物碰撞"""
# 简化版:检查点到线段的距离
for (x1, y1), (x2, y2) in self.walls:
dist = self._point_to_segment_dist(x, y, x1, y1, x2, y2)
if dist < radius:
return True
return False
def _point_to_segment_dist(self, px, py, x1, y1, x2, y2):
# 计算点到线段的距离(略)
pass
# 构建一个房间的几何地图
room = GeometryMap()
room.add_wall(0, 0, 10, 0) # 下墙
room.add_wall(10, 0, 10, 10) # 右墙
room.add_wall(10, 10, 0, 10) # 上墙
room.add_wall(0, 10, 0, 0) # 左墙
room.add_obstacle([(3,3), (5,3), (5,5), (3,5)]) # 方形障碍物
三种地图怎么选?
这个问题没有标准答案。我个人的经验是:
| 场景 | 推荐地图类型 | 原因 |
|---|---|---|
| 室内扫地机器人 | 栅格地图 | 简单、够用、实时性好 |
| 大型仓储物流 | 拓扑地图 | 范围大、结构清晰、内存友好 |
| 自动驾驶 | 几何地图 | 精度要求高、需要厘米级定位 |
| 未知环境探索 | 栅格地图+拓扑地图混合 | 先建栅格做感知,再提取拓扑做规划 |
其实很多时候,实际项目里用的是混合地图。比如底层用栅格地图做局部避障,上层用拓扑地图做全局路径规划。各取所长嘛。
好了,环境建模这块就聊到这儿。这三种地图的构建方法,你可以在自己的项目里试试看。记住我踩过的坑——地图建好了,后面的路径规划才能顺风顺水。
公众号:蓝海资料掘金营,微信deep3321