简介:智能驾驶功能软件平台设计规范第三部分聚焦预测功能服务接口,适用于开发L2级及以上智能驾驶系统的主机厂、算法供应商及系统集成方。预测功能基于摄像头、雷达、激光雷达等感知数据,对交通参与者的可能行为和未来轨迹进行预估,是决策规划模块制定驾驶策略的重要依据。规范定义了行为预测与轨迹预测两类服务接口,并配套标准元数据头、行为预测数据、轨迹预测数据、单个交通参与者轨迹预测数据及轨迹点信息等数据结构,附录A还给出详细的接口与数据描述,便于开发者直接参考实现。资源为单个PDF文档,压缩包约909KB,目前已有126人学习。通过标准化预测接口,可降低预测模块与决策规划模块的对接成本,支持按需拼插算法组件,为提升智能驾驶系统的安全性与可靠性提供设计依据。

1. 预测功能服务接口:为什么它决定智驾平台的扩展边界

在功能软件平台的落地场景里,预测模块往往是最后一个被认真设计的服务。感知给了障碍物列表,规划催着要未来轨迹,预测夹在中间,输出格式稍微变一下,上下游都要跟着改。更麻烦的是,预测算法更新频繁:从规则模型切到学习模型,输入要加地图特征,输出要带多模态轨迹和概率,接口如果定死了,整个平台尝不到算法迭代的红利。这篇规范要解决的,不是预测算法本身,而是把预测能力封装成一个稳定、可度量、可替换的服务接口。适合做软件架构、通信中间件、功能集成的工程师。读完能明确接口怎么切分、消息怎么定义、调用怎么调度、性能怎么验证,让预测成为平台里一块可以独立演进又随时可插拔的组件。

2. 预测功能服务接口的总体框架与数据流设计

2.1 接口在功能软件平台中的分层定位

功能软件平台一般按“感知 - 预测 - 规划 - 控制”分层,预测服务接口处于感知融合与决策规划之间。它在软件架构里属于原子服务层,往下依赖感知结果和地图数据,往上服务规划模块。接口设计的第一件事是划清边界:预测服务的输入是“已经完成融合的目标列表”,而不是原始点云或图像特征;输出是“目标未来的轨迹假设集”,而不是最终的驾驶决策。边界划清楚后,预测模块的输入输出协议就可以独立于感知算法和规划策略演进。

从工程落地看,接口的分层定位还决定了消息传输方式。预测输入和输出的数据量都不大,单帧目标几十到几百个,轨迹点上千个,总数据量通常在几十KB以内。常见做法是采用共享内存或本地IPC通信,时延可以控制在几毫秒量级;跨域部署时才走SOME/IP或DDS。设计规范里最好把底层通信方式隐去,接口定义只暴露业务数据本身,这样后续从IPC切换到DDS,上下游代码不需要改动。

2.2 输入数据流与输出数据流的聚合规则

预测服务的数据流设计,核心是一个时序对齐问题。感知模块的帧率是20Hz或30Hz,地图服务是异步更新,预测服务却需要拿到同一时间戳下的目标状态和地图切片。如果每个模块各发各的,预测端做时间同步会非常痛苦。因此接口规范里要让上游在输入消息内统一带 timestamp_ms 字段,并要求感知融合模块在发布目标列表时,把地图服务最近一次更新的版本号一并带上,预测服务可以据此判断数据新鲜度。

输出数据流的聚合也要提前定义。预测结果不是简单拼接每个目标的轨迹,而是按场景聚合:同一时刻、同一区域内的多个目标预测,要打包在一个 PredictionOutput 里,方便规划模块做联合评估。如果预测服务采用多模型并行推理,每个模型产出的结果也要在输出层合并,按目标ID对齐后再发布。聚合规则里最容易被忽略的是重复目标的覆盖逻辑:当感知融合模块因遮挡短暂丢失某个目标时,预测服务应当在输出里保留该目标的最新轨迹,并标记 is_valid=false ,而不是直接删除,否则规划模块的行为会突然跳变。

