3、仿真场景概念:什么是仿真场景、场景文件格式、场景构成要素

好,咱们进入第三章。这一章我打算把仿真场景这个概念彻底讲透。

说实话,我刚开始接触Autoware那会儿,对“仿真场景”的理解特别模糊。总觉得不就是把车放在一个虚拟地图里跑一跑吗?后来踩了不少坑才明白——场景设计的好坏,直接决定了你的算法能不能在实车上跑通。

3.1 什么是仿真场景

简单来说,仿真场景就是自动驾驶算法在虚拟世界里“考试”的试卷。

它不光包含一条路、一辆车。它定义了:

  • 车在什么路上开(道路几何、车道线、交通标志)
  • 周围有什么动态物体(行人、车辆、自行车)
  • 这些物体怎么动(速度、轨迹、行为)
  • 天气、光照、时间等环境条件

我个人的习惯是,把仿真场景比作一个舞台剧剧本。道路是舞台布景,交通参与者是演员,而场景文件就是导演手里的脚本。你想想看,没有剧本的舞台剧,演员上去只能乱演。没有精心设计的场景,你的自动驾驶算法也练不出真本事。

核心观点:仿真场景 = 静态环境 + 动态行为 + 规则约束

我在项目中遇到过最典型的反面案例:有人用一条笔直的空马路测试感知算法,结果效果“惊艳”。一上实车,各种弯道、遮挡、突发行人,算法直接崩了。嗯,这就是场景设计太单一的问题。

3.2 场景文件格式

Autoware生态里,场景文件主要涉及两种格式。我分别说说。

3.2.1 OpenDRIVE(.xodr)—— 描述“路”长什么样

OpenDRIVE是描述静态道路网络的格式。说白了,它告诉仿真器:

  • 这条路有几条车道
  • 车道宽多少、曲率多大
  • 有没有人行横道、停止线
  • 路肩、护栏在什么位置

它的核心是参考线(reference line)。所有车道、标线都基于这条参考线偏移生成。

给你看一段简化的OpenDRIVE示例:

<road name="Highway_1" length="500.0" id="1" junction="-1">
  <planView>
    <geometry s="0.0" x="0.0" y="0.0" hdg="0.0" length="500.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>
      <center>
        <lane id="0" type="none"/>
      </center>
      <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>

这段代码定义了一条500米长的直路,双向各一条3.5米宽的车道。看着挺简单对吧?但实际项目中,一个复杂的城市路口,OpenDRIVE文件可能有上万行。

我的经验:刚开始学OpenDRIVE时,别急着手写。先用Autoware自带的RoadRunner或者开源工具生成,再对照文档理解每个标签的含义。我曾经手写过一个十字路口,写了三天,结果导入仿真器直接报错——坐标对不上。

3.2.2 OpenSCENARIO(.xosc)—— 描述“戏”怎么演

OpenSCENARIO是描述动态场景的格式。它定义:

  • 什么时候触发什么事件
  • 车辆以什么速度、沿什么轨迹行驶
  • 行人什么时候横穿马路
  • 信号灯什么时候变红

它的核心是故事板(Storyboard)。故事板里包含一幕幕(Act)、一个个场景(Scene)、一个个事件(Event)。

看个简单例子:

<Scenario name="CutInScenario">
  <Storyboard>
    <Init>
      <Action name="InitEgo">
        <Private>
          <Position>
            <LanePosition roadId="1" laneId="1" s="10.0" offset="0.0"/>
          </Position>
        </Private>
      </Action>
    </Init>
    <Story name="CutInStory">
      <Act name="CutInAct">
        <ManeuverGroup name="CutInManeuver">
          <Actors>
            <EntityRef entityRef="TargetVehicle"/>
          </Actors>
          <Maneuver name="LaneChange">
            <Event name="StartCutIn">
              <Action name="ChangeLane">
                <PrivateAction>
                  <LateralAction>
                    <LaneChangeAction targetLane="-1"/>
                  </LateralAction>
                </PrivateAction>
              </Action>
            </Event>
          </Maneuver>
        </ManeuverGroup>
      </Act>
    </Story>
  </Storyboard>
</Scenario>

这个场景描述了一辆目标车从右侧车道切入主车前方。你看,OpenSCENARIO把“谁、在什么时候、做什么动作”写得清清楚楚。

注意:OpenSCENARIO和OpenDRIVE是配套使用的。OpenDRIVE搭好舞台,OpenSCENARIO指挥演员。两者缺一不可。我曾经见过有人只配了OpenDRIVE,没写OpenSCENARIO,结果仿真器里只有一辆孤零零的主车停在路上——动都动不了。

3.3 场景的构成要素

一个完整的仿真场景,我习惯把它拆成三个维度来看。下面这张图可以帮你快速建立整体认知:

仿真场景构成要素 静态环境 道路、车道、交通标志 动态元素 车辆、行人、自行车 规则约束 交通规则、触发条件 静态环境子要素 • 道路几何(OpenDRIVE) • 车道数量与宽度 • 交通标志与标线 • 路侧设施(护栏、灯杆) • 地形与建筑 • 天气与光照 动态元素子要素 • 主车(Ego Vehicle) • 目标车辆(NPC) • 行人(Pedestrian) • 自行车/摩托车 • 动物(罕见场景) • 动态障碍物 规则约束子要素 • 速度限制 • 交通信号灯时序 • 路权规则 • 触发条件(距离/时间) • 事件序列 • 评价指标(碰撞/违规)

从这张图你可以看到,三个维度缺一不可。我拿一个实际场景举例:

场景:前车急刹

  • 静态环境:城市双向四车道,限速60km/h,路面干燥,白天
  • 动态元素:主车以50km/h行驶,前车(NPC)在20米处
  • 规则约束:当前车速度降到10km/h以下时,触发急刹事件;主车必须在3秒内完成制动且不追尾

你看,这样一拆解,场景就变得非常清晰。写OpenSCENARIO文件时,你只需要按照这个思路去填充XML标签就行。

避坑指南:我曾经犯过一个低级错误——只关注了动态元素,忽略了静态环境的细节。比如在一个路口场景里,我忘了在OpenDRIVE里定义停止线。结果仿真时,主车直接无视红灯冲了过去。嗯,从那以后我养成了习惯:每次写完场景,先对照这三个维度检查一遍。

3.4 场景文件的管理建议

最后,分享一点我在项目中的文件管理经验。一个自动驾驶项目,场景文件少则几十个,多则上千个。乱放的话,找起来真要命。

文件类型 命名规范 存放位置
OpenDRIVE map_城市_路段_版本.xodr maps/
OpenSCENARIO scenario_场景类型_编号.xosc scenarios/
参数配置 param_测试项_版本.yaml params/
测试报告 report_日期_场景编号.html reports/

我个人习惯用场景编号+场景类型作为文件名前缀。比如 scenario_001_cut_in_left.xosc,一看就知道是第1个场景,左侧切入。这样不管是自己找,还是团队协作,效率都高很多。

好了,这一章的内容就到这里。场景概念是后续所有操作的基础,建议你花点时间把OpenDRIVE和OpenSCENARIO的官方文档翻一翻,不用全看,重点看road、lane、storyboard这几个核心标签就行。

可可精品资料库