自动驾驶预测服务接口设计:数据流、消息规范与调度策略
简介:智能驾驶功能软件平台设计规范第三部分聚焦预测功能服务接口,适用于开发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 测试,用仿真传感器数据持续灌入预测服务,反复验证接口在多传感器帧率不同步时的鲁棒性。
更多推荐



所有评论(0)