2.3 接口分组:按场景与按对象的两条切分线

预测功能服务接口虽然只有一个对外入口,但内部接口应该按两条维度分组。第一条是场景维度:高速巡航、城区路口、拥堵跟驰、换道博弈,不同场景的预测模型输入特征差异巨大,输出轨迹的长短和条数也不一样。接口设计上要预留 scenario_id 字段,让上游可以指定预测场景,也可以不指定,由预测服务内部做场景识别。

第二条是对象维度:车辆、行人、自行车、异形障碍物。不同对象的运动学模型不同,车辆用自行车模型拟合,行人用意图驱动模型。接口的 TargetType 枚举需要从规范层固化下来,同时允许算法在内部做细分。这里的关键是既不能把类型枚举定得太细(否则感知端无法稳定填充),也不能太粗(否则预测算法无法复用)。工程上常见的做法是规范里只定义大类,预测服务内部再做子类映射,接口层保持稳定。

3. 预测功能服务接口的核心数据结构与消息规范

3.1 用 Protobuf 定义接口:三个核心消息组

接口数据结构建议用 Protobuf 定义,它是车载和智驾工程里最常见的选择。相比 JSON,Protobuf 有强类型约束、字段编号稳定、序列化紧凑,适合高频传输。整个接口规范可以拆成三组消息:输入组 PredictionInput 、输出组 PredictionOutput 、以及中间共享的类型组 TrackedTarget 、 PredictedTrajectory 等。共享类型单独建文件是为了让输入和输出都能复用同一套类型定义,避免重复。

syntax = "proto3";

package ad.prediction.v1;

// 预测服务输入消息
message PredictionInput {
  uint64 timestamp_ms = 1;             // 输入帧时间戳,单位毫秒
  repeated TrackedTarget targets = 2;  // 待预测目标列表,由感知融合模块发布
  LaneMap lane_map = 3;                // 局部车道级地图,用于模型推理
  uint64 map_version = 4;              // 地图版本号,用于数据新鲜度校验
}

// 感知融合输出的单个目标
message TrackedTarget {
  uint64 target_id = 1;
  TargetType type = 2;
  KinematicState state = 3;            // 目标当前运动状态
  repeated KinematicState history = 4; // 最近1秒的历史轨迹
}

// 运动状态,坐标统一使用车体坐标系
message KinematicState {
  double pos_x = 1;
  double pos_y = 2;
  double heading = 3;                  // 航向角,单位弧度
  double velocity = 4;                 // 速度,单位m/s
  double acceleration = 5;             // 加速度,单位m/s^2
}

enum TargetType {
  TARGET_TYPE_UNKNOWN = 0;
  TARGET_TYPE_VEHICLE = 1;
  TARGET_TYPE_PEDESTRIAN = 2;
  TARGET_TYPE_CYCLIST = 3;
}

代码里的时间戳字段是整个消息组的时间基准。 timestamp_ms 在输入和输出消息中必须存在,且建议统一使用单调时钟的时间,避免跨模块时系统时间跳变导致数据乱序。 map_version 字段初学者经常漏掉,实际在接口设计与联调阶段,这个字段是排查“地图更新了但预测结果没变”这类问题最直接的抓手。

3.2 输入消息的字段设计与单位约束

输入消息最容易被质疑的一个问题是:为什么不在输入里直接传栅格地图或高精地图元素,而是传 LaneMap ?从接口规范视角看,预测服务不应该依赖具体的地图格式。 LaneMap 在规范里定义为消息类型,内部只包含当前车周围的车道中心线、车道边界和关联关系,相当于对高精地图做了一次裁剪视图。

