4. 地图构建基础
做自动驾驶仿真,地图是灵魂。没有地图,车就是瞎子。
我个人习惯把地图比作「给车看的说明书」——告诉它路在哪、能走多快、哪里要停。Autoware 用的地图格式叫 Lanelet2,今天我们就把它彻底搞明白。
4.1 Lanelet2 到底是什么?
Lanelet2 不是普通的矢量地图。它是一套专门为自动驾驶设计的「高精地图」格式。说白了,它不光画了路,还画了路该怎么走。
我记得第一次接触 Lanelet2 时,觉得它跟 OpenDRIVE 有点像。但用久了发现,Lanelet2 更灵活,尤其适合城市道路场景。它把道路拆成一个个「车道片段」(Lanelet),每个片段都带着自己的规则。
核心概念: Lanelet2 地图由三个层级组成:
- 物理层(Physical Layer): 点、线、多边形——就是地图的「骨架」
- 关系层(Relational Layer): 车道、路口、区域——把骨架拼成路
- 拓扑层(Topological Layer): 车道之间的连接关系——告诉车怎么走
你想想看,这三个层级就像盖房子:先搭钢筋(物理层),再砌墙(关系层),最后画水电图(拓扑层)。缺一层都不行。
4.2 地图元素详解
Lanelet2 的地图元素其实不多,但每个都很关键。我按使用频率给你排个序:
4.2.1 车道(Lanelet)
车道是地图的基本单元。每个 Lanelet 由左右两条边界线构成,中间就是车能走的路。
我曾经在项目中踩过一个坑:两条车道拼接处,如果边界线没对齐,车就会「跳一下」。后来我养成了习惯——每次建完地图,先让车跑一圈,看有没有「颠簸感」。
| 属性 | 说明 | 我的建议 |
|---|---|---|
| id | 唯一标识 | 用有规律的编号,比如 lane_001 |
| leftBound | 左边界线 | 注意方向,从起点到终点 |
| rightBound | 右边界线 | 跟左边界保持平行 |
| speedLimit | 限速(m/s) | 别设太高,仿真时容易失控 |
4.2.2 路口(Intersection)
路口是最容易出问题的地方。Lanelet2 用 RegulatoryElement 来管理路口规则。
嗯,这里要注意:路口不是简单画个框就行。你得定义清楚——哪个方向有优先权、哪里是停止线、行人从哪里过。
我的小技巧: 建路口时,先把所有进入路口的车道画好,再画出去的车道。中间空出来的区域就是路口。这样逻辑最清晰。
4.2.3 交通标志(Traffic Sign)
交通标志在 Lanelet2 里属于「附加信息」。你可以把它挂在任意一个 Lanelet 上。
常见的标志类型:
- 停止标志(stop_sign): 车必须在停止线前停稳
- 让行标志(yield_sign): 减速观察,没车再走
- 限速标志(speed_limit): 覆盖车道的默认限速
- 红绿灯(traffic_light): 这个最复杂,后面章节细讲
我曾经犯过一个低级错误:把停止标志的位置放偏了 2 米。结果车每次都停在路中间,而不是停止线前。找 bug 找了半天...从那以后,我建完地图都会手动检查每个标志的位置。
4.3 使用 Vector Map Builder 创建地图
Vector Map Builder 是 Autoware 官方推荐的建图工具。说实话,它不算漂亮,但够用、稳定。
4.3.1 准备工作
你需要:
- 一张底图(卫星图或 CAD 图)
- Vector Map Builder 插件(QGIS 或独立版)
- 耐心——建图是个细致活
避坑指南: 我曾经直接用卫星图建图,结果发现卫星图有偏移。车跑起来总是偏一个车道。后来我学会了——先校准底图坐标,再开始画。
4.3.2 建图步骤
- 导入底图: 把地图图片拖进 Vector Map Builder,设置好坐标系
- 画道路骨架: 先画中心线,再生成左右边界
- 分割车道: 把长路切成多个 Lanelet,每个 50-100 米一段
- 添加路口: 把交叉区域标记为路口,连接进出的车道
- 挂载标志: 在对应位置放上交通标志
- 导出 .osm 文件: Lanelet2 的标准格式
我个人习惯每画完一段路,就保存一次。别问我为什么——有一次画了两个小时,软件崩了,没保存...嗯,你懂的。
4.3.3 代码示例:加载地图
建好地图后,在 Autoware 里加载很简单:
// C++ 示例:加载 Lanelet2 地图
#include <lanelet2_io/Io.h>
#include <lanelet2_projection/UTM.h>
lanelet::LaneletMapPtr loadMap(const std::string& filePath) {
// 设置投影方式
lanelet::projection::UtmProjector projector(lanelet::Origin({37.7749, -122.4194}));
// 加载地图
lanelet::LaneletMapPtr map = lanelet::load(filePath, "autoware", projector);
// 检查是否加载成功
if (!map) {
throw std::runtime_error("地图加载失败!检查文件路径");
}
std::cout << "地图加载成功,共有 " << map->laneletLayer.size() << " 个车道" << std::endl;
return map;
}
这段代码我用了好几年,基本没改过。唯一要注意的是投影原点——一定要跟你建图时用的坐标一致,否则地图会飘。
4.4 本章知识体系
下面这张图帮你理清 Lanelet2 地图的核心逻辑:
这张图把 Lanelet2 的「三层结构」和「核心元素」串起来了。你建图时,心里要时刻想着这三层——物理层画准了,关系层连对了,拓扑层自然就通了。
最后说一句: 地图构建是仿真里最枯燥但最重要的环节。我见过太多人急着跑仿真,结果地图画得马马虎虎,最后 debug 花的时间比建图还多。我的建议是——慢一点,画仔细一点,后面会省很多事。
可可精品资料库