3、车道模型与拓扑:车道中心线、车道边界线、车道连接关系(Link)、车道宽度与曲率
好,咱们进入正题。车道模型,说白了就是高精地图里最核心的「骨架」。你想想看,自动驾驶车在路上跑,它得知道自己该走哪条道,哪条道能拐弯,哪条道会变窄。这些信息,全藏在车道模型里。
我个人习惯把车道模型拆成三块来看:几何形状、拓扑关系、属性参数。今天咱们一个一个啃。
3.1 车道中心线:车的「导航线」
车道中心线,就是车道上那条虚拟的引导线。它不是实体的标线,而是我们人为定义的一条参考线。车控模块会盯着这条线做横向控制。
中心线的表示方式,业内主流有两种:
- 离散点序列:每隔一定距离(比如0.5米或1米)采一个点,存成(x, y, z)坐标。简单粗暴,但数据量大。
- 参数化曲线:用三次样条或贝塞尔曲线拟合。我更喜欢用三次样条,因为它在连接处能保证曲率连续,车跑起来不抖。
核心要点:中心线必须保证C2连续(二阶导数连续),否则控制模块会算出跳变的转向角,车会「画龙」。
举个例子,OpenDRIVE格式里,中心线是用planView和elevationProfile组合描述的。代码里长这样:
<!-- OpenDRIVE 车道中心线示例 -->
<planView>
<geometry s="0.0" x="0.0" y="0.0" hdg="0.0" length="100.0">
<line/>
</geometry>
<geometry s="100.0" x="100.0" y="0.0" hdg="0.1" length="50.0">
<spiral curvStart="0.0" curvEnd="0.02"/>
</geometry>
</planView>
这里spiral就是缓和曲线,用来平滑过渡曲率。嗯,这个细节很多新手会忽略。
3.2 车道边界线:别压线,别越界
边界线就是车道的物理或虚拟边界。它决定了车能不能变道、能不能借道。
边界线分两种:
- 物理边界:路缘石、护栏、混凝土墙。这些是硬约束,车绝对不能碰。
- 标线边界:白实线、白虚线、双黄线。虚线可以跨越,实线不行。
我在项目中遇到过一个问题:某段路的标线因为磨损,地图里标的是实线,但实际已经模糊不清了。结果规划模块死活不让变道,车就在那堵着。后来我们加了一个「标线置信度」字段,低于阈值的按虚线处理。
避坑指南:边界线的类型一定要和交通规则绑定。比如「实线变道扣3分」,这个逻辑必须在路径规划里写死。我曾经见过一个demo,车压着实线变道,虽然技术上做到了,但上路就是违章。
3.3 车道连接关系(Link):谁跟谁连着
车道不是孤岛。前一个车道到后一个车道怎么接?左转车道连到哪条路上?这些就是Link要干的事。
Link关系通常用「前驱-后继」模型来描述:
- 前驱(Predecessor):当前车道从哪来
- 后继(Successor):当前车道到哪去
- 左邻/右邻(Left/Right Neighbor):相邻车道是谁
你想想看,如果路口有5条车道,每条车道可能对应3个不同的出口方向。那Link关系就是一张网。我习惯用邻接表来存,查询效率高。
举个例子,一个十字路口左转车道的Link配置:
车道ID: lane_3
类型: 左转专用道
前驱: lane_2 (直行车道)
后继: lane_101 (目标路段的最左侧车道)
左邻: lane_4 (直行车道)
右邻: lane_2 (直行车道)
转向类型: left_turn
这里要注意,转向类型必须和交通灯相位匹配。否则会出现「绿灯亮了但规划模块还在等」的尴尬情况。
3.4 车道宽度:不是所有车道都一样宽
车道宽度是个容易被低估的参数。你以为都是3.5米?错。高速车道、城市快速路、匝道、停车带,宽度都不一样。
| 道路类型 | 标准宽度 | 最小宽度 | 备注 |
|---|---|---|---|
| 高速公路 | 3.75m | 3.5m | 大型车多,需要更宽 |
| 城市主干道 | 3.5m | 3.25m | 混行路段要注意 |
| 匝道 | 3.0m | 2.8m | 弯道处要加宽 |
| 停车带 | 2.5m | 2.0m | 仅限临时停车 |
宽度数据怎么用?路径规划时,如果车道宽度小于车辆宽度+安全余量,那就不能通行。我见过一个案例,某辆宽体SUV在窄匝道上蹭了护栏,就是因为地图里宽度字段写错了。
个人经验:车道宽度最好存成「分段函数」。因为有些车道在路口会渐变加宽,比如从3.5米变成4.5米。用s_offset和width两个字段就能描述这种变化。
3.5 曲率:弯道快慢全靠它
曲率决定了车过弯时的最大安全速度。曲率越大,弯越急,速度就得越低。
曲率的计算很简单:曲率 = 1 / 转弯半径。单位是 1/m。比如半径100米的弯,曲率就是0.01。
但实际应用中,曲率不是常数。一个弯道从直道进入,曲率从0逐渐增大到最大值,再逐渐减小到0。这就是为什么需要缓和曲线(clothoid)。
我建议在车道模型里存两个曲率值:
- 起始曲率:车道起点处的曲率
- 终止曲率:车道终点处的曲率
中间的变化,用线性插值或者样条插值算出来。这样规划模块就能提前知道「前面有个弯,该减速了」。
举个例子,一段匝道的曲率变化:
车道ID: ramp_05
长度: 200m
起始曲率: 0.001 (半径1000m,几乎直道)
终止曲率: 0.02 (半径50m,急弯)
曲率变化率: 0.000095 /m (线性变化)
有了这个数据,速度规划就能算出:在哪个位置开始刹车,刹到多少速度,过了弯心再加速。
核心要点:曲率变化率(曲率对弧长的导数)比曲率本身更重要。如果曲率变化率太大,车会感觉「突然被拽了一把」,乘客体验极差。我一般要求曲率变化率不超过0.001/m²。
3.6 把这些串起来:一个完整的车道模型
好了,咱们把上面这些元素拼起来,就是一个完整的车道模型了。我习惯用JSON格式来存,方便调试:
{
"lane_id": "lane_001",
"type": "driving",
"center_line": [
{"s": 0.0, "x": 0.0, "y": 0.0, "z": 0.0},
{"s": 1.0, "x": 0.0, "y": 1.0, "z": 0.0}
],
"left_boundary": {
"type": "solid_white",
"points": [...]
},
"right_boundary": {
"type": "dashed_white",
"points": [...]
},
"predecessor": "lane_000",
"successor": "lane_002",
"left_neighbor": null,
"right_neighbor": "lane_003",
"width": {
"s_start": 0.0,
"s_end": 100.0,
"width_start": 3.5,
"width_end": 3.5
},
"curvature": {
"s_start": 0.0,
"s_end": 100.0,
"curv_start": 0.0,
"curv_end": 0.005
}
}
这个结构虽然简单,但足够支撑90%的路径规划场景了。剩下的10%,比如多叉路口、环形岛、潮汐车道,需要在这个基础上加一些特殊字段。
最后说一句,车道模型的质量直接决定了自动驾驶的上限。地图数据不准,算法再牛也白搭。所以,做地图的同学,请善待每一条车道线。