单位约束是输入设计里必须写死的内容。 pos_x 、 pos_y 使用车体坐标系,单位统一为米; heading 使用弧度,角度范围 -pi 到 pi ; velocity 和 acceleration 分别使用 m/s 和 m/s^2。实践里最常见的错误是某条链路上传的数据是 km/h,预测出的轨迹偏大或偏小,排查半天才发现是单位问题。规范里应该在字段注释和接口文档中反复强调单位,并在代码里加校验层,对超过物理极限的数值打错误日志。

输入消息里另一个容易忽略的是 history 字段。历史轨迹的稠密程度决定了预测模型是使用单帧状态还是时序序列。规范建议感知融合模块提供至少 1 秒的历史轨迹,采样间隔与感知帧率一致。字段上限要预设,避免出现内存竞争问题;设计上可以规定最多保留 30 个历史点,超出则丢弃更早的点,这样模型输入维度固定,推理时不需要动态处理变长序列。

3.3 输出消息的轨迹表示与置信度表达

输出消息是规划模块直接消费的数据,设计不当会直接抑制规划效果。 PredictionOutput 里最关键的是多轨迹表达:每个目标输出 1 到 N 条候选轨迹,每条轨迹带一个 probability ,所有轨迹概率之和不超过 1。多轨迹语义是整个预测接口设计中最难的点:它要求接口使用方理解“目标可能向左也可能向右”,而不是“目标最有可能向左”。

// 预测服务输出消息
message PredictionOutput {
  uint64 timestamp_ms = 1;
  repeated PredictedTarget predicted_targets = 2;
}

message PredictedTarget {
  uint64 target_id = 1;
  uint32 scenario_id = 2;                 // 场景类型,0表示未知
  repeated PredictedTrajectory trajectories = 3;
}

message PredictedTrajectory {
  double probability = 1;                 // 该条轨迹的概率,取值范围0.0~1.0
  repeated TrajectoryPoint points = 2;    // 轨迹点序列,按时间排序
}

message TrajectoryPoint {
  double pos_x = 1;                       // 位置,单位米
  double pos_y = 2;
  double heading = 3;                     // 航向角,单位弧度
  double velocity = 4;                    // 期望速度,单位m/s
  uint64 relative_time_ms = 5;            // 相对于timestamp_ms的偏移,单位毫秒
}

轨迹点的语义要明确是“期望状态”还是“预测状态”,规范建议定义为预测期望值。 relative_time_ms 在历史设计里常被省略,规划模块只能用等间隔假设,一旦轨迹点的时间戳不均匀,后续速度插值就会出错。此外,规范应该约定输出轨迹的时间长度。高速场景建议 8 秒(覆盖一次变道和超车需要的规划时域);城区场景建议 5 秒即可,因为路口内目标状态变化快,预测时间再长可靠度也低。

4. 预测功能服务接口的调用机制、调度策略与性能约束

4.1 同步与异步:预测服务适合的调用模型

预测服务接口的调用模型基本在同步 RPC 和异步发布/订阅之间选。预测的计算时延通常在 10~50ms 之间,它在整个决策链路里处于“在确定窗口内必须返回结果”的位置。许多落地项目最终选择同步 RPC 或共享内存加请求响应的方式,因为规划模块需要在当前决策周期内拿到预测结果;如果走异步订阅,规划要等下一帧,相当于整体推迟一拍,这在高速场景下不可接受。

同步调用模型也更方便做超时控制和结果降级。请求方发出请求后,预测服务必须在指定时间内返回,否则按超时处理。RPC 框架天然支持 deadline 机制,实现成本比异步方案低。实际工程中如果预测服务部署在独立进程中,优先级高的规划任务可以通过接口把请求的 QoS 等级带过去,让预测服务内部优先调度。

4.2 调度周期与资源竞争:接口的响应时间约束

设计接口规范时,预测服务的调度周期和规划服务是绑定的。规划模块运行频率是 20Hz(50ms 一个周期)时,预测服务的响应时间必须显著小于 50ms,一般要求 P99 在 35ms 以内,预留 15ms 给规划本身、通信和调度抖动。如果预测模型用的是深度学习网络,GPU 推理独占时间可能就要 15ms,CPU 前处理和地图查询需要压缩在 10ms 内。那么这个预算是否可行,取决于地图查询裁剪和特征编码的实现效率。

