环境建模基础:栅格地图、拓扑地图、几何地图的构建与表示方法

做路径规划这么多年,我越来越觉得一个道理:地图建不好,算法跑不了。你想想看,再牛的A*算法、再快的Dijkstra,如果地图本身有问题,那规划出来的路径基本就是废的。

今天咱们就来聊聊环境建模这件事。说白了,就是把真实世界变成机器人能理解的数据结构。我习惯把地图分成三大类:栅格地图、拓扑地图、几何地图。每种都有它的脾气,咱们一个一个说。

环境建模基础 栅格地图 拓扑地图 几何地图 离散化表示 A*适用 障碍物标记 节点+边 路网结构 大场景高效 点线面精确 CAD风格 计算量大 选择哪种?看场景、看精度、看算力

一、栅格地图:最直观,也最常用

栅格地图,其实就是把环境切成一个个小格子。每个格子要么是空地(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
我的经验: 栅格大小很关键。格子太大,路径会丢失细节;格子太小,计算量爆炸。我一般取机器人直径的1/3到1/2作为栅格尺寸,效果还不错。

栅格地图最大的好处是——简单。A*算法、Dijkstra算法直接往上怼就行。但缺点也很明显:存储空间大。一个1000×1000的地图就是100万个格子,要是三维的...嗯,你懂的。

二、拓扑地图:只关心「怎么走」

拓扑地图就不一样了。它不关心具体的地理位置,只关心节点之间的连接关系。说白了,就是一张「路网图」。

我做过一个仓储机器人的项目,仓库有几千平米。如果用栅格地图,内存直接爆掉。后来改用拓扑地图,只记录货架之间的通道和交叉点,问题就解决了。

核心思想: 用节点表示关键位置,用边表示可通行路径。

拓扑地图的构建通常分两步:

  1. 提取节点:比如走廊拐角、房间门口、岔路口
  2. 连接边:节点之间如果有直通路径,就加一条边

代码实现也很清晰:

# 拓扑地图的简单表示
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)])  # 方形障碍物
我的建议: 几何地图虽然精确,但计算量也最大。如果你做的是扫地机器人这种小场景,栅格地图就够了。但如果是自动驾驶或者工业AGV,几何地图几乎是必须的。

三种地图怎么选?

这个问题没有标准答案。我个人的经验是:

场景 推荐地图类型 原因
室内扫地机器人 栅格地图 简单、够用、实时性好
大型仓储物流 拓扑地图 范围大、结构清晰、内存友好
自动驾驶 几何地图 精度要求高、需要厘米级定位
未知环境探索 栅格地图+拓扑地图混合 先建栅格做感知,再提取拓扑做规划

其实很多时候,实际项目里用的是混合地图。比如底层用栅格地图做局部避障,上层用拓扑地图做全局路径规划。各取所长嘛。

一句话总结: 栅格地图适合小范围精细规划,拓扑地图适合大范围快速规划,几何地图适合高精度场景。没有最好的地图,只有最合适的。

好了,环境建模这块就聊到这儿。这三种地图的构建方法,你可以在自己的项目里试试看。记住我踩过的坑——地图建好了,后面的路径规划才能顺风顺水。


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