4、地图构建基础:Lanelet2地图格式、地图元素(车道、路口、交通标志)、使用Vector Map Builder创建地图

做自动驾驶仿真,地图是灵魂。没有地图,车就是瞎子。

我个人习惯把地图比作「给车看的说明书」——告诉它路在哪、能走多快、哪里要停。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 建图步骤

  1. 导入底图: 把地图图片拖进 Vector Map Builder,设置好坐标系
  2. 画道路骨架: 先画中心线,再生成左右边界
  3. 分割车道: 把长路切成多个 Lanelet,每个 50-100 米一段
  4. 添加路口: 把交叉区域标记为路口,连接进出的车道
  5. 挂载标志: 在对应位置放上交通标志
  6. 导出 .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 地图构建知识体系 物理层 点、线、多边形 地图的「骨架」 坐标、形状、位置 关系层 车道、路口、区域 把骨架拼成路 连接、分组、规则 拓扑层 车道连接关系 告诉车怎么走 前后、左右、优先 核心地图元素 车道 (Lanelet) 路口 (Intersection) 交通标志 RegulatoryElement 建图工具:Vector Map Builder 导入底图 → 画车道 → 分割 → 加路口 → 挂标志 → 导出 .osm

这张图把 Lanelet2 的「三层结构」和「核心元素」串起来了。你建图时,心里要时刻想着这三层——物理层画准了,关系层连对了,拓扑层自然就通了。

最后说一句: 地图构建是仿真里最枯燥但最重要的环节。我见过太多人急着跑仿真,结果地图画得马马虎虎,最后 debug 花的时间比建图还多。我的建议是——慢一点,画仔细一点,后面会省很多事。

可可精品资料库