接口设计在性能约束这块,建议把服务端的线程模型和并发策略写进规范。预测服务如果是 CPU 推理,通常使用多线程同时跑多个目标;如果是 GPU 推理,建议把同帧的目标打包成一个 batch。输入消息里的 targets 是一个 repeated 字段,恰好方便服务端做 batch 组装。规范里可以约定单帧目标数量上限(例如 128 个),超过上限要截断并告警,防止极端场景下推理时延膨胀。

4.3 超时、降级与错误码:有损场景下的统一处理

自动驾驶系统中,预测服务偶尔超时或推理失败是常态。接口规范必须定义一个明确的降级策略。常用做法是:预测服务在上一帧输出结果基础上外推一个周期,得到“衰减轨迹”并返回;轨迹的置信度按时间衰减系数乘以 0.85 左右,同时打上 degraded=true 标记。请求方收到该标记后,规划模块主动降低对该目标预测的置信依赖,保留更大的安全距离。

// 预测服务接口的同步调用示例
// 使用 gRPC 的 ClientContext 设置超时,并处理降级结果

#include <memory>
#include <chrono>
#include "ad/prediction/v1/prediction_service.grpc.pb.h"

using ad::prediction::v1::PredictionInput;
using ad::prediction::v1::PredictionOutput;

class PredictionClient {
 public:
  explicit PredictionClient(std::shared_ptr<grpc::Channel> channel)
      : stub_(PredictionService::NewStub(channel)) {}

  // 同步调用预测接口,超时时间为 35ms
  // 如果超时或调用失败,返回上一帧的有效预测结果
  PredictionOutput PredictWithFallback(const PredictionInput& input) {
    PredictionOutput output;
    grpc::ClientContext context;

    // 设置调用超时:35ms,与规划周期50ms匹配
    context.set_deadline(std::chrono::system_clock::now() +
                         std::chrono::milliseconds(35));

    grpc::Status status = stub_->Predict(&context, input, &output);

    // 调用失败时,返回上一帧结果
    // 注意:std::chrono::steady_clock::now()
    // 计算上一帧年龄,用于衰减轨迹置信度
    if (!status.ok()) {
      auto age_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
                        std::chrono::steady_clock::now() - last_timestamp_)
                        .count();
      output = last_output_;
      output.set_degraded(true);
      // 按时间衰减修改轨迹概率,衰减系数0.85
      for (auto& target : *output.mutable_predicted_targets()) {
        for (auto& traj : *target.mutable_trajectories()) {
          traj.set_probability(traj.probability() * 0.85);
        }
      }
    } else {
      last_output_ = output;
      last_timestamp_ = std::chrono::steady_clock::now();
    }
    return output;
  }

 private:
  std::unique_ptr<PredictionService::Stub> stub_;
  PredictionOutput last_output_;
  std::chrono::steady_clock::time_point last_timestamp_;
};

代码里的超时时间 35ms 不是随意取的,要配合规划周期与内部时间预算来定。如果规划周期是 100ms,超时可放宽到 70ms;如果预测服务内部使用 GPU 推理且目标数超过 64 个,超时要适当加大。 degraded 标志位应在消息定义中显式增加,这样双方协议清晰。

错误码设计也要在规范里固化。不要使用业务自定义的负整数,建议统一走 RPC 的 status code,在服务内部把异常分为地图数据过期、输入目标数超限、推理引擎失败三类,分别在客户端日志里暴露。工程上常犯的错误是利用 result_code 来表达算法层的不确定性,这必然会导致两个方向重复且混乱。

4.4 QoS 参数与部署形式对照表

接口调用的性能参数需要一张表来做基线约束。以下是设计规范中直接推荐的一档参数,供落地时根据计算平台和传感器配置调整。

