引子
一个常见的误区:场景越多,测试越充分。
很多团队疯狂采集路采数据,场景库膨胀到几千甚至上万个。CI流水线一跑就是十几个小时,工程师第二天来开早会,200个失败,其中180个是环境漂移和场景自身的参数问题。
这不是测试,这是噪声。
核心矛盾:仿真场景库的价值不在数量,在覆盖效率。 这篇聊聊我们在一线汽车大厂的智驾测试实践中沉淀下来的场景库构建方法论。
场景四层分类:不同层级的生成策略完全不同
先把场景分成四层:
| 层级 | 来源 | 典型占比 | 回归频率 | 策略关键词 |
|---|---|---|---|---|
| L1 法规场景 | NCAP / 国标 | ~15% | 每次提交 | 翻译,不做增量 |
| L2 NP 场景 | ODD 内常规驾驶 | ~40% | 每日 | 参数化 + 约束剪枝 |
| L3 边界场景 | NP 的参数边界 + 交互复杂 | ~30% | 每周 | 风险度加权采样 |
| L4 Corner Case | 事故库 / 路采异常 / 领域经验 | ~15% | 发版前 | 人工积累,碎片化 |
L1 没什么发挥空间——把 NCAP 和国标测试规程翻译成仿真脚本,关键是保持和法规版本同步。
真正拉开团队差距的是 L2 和 L3。
L2 NP 场景:参数化的核心不是"全",是"覆盖"
NP(Nominal Performance)场景的出发点是:不枚举具体场景,而是定义参数空间,让工具链自动展开。
我们定义了三组核心参数维度:
道路几何:曲率半径[200|500|1000|直线] × 坡度[-6%..6%,step=2%] × 车道数[2|3|4]
交通参与:类型[轿车|卡车|行人|自行车] × 数量[0..5] × 行为[跟车|切入|横穿|静止]
环境条件:光照[白天|黄昏|夜间] × 天气[晴|雨|雾] × 路面[干|湿]
三组全组合:4×7×3 × 4×6×4 × 3×3×2 ≈ 43 万种。全跑是自杀。
两个手段把 43 万压到可用:
第一刀:pairwise 组合。 用两两覆盖替代全组合,43 万 → 约 600 个参数组合。实践验证 2-way pairwise 对 NP 场景已经足够——三因子交互导致的功能性问题在 NP 域极少见。
第二刀:领域约束剪枝。
- 夜间 + 晴 → 逻辑矛盾,剔除
- 行人 + 高速(>80km/h) + 横穿 → 保留但标"极端",不参与 NP 回归
- 坡度 > 4% → 限定车速 < 60km/h,自动约束注入
最终 L2 核定为约 200 个参数化模板,展开覆盖约 2000+ 具体场景。
L3 边界场景:不要枚举,要风险度采样
边界场景的痛点在于交互逻辑复杂。以无保护左转为例:
- 自车速度、转向角、加速度曲线
- 对向来车速度、距离、数量
- 行人/非机动车的穿插时机
枚举下去轻松过万。
我们的策略:基于风险度的自适应采样。 给每组场景参数定义风险函数:
def risk_score(params):
ttc_factor = 1.0 / max(calc_ttc(params), 0.1)
window_factor = 1.0 / (reaction_window(params) + 0.5)
env_factor = 1.0 + 0.3 * params.traffic_count
return ttc_factor * window_factor * env_factor
然后按风险度加权采样——高风险区(TTC < 2s, 多交通参与者)密集取点,低风险区稀疏取点。200 个采样点覆盖原来需要上万枚举的边界场景类型。
这是跟做感知的同事学来的思路:模仿训练集的 hard example mining——让小概率但高风险场景获得不成比例的测试资源。
OpenSCENARIO 落地:参数不是终点,注入才是
场景定义好了,怎么注入仿真引擎?OpenSCENARIO 2.0 虽然原生支持参数化,但实际落地时两个坑避不开。
坑一:引擎方言不一致
同一个"切入场景",Carla 和 VTD 对参数语义的理解不完全一致。崩溃往往不是场景设计的问题,是翻译层出的 bug。
解法:引入一个中间表示层(IR),场景工程师只写 IR,由适配器翻译到目标引擎。
# IR 示例:高速切入
scene:
id: "NP_CUTIN_HW_001"
category: "L2_NP"
parameters:
ego_speed: {type: float, range: [60, 120], unit: km/h}
target_speed:{type: float, range: [40, 100], unit: km/h}
cutin_dist: {type: float, range: [10, 50], unit: m}
constraints:
- "target_speed < ego_speed"
- "cutin_dist > ego_speed * 0.3"
适配器将 IR 展开为 Carla PythonAPI 或 VTD OpenSCENARIO XML。场景工程师不关心底层引擎差异。
坑二:引擎升级破坏场景一致性
同一个参数组合,仿真引擎小版本升级后结果变了——不是算法退化,是时间步长计算方式改了。
对策:场景 + 引擎版本锁定。 每个场景记录自己验证通过的引擎版本号。CI 检测到版本不匹配 → 自动标记"需重验证",而非直接判 Fail。否则你的告警频道会被假阳性淹没。
覆盖率度量:别被"里程数"骗了
很多团队汇报"仿真里程已突破 xx 万公里"。这是虚荣指标。
我们内部用三个维度衡量:
| 维度 | 指标 | 红线 |
|---|---|---|
| 参数空间覆盖 | 2-way pairwise 覆盖率 | ≥ 95% |
| ODD 覆盖 | ODD 子域 × 场景矩阵 | 无空白象限 |
| 缺陷发现效率 | 每千场景发现的真缺陷数 | ≥ 0.5 |
第三条才是硬指标。场景再多,发现不了缺陷就是废的。我们曾经有一个季度仿真场景数增长了 40%,缺陷发现效率反而从 0.7 降到 0.3——原因是新增的 200 个场景全部来自 L1 法规扩充,对实际算法改进的贡献几乎为零。
总结
仿真场景库的构建本质是一道数学题:用最小的场景集合,覆盖最大的参数空间和缺陷概率。
三个可带走的决策:
- 分层分类,不同层级不同策略——L1 翻译、L2 参数化剪枝、L3 风险采样、L4 人工积累;
- 用 pairwise + 领域约束替代全组合枚举,200 个参数化模板足够覆盖 NP 域;
- 别信里程数,用缺陷发现效率衡量场景库价值,定期做场景有效性审计。
仿真不是用数量堆出来的信心,是用方法论算出来的确定性。
🔖 #智能驾驶 #仿真测试 #场景库 #OpenSCENARIO #自动化测试
📮 下一篇预告:感知算法回归测试平台设计——标注数据集管理、推理结果对比、mAP 自动计算与退化检测,讲清如何设定"可接受的精度波动"和回归拦截红线。