4. 场景构建基础

场景构建,说白了就是给自动驾驶系统搭一个「虚拟考场」。你想想看,没有这个考场,你怎么敢让车直接上路跑?我刚开始做仿真时,总觉得随便画几条路就能跑,结果第一次测试就撞了虚拟护栏——嗯,从那以后我再也不敢轻视场景构建了。

4.1 OpenDrive高精地图:场景的「骨架」

高精地图不是普通导航地图。普通地图告诉你「前方500米右转」,高精地图告诉你「前方500米右转,车道宽度3.5米,曲率半径200米,坡度2%」。我个人习惯把OpenDrive比作场景的骨架——没有它,所有元素都是散的。

4.1.1 OpenDrive的核心结构

OpenDrive用XML描述道路网络。它的核心概念其实就几个:

  • 道路(Road):最基本的单元,包含车道、参考线
  • 车道(Lane):每条道路由多个车道组成,有编号和类型
  • 连接(Junction):道路之间的交叉口
  • 参考线(Reference Line):道路的几何中心线

我遇到过最坑的事是什么?有一次导入第三方地图,发现车道编号全乱了。后来查了半天,原来是参考线方向定义反了。所以这里要提醒你:参考线的方向决定了车道编号的正负,这是最容易踩的坑。

关键点:OpenDrive中,车道编号以参考线为界,右侧为正(1,2,3...),左侧为负(-1,-2,-3...)。千万别搞反了!

4.1.2 一个简单的OpenDrive示例

来看一段实际代码。这是一条双向两车道的道路:

<?xml version="1.0" encoding="UTF-8"?>
<OpenDrive>
  <header revMajor="1" revMinor="4" name="demo_map"/>
  <road name="Road_0" length="100.0" id="0" junction="-1">
    <planView>
      <geometry s="0.0" x="0.0" y="0.0" hdg="0.0" length="100.0">
        <line/>
      </geometry>
    </planView>
    <lanes>
      <laneSection s="0.0">
        <left>
          <lane id="-1" type="driving" level="false">
            <width sOffset="0.0" a="3.5" b="0.0" c="0.0" d="0.0"/>
          </lane>
        </left>
        <right>
          <lane id="1" type="driving" level="false">
            <width sOffset="0.0" a="3.5" b="0.0" c="0.0" d="0.0"/>
          </lane>
        </right>
      </laneSection>
    </lanes>
  </road>
</OpenDrive>

这段代码定义了一条100米长的直路,左右各一条3.5米宽的车道。你看,其实不复杂。但实际项目中,一条城市道路可能有几十个车道段,每个段的宽度、坡度都在变化。

小技巧:我建议初学者先用OpenDrive官方提供的编辑器(如ODR Viewer)可视化地图,确认几何正确后再写代码。肉眼检查比调试代码快得多。

4.2 场景元素:给骨架「长肉」

有了地图骨架,接下来要填充场景元素。我把它们分成三类:道路本身、交通标志、动态障碍物。

4.2.1 道路元素

道路不只是路面。它还包括:

  • 车道标线:实线、虚线、双黄线
  • 路肩:硬路肩、软路肩
  • 人行横道:位置、宽度、方向
  • 减速带:高度、长度

我曾经在一个项目中,忽略了人行横道的方向属性。结果仿真时,车辆在人行横道上横着刹车——嗯,行人看了都得愣住。所以每个元素的属性都要仔细核对。

4.2.2 交通标志

交通标志在OpenDrive中用<signal>标签表示。核心属性包括:

属性 说明 示例值
id 唯一标识 "1001"
s 沿道路的纵向位置 50.0
t 横向偏移(正为右侧) 2.0
type 标志类型 "100"(限速)
value 标志数值 60(km/h)

举个例子,在道路50米处放置一个限速60km/h的标志:

<signal s="50.0" t="2.0" id="1001" type="100" value="60" dynamic="no"/>

注意:交通标志的坐标系是沿道路的s-t坐标系,不是全局x-y坐标系。很多新手在这里栽跟头——标志位置看起来对,但实际在仿真中飘在半空中。我建议先用可视化工具确认位置。

4.2.3 动态障碍物

动态障碍物是场景中最难搞的部分。它包含:

  • 车辆:轿车、卡车、自行车
  • 行人:行走、奔跑、静止
  • 动物:狗、猫(别笑,真遇到过)

每个动态障碍物需要定义:初始位置、速度、轨迹、行为模式。我个人习惯用OpenSCENARIO来定义动态行为,它和OpenDrive是天生一对。

举个简单的例子,一辆以10m/s匀速行驶的车辆:

<Scenario>
  <Entity name="Ego">
    <Position x="0.0" y="0.0" z="0.0"/>
    <Speed value="10.0"/>
  </Entity>
  <Entity name="Target">
    <Position x="50.0" y="0.0" z="0.0"/>
    <Speed value="5.0"/>
    <Trajectory>
      <Waypoint time="0.0" x="50.0" y="0.0"/>
      <Waypoint time="10.0" x="100.0" y="0.0"/>
    </Trajectory>
  </Entity>
</Scenario>

这里Ego车是自车,Target车是目标障碍物。你看,Target车从50米处出发,10秒后到达100米处,速度5m/s。这就是一个最简单的跟车场景。

4.3 知识体系总览

说了这么多,我画了一张图帮你理清思路。场景构建的核心就是「骨架+血肉+灵魂」:

场景构建知识体系 骨架:高精地图 血肉:静态元素 灵魂:动态元素 OpenDrive格式 道路(Road) 车道(Lane) 参考线(RefLine) 连接(Junction) s-t坐标系 车道标线 路肩/人行横道 减速带 交通标志 信号灯 限速/警告标识 车辆(轿车/卡车) 行人 动物 轨迹定义 行为模式 OpenSCENARIO 三者缺一不可,共同构成完整的仿真场景 推荐工具:OpenDrive编辑器 + OpenSCENARIO + 可视化验证

4.4 避坑指南:我踩过的那些坑

最后,分享几个实战中容易踩的坑:

  1. 坐标系混淆:全局坐标系和s-t坐标系混用。我曾经把交通标志的t值当成了全局y坐标,结果标志跑到了路对面。
  2. 车道宽度突变:OpenDrive允许车道宽度在相邻段突变,但实际道路不会这样。仿真时车辆会「跳」一下,看着特别假。
  3. 动态障碍物轨迹不连续:两个路径点之间速度变化太大,车辆会瞬移。我建议每个路径点之间时间间隔不超过1秒。
  4. 忽略高程:平面地图看着没问题,一加上坡度就全乱了。记得检查z轴数据。

我的习惯:每次构建完场景,先用可视化工具跑一遍,再让自车以低速跑一遍,最后才上高速测试。这个流程虽然慢,但能省掉后面90%的调试时间。

好了,场景构建的基础就这些。记住:地图是骨架,静态元素是血肉,动态元素是灵魂。三者配合好了,你的仿真场景才能真实、可靠。