参数项 推荐值 说明
调度频率 20 Hz 与规划模块运行频率保持一致
预测时间窗 8 秒 高速场景覆盖换道与避障需求
轨迹采样间隔 0.2 秒 40 个轨迹点,可平衡带宽与精度
RPC 超时时间 35 ms P99 约束,包含排队与推理
单帧目标数上限 128 个 超出后丢弃多余目标并记录告警
降级策略 上一帧外推+概率衰减 衰减系数 0.85,超时超过 200ms 则清空输出

表中的“调度频率”直接决定接口的触发逻辑。预测服务是事件触发,即感知模块每发布一帧输入就触发一次计算,而不是按固定时间片轮询。事件触发的优点是天然对齐数据源,缺点是一旦感知帧率波动,预测负载会随之抖动。可以设计一个前置队列,在 5ms 内做输入对齐,然后立即触发推理,保证 20Hz 调度率不被打乱。

5. 预测功能服务接口的离线验证与参数收敛技巧

5.1 用录制数据回放接口闭环

接口开发完成后,最有效的验证方式是录制一段真实路采数据,离线回放到预测服务中,验证输入构造、输出解析与各字段取值的正确性。回放工具需要把录制好的感知结果和地图数据转换成 PredictionInput ,按原始时间戳间隔发送给预测服务,再校验输出结果时间戳是否单调递增、轨迹点长度是否一致。

# 回放录制数据到预测服务进程,验证接口稳定性
# input.bin 为录制的 PredictionInput 序列化文件
./prediction_replay \
  --input_file=/data/recordings/20250410_shanghai_0420.bin \
  --output_file=/data/recordings/result_out.bin \
  --max_frames=500 \
  --print_interval=50

回放命令中 print_interval=50 表示每处理 50 帧打印一次统计信息,包含平均推理时延和轨迹条数。这一步能立刻发现两个常见问题: map_version 更新后输入消息是否在前 5ms 内生效;目标 history 序列长度是否统一。规范里可以约定,回放验证必须作为接口变更的准入条件,任何字段新增或语义修改都要通过回放回归。

5.2 轨迹精度与接口稳定性的度量方式

接口是否满足设计要求,重点是看轨迹精度在回放和实车上的稳定性。轨迹精度有三个常用指标:位置误差计算每个轨迹点与真值的距离,单位米;方向误差适合检查换道模型的输出方向与真实转向是否一致;概率校准误差用于检查输出的概率是否符合真实频率——例如输出 0.7 概率的轨迹,在 100 次相同场景里实际发生了 65 到 75 次,才算合理。

校验脚本或模块需要从回放输出里提取出 PredictedTrajectory ,与高精地图标注的真值做点对点匹配。做轨迹对齐时要注意时间戳对齐,而不是简单按序号对齐。同时,要单独检查单帧结果里是否有 probability 之和超过 1 的目标,这是层级冲突的典型信号。

5.3 接口参数调整的优先顺序与验证流程

接口参数调优时,优先调整轨迹采样间隔和降级系数,这两个参数对规划模块影响最大。现场调试如果出现目标轨迹抖动,先检查 relative_time_ms 是否连续;如果出现路口激进变道,则调低低概率轨迹的概率值,让规划模块更保守。参数调优后直接回放同一段数据,对比调整前后的轨迹差异,避免实车调试的高成本。

验证流程建议固定在接口开发流程里:先做冒烟测试,确认输入输出消息能正确解析;再做回放回归,确认接口字段没有回归;最后做不同场景切片对比,组合测试高速、路口、密集行人区域的数据包。最后一公里是接仿真环境做 SIL 测试,用仿真传感器数据持续灌入预测服务,反复验证接口在多传感器帧率不同步时的鲁棒性。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

立足具身智能前沿赛道,致力于搭建全球化、开源化、全栈式技术交流与实践共创平台。

更多推荐