当道路交通安全法修订草案把“自动驾驶违法由车企担责”这个方向摆到台面上时,很多自动驾驶从业者第一反应是法律问题,但真正落地时会发现,这首先是一个工程问题。车企要承担责任,就必须回答三个非常具体的问题:系统当时为什么这么决策?决策依据是否完整可查?整个决策链路能否回放和复现?
本文不展开法律条文分析,而是从自动驾驶工程开发的角度,聊聊在“责任可追溯”这个前提下,算法团队、测试团队、数据团队需要补齐哪些技术能力。文章会覆盖路径规划合理性评估、自动驾驶测试、自动驾驶数据集、ROS 系统日志链路,以及一套可以直接运行的轨迹评估代码,大家可以根据自己的项目阶段按需取用。
1. 背景与核心概念
1.1 责任主体变化带来的技术命题
在过去以“驾驶员负责”为默认假设的交通体系里,辅助驾驶系统只需要提供提示,最终决策责任落在人身上。但当法规方向明确为“车企担责”后,整条技术链路的要求会发生变化:
- 系统需要证明自己在运行设计域(ODD)内运行。
- 每一次规划决策都需要有输入数据和决策逻辑的完整记录。
- 无论最终是车企、算法供应商还是运营方承担赔偿责任,企业内部都要能还原事故前的系统状态。
所以“车企担责”落到技术上,本质上是一套“证据链建设”问题。没有完善的日志、没有可复现的算法版本、没有统一的场景数据集,企业几乎无法进行有效的责任界分。
尤其值得注意的是,L4 级自动驾驶对责任链的要求比 L2/L3 更高。L2 级系统还可以说“驾驶员接管”,L4 级在大多数场景下已经没有人类驾驶员兜底,系统本身就承担了安全主体的角色。这就是为什么行业对“自动驾驶L4代码”中的安全设计如此关注。
1.2 自动驾驶系统开发中的责任链
从技术视角看,自动驾驶系统是一条比较长的链路:
传感器数据采集 -> 感知与融合 -> 预测 -> 路径规划 -> 控制执行 -> 人机交互任何一环出问题,最后的表现都是车辆行为异常。但责任追溯时,需要搞清楚是哪一环先出错。这要求每个模块都满足两个基本条件:
- 输入输出有明确的时间戳和坐标信息。
- 模块版本和配置参数能够被固化、复现。
举个例子,路径规划模块给出了一个急转向指令,表面上看是规划问题。但真实原因可能是感知模块漏检、预测模块给了错误轨迹、或者定位模块坐标跳变。如果没有完整的话题日志和版本记录,排查起来会非常困难。
1.3 自动驾驶测试如何承接责任要求
“车企担责”还带来一个直接影响:测试不再只是为了“让功能跑通”,而是为了证明系统在已知风险场景下的应对能力。
这意味着测试工作要从“验证功能正确性”扩展到“验证系统安全性”,需要重点关注:
- 自动驾驶数据集是否覆盖了足够多的危险场景。
- 路径规划合理性评估是否有量化指标。
- 仿真测试与实车测试是否形成闭环。
- 系统是否有最小风险状态(Minimal Risk Maneuver,MRM)兜底。
接下来,我先把环境准备和核心原理讲清楚,再给出一套代码实践,帮助大家理解“可量化评估 + 可追溯日志”在自动驾驶工程中如何落地。
2. 环境准备与版本说明
2.1 操作系统与基础依赖
自动驾驶开发环境通常会涉及多种语言和工具链,很多同学刚接触时容易在环境上卡住。这里给出一套经过实践验证的组合,大家可以根据实际项目灵活调整版本。
以我日常开发使用的环境为例:
- 操作系统:Ubuntu 20.04 / 22.04。
- 编程语言:Python 3.8+,C++11/14。
- 机器人中间件:ROS 1(Melodic/Noetic)或 ROS 2(Humble 等发行版)。
- 仿真工具:CARLA、SUMO,或公司自研仿真平台。
- 数据科学库:numpy、scipy、matplotlib、pandas。
- 版本管理:Git,配合 DVC 或 Git LFS 管理数据集和模型权重。
- 容器化:Docker,用于同步团队开发环境。
版本不要盲目追求最新,稳定性和团队一致更重要。ROS 2 的不同发行版对 Ubuntu 版本有要求,比如 Ubuntu 22.04 通常搭配 ROS 2 Humble。大家在搭建时注意先查清楚对应关系。
2.2 自动驾驶数据集与仿真平台
要开展自动驾驶测试和算法评估,数据集和仿真平台是绕不开的基础设施。
公开的自动驾驶数据集里,最有代表性的包括 KITTI、nuScenes、Waymo Open Dataset 等。这些数据集主要用于感知模型训练和评测,包含图像、激光雷达点云、毫米波雷达、GPS/IMU 以及 3D 标注框。它们对目标检测、多传感器融合、轨迹预测的研究非常重要。
但需要注意的是,公开数据集通常只能作为算法研究的起点,不能直接用于安全责任论证。原因在于:
- 数据集的场景采集中,危险场景比例普遍偏低。
- ODD 定义与你的量产场景不一定匹配。
- 传感器型号、安装位置和标定参数存在差异。
所以工程团队必须构建自有的自动驾驶数据集,重点收集恶劣天气、夜间、弱势交通参与者、施工区域、无保护转弯等场景。
仿真平台方面,CARLA 是开源场景中比较常用的选择,支持传感器仿真、场景编辑和自动驾驶接口。更贴近产业级的仿真平台往往由企业自研,因为它们需要更高精度的车辆动力学模型、传感器噪声模型和交通流模拟。
2.3 示例工程目录结构
为了让后面的代码演示更清晰,我们先约定一个示例工程目录:
trajectory_eval_demo/ ├── README.md ├── requirements.txt ├── data/ │ ├── trajectories/ │ └── obstacles/ ├── logs/ │ └── planning_logs/ ├── src/ │ ├── models.py │ ├── evaluator.py │ └── main.py └── tests/ └── test_evaluator.py这个目录不算复杂,但已经具备了一个可迭代工程的基本形态。models.py放数据结构定义,evaluator.py放轨迹评估算法,main.py放运行入口。后面实战部分会按这个结构展开。
3. 核心原理:路径规划合理性评估与安全链路
3.1 自动驾驶系统的分层架构
在讨论路径规划之前,先对自动驾驶系统架构做一个简单梳理。从下到上通常可以分成这几层:
- 定位层:融合 GNSS、IMU、里程计、激光雷达/视觉点云,输出车辆位姿。
- 感知层:目标检测、语义分割、跟踪、红绿灯识别等。
- 预测层:对周边交通参与者的未来轨迹进行预测。
- 规划层:包括全局路径规划(Route Planning)、行为决策(Behavior Planning)、局部路径规划(Motion Planning)。
- 控制层:将规划轨迹转换成转向、油门、刹车指令。
路径规划模块处于“感知做完、控制还没执行”的中间位置,它的输入是车辆状态、地图、障碍物和预测轨迹,输出是一系列轨迹点。因此,路径规划是否合理直接决定了车辆行为是否安全、平顺、合规。
在 ROS 系统中,这些模块通常以节点形式运行,通过话题(Topic)通信。比如感知节点发布检测到的障碍物列表,规划节点订阅这些话题并发布规划轨迹,控制节点再订阅轨迹输出控制指令。理解这条通信链很重要,因为责任追溯最终要回到这些话题消息上。
3.2 路径规划合理性如何评估
“路径规划是否合理”,这个问题如果不量化,在工程上基本没法讨论。我们需要把它拆成可计算的指标。我通常从以下五个维度来评估一条候选轨迹:
| 评估维度 | 核心指标 | 说明 |
|---|---|---|
| 安全性 | 碰撞距离、TTC | 轨迹与障碍物的最小距离、预计碰撞时间 |
| 舒适性 | 横向加速度、曲率、加加速度 | 避免急转弯和急加减速 |
| 效率性 | 预计行驶时间、平均速度 | 在安全前提下的通行效率 |
| 可行性 | 最大曲率、速度约束 | 曲率是否超过车辆最小转弯半径 |
| 合规性 | 车道偏离、交规约束 | 是否压线、是否侵入对向车道 |
以安全性为例,轨迹上每个点到最近障碍物的距离,是一条非常直观的评估曲线。如果某个点距离障碍物过近,就需要触发风险提示。以舒适性为例,横向加速度过大往往意味着曲线太急,乘客会有明显不适感。
在自动驾驶测试中,这些量化指标的作用不只是在线上做实时判断,更重要的是离线对大量轨迹做批量评估。测试团队可以把评估结果绘制成指标分布图,和人工经验判断做对照,逐步把“合理”这种主观概念收敛成一套可解释的标准。
3.3 功能安全与预期功能安全
谈到安全性,就不能不提功能安全(Functional Safety)和预期功能安全(SOTIF)。
功能安全主要处理系统故障带来的风险,比如传感器失效、计算单元崩溃、通信中断。汽车行业常用功能安全标准(如 ISO 26262)来指导开发,将安全等级分为 ASIL A 到 D。等级越高,对开发流程和系统冗余的要求越严格,比如 ASIL D 通常需要多重冗余和更强的故障检测能力。
预期功能安全(SOTIF)则处理的是“没有故障但功能表现不符合预期”的情况。典型例子:感知算法在逆光环境下漏检了行人,或者路径规划算法遇到了未见过的路形,生成了不合理轨迹。这类问题不是硬件故障,而是算法能力边界导致的。
从“车企担责”的视角看,功能安全更多回答“系统坏了怎么办”,SOTIF 则回答“系统没坏但不够聪明怎么办”。两者都要求团队在开发过程中不断积累数据、复现问题、迭代算法,并且把每一次问题分析记录成可回溯的知识库。
3.4 ROS 系统中的证据链路
如果团队使用 ROS 或 ROS 2 开发,证据链路的建设会相对清晰,因为 ROS 本身带有话题通信和时间戳机制,但需要工程化利用起来。
一种常见的实践是,在规划节点发布轨迹时,同时发布一份决策日志,完整记录:
- 当前时间戳和车辆位姿。
- 输入障碍物列表(可带唯一 ID)。
- 候选轨迹数量及各项评分。
- 最终选中的轨迹索引。
- 算法版本号和配置文件哈希。
这样每一帧规划输出都自带“为什么会选这条轨迹”的证据。在 ROS 2 中,使用带有 QoS 策略的话题,并开启 rosbag 录制,可以很方便地把感知、预测、规划、控制的消息按时间对齐保存下来。出现问题时,只需要回放对应时间段的 rosbag 数据,就能还原现场。
不过我在这里想提醒一点:录 rosbag 不等于有证据链。如果日志里没有算法版本信息,没有配置文件哈希,回放出来也没有意义。真正重要的是日志内容的完整性和可复现性,而不仅仅是“存了数据”这个动作。
4. 完整实战:构建可追溯的轨迹合理性评估模块
4.1 需求拆解
下面我们实现一个简化但完整的轨迹合理性评估模块。它的定位类似于自动驾驶系统中的“离线评估工具”,用于批量评估规划模块输出的轨迹。
需求如下:
- 支持输入一条轨迹点序列和障碍物列表。
- 计算每个轨迹点到最近障碍物的距离。
- 计算轨迹曲率,评估平滑性。
- 计算安全性评分、舒适性评分、效率性评分和综合评分。
- 输出决策日志,包括输入来源、算法版本、各指标数值,方便后续追溯。
需要提前说明,这是一个教学演示版,不能直接用于量产项目。真实系统中的评估项要复杂得多,比如要区分静态障碍物和动态障碍物、考虑车辆运动学约束、加入预测模块输出等。但结构是一样的,大家可以在此基础上扩展。
4.2 数据结构设计
先创建src/models.py,定义障碍物、轨迹点、安全风险和评估结果的数据结构。
# 文件路径:trajectory_eval_demo/src/models.py from dataclasses import dataclass, field from typing import List, Dict, Any @dataclass class Obstacle: """障碍物描述""" x: float y: float radius: float obj_type: str = "unknown" obj_id: str = "0" timestamp: float = 0.0 @dataclass class TrajectoryPoint: """轨迹点""" x: float y: float speed: float # m/s time: float # s @dataclass class SafetyRisk: """安全风险标记""" min_clearance: float risk_level: str # low / medium / high message: str = "" @dataclass class EvaluationResult: """单个轨迹的评估结果""" trajectory_index: int min_clearance: float max_curvature: float avg_speed: float total_time: float safety_score: float comfort_score: float efficiency_score: float total_score: float risk: SafetyRisk使用dataclass的好处是代码简洁、可读性好,而且后面很容易转成字典或 JSON 输出,适合写日志。
4.3 核心评估算法实现
接下来创建src/evaluator.py,实现曲率计算、最近障碍物距离计算、综合评分和日志组装。
# 文件路径:trajectory_eval_demo/src/evaluator.py import math import json import hashlib from typing import List, Dict, Any from models import Obstacle, TrajectoryPoint, SafetyRisk, EvaluationResult def compute_clearance(point: TrajectoryPoint, obstacles: List[Obstacle]) -> float: """计算轨迹点到最近障碍物的距离(考虑障碍物半径)""" min_dist = float("inf") for obs in obstacles: dist = math.hypot(point.x - obs.x, point.y - obs.y) - obs.radius if dist < min_dist: min_dist = dist return max(min_dist, 0.0) def compute_curvature(points: List[TrajectoryPoint]) -> List[float]: """利用三点法估算轨迹曲率""" curvatures = [] n = len(points) for i in range(n): if i == 0 or i == n - 1: curvatures.append(0.0) continue p0 = points[i - 1] p1 = points[i] p2 = points[i + 1] d1 = math.hypot(p1.x - p0.x, p1.y - p0.y) d2 = math.hypot(p2.x - p1.x, p2.y - p1.y) d3 = math.hypot(p2.x - p0.x, p2.y - p0.y) if d1 < 1e-6 or d2 < 1e-6 or d3 < 1e-6: curvatures.append(0.0) continue # 海伦公式求三角形面积 s = (d1 + d2 + d3) / 2.0 area = math.sqrt(max(s * (s - d1) * (s - d2) * (s - d3), 0.0)) if area < 1e-9: curvatures.append(0.0) continue # 外接圆半径 R = (d1 * d2 * d3) / (4 * area) circumradius = (d1 * d2 * d3) / (4.0 * area) curvatures.append(1.0 / circumradius) return curvatures class TrajectoryEvaluator: """轨迹评估器""" def __init__(self, config: Dict[str, Any]): self.config = config self.safety_weight = config.get("safety_weight", 0.5) self.comfort_weight = config.get("comfort_weight", 0.3) self.efficiency_weight = config.get("efficiency_weight", 0.2) self.min_clearance_threshold = config.get("min_clearance", 1.0) self.max_curvature_threshold = config.get("max_curvature", 0.2) def evaluate( self, trajectory: List[TrajectoryPoint], obstacles: List[Obstacle], trajectory_index: int = 0, ) -> EvaluationResult: clearances = [compute_clearance(p, obstacles) for p in trajectory] min_clearance = min(clearances) if clearances else 0.0 curvatures = compute_curvature(trajectory) max_curvature = max(curvatures) if curvatures else 0.0 speeds = [p.speed for p in trajectory] avg_speed = sum(speeds) / len(speeds) if speeds else 0.0 total_time = trajectory[-1].time - trajectory[0].time if len(trajectory) >= 2 else 0.0 safety_score = self._safety_score(min_clearance) comfort_score = self._comfort_score(max_curvature) efficiency_score = self._efficiency_score(avg_speed) total_score = ( self.safety_weight * safety_score + self.comfort_weight * comfort_score + self.efficiency_weight * efficiency_score ) risk = self._build_risk(min_clearance, max_curvature) return EvaluationResult( trajectory_index=trajectory_index, min_clearance=min_clearance, max_curvature=max_curvature, avg_speed=avg_speed, total_time=total_time, safety_score=safety_score, comfort_score=comfort_score, efficiency_score=efficiency_score, total_score=total_score, risk=risk, ) def _safety_score(self, min_clearance: float) -> float: if min_clearance >= self.min_clearance_threshold: return 100.0 # 低于安全阈值的部分,距离越小扣分越多 return max(0.0, 100.0 * (min_clearance / self.min_clearance_threshold)) def _comfort_score(self, max_curvature: float) -> float: if max_curvature <= self.max_curvature_threshold: return 100.0 return max(0.0, 100.0 * (self.max_curvature_threshold / max_curvature)) def _efficiency_score(self, avg_speed: float) -> float: expected_speed = self.config.get("expected_speed", 8.0) if avg_speed <= 0: return 0.0 return max(0.0, min(100.0, 100.0 * (avg_speed / expected_speed))) def _build_risk(self, min_clearance: float, max_curvature: float) -> SafetyRisk: risky = False messages = [] if min_clearance < self.min_clearance_threshold: risky = True messages.append(f"min_clearance={min_clearance:.2f}m < {self.min_clearance_threshold}m") if max_curvature > self.max_curvature_threshold: risky = True messages.append(f"max_curvature={max_curvature:.3f} > {self.max_curvature_threshold}") if not risky: return SafetyRisk(min_clearance=min_clearance, risk_level="low", message="ok") risk_level = "high" if min_clearance < 0.3 else "medium" return SafetyRisk( min_clearance=min_clearance, risk_level=risk_level, message="; ".join(messages), ) def build_log( self, result: EvaluationResult, scenario_id: str, algorithm_version: str, source_inputs: Dict[str, Any], ) -> Dict[str, Any]: """组装决策日志,方便后续追溯""" log = { "scenario_id": scenario_id, "algorithm_version": algorithm_version, "source_inputs": source_inputs, "trajectory_index": result.trajectory_index, "metrics": { "min_clearance": round(result.min_clearance, 3), "max_curvature": round(result.max_curvature, 4), "avg_speed": round(result.avg_speed, 2), "total_time": round(result.total_time, 2), "safety_score": round(result.safety_score, 2), "comfort_score": round(result.comfort_score, 2), "efficiency_score": round(result.efficiency_score, 2), "total_score": round(result.total_score, 2), }, "risk": result.risk.__dict__, } log_str = json.dumps(log, sort_keys=True, ensure_ascii=False) log["log_hash"] = hashlib.sha256(log_str.encode("utf-8")).hexdigest() return log这里有两个小细节值得说一下。
第一,曲率计算用了三点外接圆法,比直接对角度做差分更稳健。对于轨迹点过于稀疏或三点共线的情况,代码做了保护。
第二,build_log方法把评估结果组装成结构化日志,并通过log_hash对日志内容做哈希。这样做的目的是防止日志被无意篡改,在责任追溯时能快速确认数据完整性。
4.4 决策日志与证据输出
刚才的代码里已经生成了结构化日志。实际项目中,build_log的输出还需要补充几个字段:
- 车辆位姿序列。
- 感知模块输出的原始障碍物列表。
- 预测模块给出的动态障碍物预测轨迹。
- 算法版本、地图版本、标定参数版本。
- 系统运行模式(手动/辅助/自动驾驶)。
这些信息在 ROS 系统中可以通过订阅对应 topic 获取。在离线评估场景中,则从 rosbag 或数据库里读取。日志建议使用 JSON 格式,因为 JSON 可读性好、跨语言兼容,也方便后续接入数据分析平台。
4.5 运行与验证
最后创建src/main.py,模拟两条候选轨迹的评估过程并打印结果。
# 文件路径:trajectory_eval_demo/src/main.py import json from models import Obstacle, TrajectoryPoint from evaluator import TrajectoryEvaluator def build_trajectory(): """构造一条简单轨迹""" points = [] for i in range(11): t = i * 0.5 x = i * 0.8 y = 0.5 * math.sin(i * 0.3) speed = 5.0 points.append(TrajectoryPoint(x=x, y=y, speed=speed, time=t)) return points def build_obstacles(): return [ Obstacle(x=4.0, y=0.6, radius=0.5, obj_type="pedestrian", obj_id="obj_001"), Obstacle(x=6.5, y=-0.4, radius=0.8, obj_type="vehicle", obj_id="obj_002"), ] def main(): import math # noqa: F401 obstacles = build_obstacles() trajectory = build_trajectory() estimator = TrajectoryEvaluator( config={ "safety_weight": 0.5, "comfort_weight": 0.3, "efficiency_weight": 0.2, "min_clearance": 1.0, "max_curvature": 0.2, "expected_speed": 5.0, } ) result = estimator.evaluate(trajectory, obstacles, trajectory_index=0) log = estimator.build_log( result=result, scenario_id="scenario_demo_001", algorithm_version="v0.1.0", source_inputs={ "trajectory_points": len(trajectory), "obstacles": [obs.obj_id for obs in obstacles], }, ) print(json.dumps(log, indent=2, ensure_ascii=False)) if __name__ == "__main__": main()运行方式:
cd trajectory_eval_demo/src python main.py预期输出类似于下面的 JSON:
{ "algorithm_version": "v0.1.0", "log_hash": "8f2c...", "metrics": { "avg_speed": 5.0, "comfort_score": 100.0, "efficiency_score": 100.0, "max_curvature": 0.0032, "min_clearance": 0.73, "safety_score": 73.0, "total_score": 84.6, "total_time": 5.0 }, "risk": { "message": "min_clearance=0.73m < 1.0m", "min_clearance": 0.73, "risk_level": "medium" }, "scenario_id": "scenario_demo_001", "source_inputs": { "obstacles": ["obj_001", "obj_002"], "trajectory_points": 11 }, "trajectory_index": 0 }从输出里能看到,这条轨迹的最小安全距离是 0.73 米,低于我们设置的 1 米阈值,所以风险等级是 medium,综合评分也受到影响。这个结果说明,评估模块成功地把“轨迹是否合理”转化成了可比较的数值和风险等级。
大家可以把代码拷贝到本地运行,然后试着改一下障碍物位置或轨迹曲线,观察评分变化。多跑几组数据之后,对评估逻辑的理解会更直观。
5. 常见问题与排查思路
5.1 传感器时间戳不同步
在自动驾驶系统中,不同传感器的时间戳往往来自不同时钟域。相机可能用图像采集时间,激光雷达可能用旋转周期中的触发时间,惯性导航又是另一套时间。如果不对齐时间,规划模块可能把不同时刻的障碍物当作同一时刻处理。
排查时,先检查各传感器的时间戳来源和同步策略。常见方案是使用硬件同步信号(PPS、GPRMC)加软件时间同步,统一到主控系统时间轴。在 ROS 中可以使用message_filters的ApproximateTimeSynchronizer做近似时间对齐。
5.2 坐标变换不一致
坐标变换是自动驾驶开发里非常容易出错的环节。障碍物在感知模块里可能是相机坐标系、激光雷达坐标系或车辆坐标系,而路径规划模块通常需要全局坐标系。一旦外参标定或坐标变换发布有误,规划模块看到的障碍物位置就是错的,轨迹自然不合理。
排查步骤是先固定一个基础坐标系,比如车辆后轴中心坐标系或 UTM 全局坐标系,然后逐层打印每个模块输入、输出的坐标系,和标定表核对。更稳妥的做法是在每个关键 topic 的消息里同步发布坐标参考信息。
5.3 路径规划“合理”缺乏量化标准
很多团队在早期只能靠人工观察 Rviz 上的轨迹,主观地判断“看起来还可以”。这种做法无法支撑责任追溯,因为不同人的标准不一样。
建议尽早建立轨迹评估指标体系,从安全距离、曲率、加速度、预计时间等维度给出可计算、可比较的数值。离线批量评估时,还可以把全量轨迹的指标分布画出来,和专家标注结果做一致性对比。
5.4 日志记录不完整
有的系统虽然录了 rosbag,但没有保存算法版本和配置文件。几个月后回放时,根本不知道这段轨迹是哪一版算法跑出来的,责任链就断了。
解决方案是在每次测试或运行时,把算法版本、配置文件哈希、地图版本、标定版本作为全局字段写入日志。版本和代码仓库的 commit 一一对应,配置文件生成哈希后入库。这样任何一个日志包都能快速定位到可复现的代码状态。
5.5 仿真场景与真实场景差异大
仿真测试覆盖度高、成本低,但仿真和真实世界永远存在差距。如果完全依赖仿真,容易出现系统在仿真里表现良好、一上实车就出问题。
推荐做法是建立仿真与实车的双向闭环:仿真发现的问题要形成回归用例,实车发现的问题也要提取场景、迁移到仿真中复现。同时要持续积累自动驾驶数据集中真实场景的比例,尤其是危险场景、长尾场景。
下面是常见问题的快速排查表:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 轨迹突然转向 | 感知漏检或预测轨迹跳变 | 优先回放感知与预测话题,检查时间同步 |
| 规划轨迹压线严重 | 全局地图坐标漂移 | 核对定位模块和地图匹配结果 |
| 评估分数不可复现 | 日志缺少算法版本信息 | 日志统一记录代码版本和配置哈希 |
| 仿真正常、实车异常 | 传感器噪声模型不逼真 | 增加传感器噪声、延迟和失真的仿真建模 |
| 责任界定困难 | 各模块日志分散、格式不一致 | 建立统一日志规范,按场景 ID 关联 |
6. 工程最佳实践与责任链建设
6.1 数据采集与归档规范
数据是自动驾驶责任链的基础。建议团队从三个层面规范数据工作:
- 原始数据层:保存传感器原始数据、rosbag、车辆 CAN 数据。
- 场景数据层:从原始数据中抽取并标注典型场景,设计场景 ID 和标签体系。
- 指标数据层:保存每一次规划决策的评估结果、决策日志和版本信息。
数据归档一定要注意时间维度和版本维度。简单说,就是知道“什么时候采集的”“哪一版系统采集的”。使用 Git LFS 或对象存储都可以,关键是有一套清晰的命名和索引规范。
6.2 最小风险状态设计
L4 级自动驾驶系统必须认真考虑最小风险状态设计。当系统检测到超出处理能力的情况,比如感知置信度过低、定位精度超限、冗余系统故障,应该主动进入安全状态,而不是继续行驶直到问题爆发。
常见的最小风险策略包括:
- 减速并靠边停车。
- 开启危险报警闪光灯,通知监控中心。
- 通过车机和人机交互系统向乘客说明状态。
- 在满足安全条件后,等待远程接管或人工介入。
“车企担责”的背景下,最小风险状态触发的时机和触发原因必须被记录。判断是否合理,同样依赖清晰的日志。
6.3 可复现与版本管理
可复现性是责任链建设的核心。每次运行系统时,应该能够回答三个问题:
- 代码是哪个 commit 构建的?
- 模型权重是哪个版本?
- 配置参数和地图数据是否一致?
建议在系统启动时,把上述信息打印到启动日志中。如果使用 Docker 镜像,还可以在镜像标签中固化版本信息。这样当一次事故出现后,便能快速重建当时的运行环境,做离线复现。
6.4 从数据集到测试闭环
很多团队做自动驾驶数据集和测试规划是分开的,这是比较大的问题。数据集采集时没有明确目标,测试时又发现场景不够用。
比较好的实践是,先根据安全分析结果列出风险场景清单,比如“夜间横穿行人”“逆光红绿灯识别”“施工区域变道”。然后针对这个清单去采集和构造数据,再把这些数据导入仿真平台做测试。测试中发现的新问题,再次补充到场景清单中,形成闭环。
这样自动驾驶数据集会越来越贴近真实风险,而不是只追求数据量的大小。
6.5 团队流程与工具建设
最后提一个容易被忽视的点:工具链要跟上。没有统一的回放工具、评估工具和管理平台,流程规范很难落地。
自动化测试方面,可以搭建一个定时任务,每天对新版本算法跑仿真回归测试,自动输出评估报告并发送到相关群组。评估报告至少包含:场景分布、通过率、安全指标分布、与上一个版本的对比。这些数据积累下来,团队对系统安全性的认知会越来越清晰,应对责任追溯时也能拿出客观依据。
7. 总结与学习路线
7.1 核心收获
本文从一个备受关注的法规方向切入,重点讨论了自动驾驶工程侧如何应对“责任可追溯”的要求。读完本文,你应该掌握了以下关键点:
- 路径规划合理性可以从安全性、舒适性、效率性、可行性、合规性五个维度量化。
- 自动驾驶测试不仅是验证功能,还要验证系统在风险场景下的应对能力。
- 自动驾驶数据集的场景覆盖度直接影响安全评估结果。
- 完整的日志和版本管理是责任链建设的前提。
- 最小风险状态设计是 L4 级自动驾驶系统的重要安全兜底。
代码部分,我们实现了一个轨迹合理性评估模块,包含曲率计算、安全距离计算、综合评分、风险等级判定和结构化日志输出。这套结构可以直接应用到工程项目中,后续可以按需增加动态障碍物、预测轨迹、车辆运动学约束等模块。
7.2 推荐学习路径
如果对自动驾驶安全工程方向感兴趣,可以按下面的路径逐步深入:
- 第一阶段:学习 ROS / ROS 2,掌握话题、节点、tf 坐标变换、rosbag 录制与回放。
- 第二阶段:学习自动驾驶数据集的使用,跑通一个感知模型的训练和验证流程。
- 第三阶段:学习运动规划基础,了解路径规划、轨迹规划和速度规划的区别,以及在仿真中做测试。
- 第四阶段:学习功能安全和预期功能安全的核心概念,结合实际项目做 HARA 分析或 SOTIF 分析。
- 第五阶段:参与实车测试或高保真仿真项目,把日志规范、版本管理、指标评估落地到工程流程中。
7.3 下一步行动建议
如果读完本文后想在项目中做一些改进,可以从下面几个小动作开始:
- 检查当前系统的日志目录,确认是否包含算法版本、配置哈希和输入数据的来源信息。
- 在已有路径规划模块中接入一个简单的轨迹评估器,把安全距离和曲率指标输出到日志中。
- 用公司历史 rosbag 数据离线跑一遍评估脚本,看看能不能自动识别出高风险轨迹。
这几个动作看起来不大,却是把“车企担责”这个外部要求转化为内部工程能力的第一步。自动驾驶行业还在快速演进,法规会不断完善,但无论规则如何变化,扎实的工程数据、清晰的评估标准和完整的责任链路,始终是团队最可靠的技术资产。