从模块化到端到端:范式为什么转向

早期的自动驾驶系统是高度模块化的:感知负责识别世界,预测负责推测他人意图,规划负责选出一条路径,控制负责把路径变成方向盘转角与油门刹车。这套流水线结构清晰、便于分工与调试,至今仍是多数量产系统的骨架。

但模块化也带来了一个根本矛盾——信息在模块之间被反复压缩。感知把丰富的原始信号压成一份有限的目标列表,规划再基于这份列表做决策;一旦上游漏检,下游几乎没有回旋余地。端到端方案试图用一个大网络直接完成从传感器输入到驾驶动作的映射,让梯度能够从最终的控制目标回传到感知特征上。

端到端不是要否定模块化,而是把如何抽象世界这件事,从人工设计的接口交还给数据驱动的表示学习。它换来了更少的接口误差,也换来了更难解释的黑盒。
本文核心知识点
  • 模块化流水线感知、预测、规划、控制依次串联,接口清晰、可解释性强,是量产系统的基础。
  • 端到端方案用统一网络直接从传感器输入回归规划轨迹或控制量,减少中间接口的信息损失。
  • 车路协同 V2X通过车与车、车与路侧单元的信息交换,扩展单车的感知视野。
  • 仿真与测试用场景库、闭环仿真与实车路测分层验证,是自动驾驶上线前的必要关卡。
端到端自动驾驶示意:侧前方视角的流线型汽车,车身内部浮现发光神经网络节点,一条点状轨迹从车头延伸,右侧路侧通信塔发出同心信号波纹
端到端与车路协同:让一次前向推理同时承担感知与决策,并借助路侧设施突破单车视野边界。

模块化流水线拆解:感知-预测-规划-控制

在讨论端到端之前,有必要把经典流水线拆开看清楚,因为端到端的评估标准,恰恰来自这条流水线本应交付的东西。

  • 感知输出带朝向的三维目标、车道线与可通行空间,是后续所有推理的事实来源。
  • 预测为每个动态目标生成未来数秒的轨迹分布并给出概率,供规划评估风险。
  • 规划在满足动力学约束、交通规则与舒适性的前提下,选出一条可执行的轨迹。
  • 控制把轨迹转成横向与纵向指令,跟踪误差与乘坐舒适性是核心指标。

模块化的优势是每一层都能单独评测与定位问题,劣势是接口设计一旦不合理,误差会沿链路累积。工程上一个常见做法是让模块之间传递带不确定性的输出,而不是单点估计,给下游留出保守决策的空间。

端到端模型:从传感器输入直接输出轨迹

端到端模型的基本形态,是把多路相机(有时还包括激光雷达与雷达)的特征编码进一个共享的 BEV 或 Transformer 主干,再由一个规划头直接输出未来一段时间内的自车轨迹,或输出轨迹上的离散路点。整个前向过程可以写成一次干净的张量运算:

import torch

class DrivePolicy(torch.nn.Module):
    def __init__(self, bev_channels=256, num_modes=6):
        super().__init__()
        self.backbone = BEVBackbone(in_dim=3, out_dim=bev_channels)
        self.temporal = TemporalFusion(dim=bev_channels)
        self.plan_head = torch.nn.Linear(bev_channels, num_modes * 12 * 2)

    def forward(self, cams, prev_bev=None):
        feat = self.backbone(cams)              # 多相机编码为共享特征
        bev = bev_pool(feat, cams.intrinsics)   # 特征抬升到 BEV 平面
        bev = self.temporal(bev, prev_bev)      # 时序融合,维持短时遮挡下的稳定
        out = self.plan_head(bev.mean(dim=(2, 3)))
        trajectories = out.view(-1, 6, 12, 2)   # 6 条候选轨迹,各 12 个路点
        scores = score_head(bev)                # 每条候选轨迹的置信度
        return trajectories, scores

相比模块化流水线,端到端最明显的变化是训练信号:梯度可以直接来自轨迹是否安全、是否舒适、是否合规,而不必经过人工定义的目标列表。这让模型有机会学到一些难以用规则写出来的驾驶常识,代价则是可解释性下降——当模型做出一个奇怪决策时,很难像模块化系统那样一眼定位是哪一层出了问题。

规划与控制:轨迹生成与横纵向控制

无论前端是模块化还是端到端,最终都要落到开得稳这件事上。规划通常分两层:先在粗略的时空里搜索一条语义级路径,决定走哪条车道、在什么位置变道;再在局部做平滑的轨迹优化,满足曲率、加速度与加加速度约束。控制则负责把轨迹变成执行器指令,常用的几何方法有纯追踪,优化方法有模型预测控制。

import math

def pure_pursuit(pose, path, wheelbase, lookahead=6.0):
    # pose: 当前车辆位姿 (x, y, yaw);path: 待跟踪的路点序列
    target = None
    for p in path:
        if distance(pose, p) >= lookahead:
            target = p
            break
    if target is None:
        return 0.0, 0.0

    # 前视点在车辆坐标系下的横向偏差
    dx = target.x - pose.x
    dy = target.y - pose.y
    local_x =  math.cos(pose.yaw) * dx + math.sin(pose.yaw) * dy
    local_y = -math.sin(pose.yaw) * dx + math.cos(pose.yaw) * dy

    curvature = 2.0 * local_y / (lookahead ** 2)   # 圆弧拟合得到曲率
    steer = math.atan(curvature * wheelbase)       # 由曲率反推前轮转角
    speed = max_speed_for(curvature)               # 曲率越大,允许的速度越低
    return steer, speed

控制环节最容易被忽视的是延迟补偿。从传感器采样、网络推理到执行器响应,整条链路可能有上百毫秒的滞后;如果控制器不对此建模,高速行驶时就会表现为画龙或跟车距离抖动。这也是端到端方案必须与底盘、执行器联合标定的原因。

车路协同 V2X:让车看见看不见的地方

单车的传感器再强,也受物理遮挡与视距限制:路口拐角后的来车、被大车挡住的非机动车、前方远处的异常停车,都是单车感知的天然盲区。车路协同(V2X)的思路,是通过车与车、车与路侧单元、车与行人之间的低时延通信,把别人看到的世界分享给自车。

# 车路协同场景配置:路口盲区来车预警
scenario: intersection_blind_spot_warning
map:
  intersection_id: "IC-0217"
  approach_lanes: 4
  speed_limit_kph: 50
rsu:
  id: "RSU-0217-A"
  position: [116.31, 39.98]      # 经纬度
  coverage_radius_m: 300
  broadcast_hz: 10
vehicle:
  ego_speed_kph: 45
  perception_radius_m: 80
  v2x_enabled: true
message:
  type: BSM                    # 基础安全消息
  fields: [position, speed, heading, brake_status]
  max_latency_ms: 100
safety_check:
  require_time_sync: true
  fallback_on_no_v2x: [reduce_speed, increase_headway]

V2X 的工程难点在于时空一致性:一条来自路侧单元的消息,必须能和自车当前时刻、当前位置对齐才有意义。因此实际部署中要处理时钟同步、坐标系转换、消息到达时延与丢包,以及最棘手的多方数据冲突时该信谁。这也让 V2X 更像一个分布式的感知融合问题,而不是简单的消息广播。