核心数据结构对比:规划模块的轨迹、路径点与速度曲线
做自动驾驶系统集成,说白了就是在跟数据打交道。我这些年折腾Apollo和Autoware,最深的体会就是——数据结构不一致,才是集成路上最大的坑。你想想看,两个系统各自定义了一套“语言”,你要做的就是把它们翻译成彼此能懂的信号。
今天咱们就聚焦规划模块,看看轨迹、路径点、速度曲线这三个核心数据结构,在Apollo和Autoware里到底差在哪。
1. 轨迹(Trajectory)的整体设计差异
先看宏观层面。轨迹是什么?就是车辆未来要走的“时空路径”。
Apollo这边,轨迹定义在planning.proto里,核心是ADCTrajectory。它包含一个trajectory_point的列表,每个点都绑定了时间戳、位置、速度、加速度,甚至还有方向盘转角。说白了,Apollo把轨迹当成一个完整的时空序列,每个点都自带“时间轴”。
Autoware呢?它的轨迹定义在Trajectory.msg里,核心是Trajectory消息。它也有一个点列表,但每个点叫TrajectoryPoint。这里有个关键区别——Autoware的轨迹点不强制带时间。它更关注“路径”本身,速度信息是单独通过twist字段表达的。
我个人的习惯是:做集成时,先把Apollo的轨迹转成Autoware的格式,重点处理时间戳的映射。如果Autoware那边不需要时间,我就把Apollo的时间戳存到header.stamp里,至少保留原始信息。
来看个代码对比,更直观:
// Apollo的轨迹点结构(简化版)
message TrajectoryPoint {
double timestamp_sec; // 绝对时间戳
PathPoint path_point; // 路径点(x, y, z, theta, kappa)
double v; // 纵向速度 m/s
double a; // 纵向加速度 m/s^2
double steer; // 方向盘转角 rad
}
// Autoware的轨迹点结构(简化版)
TrajectoryPoint.msg:
geometry_msgs/Pose pose // 位置+朝向
geometry_msgs/Twist twist // 线速度+角速度
float32 acceleration // 加速度(可选)
float32 curvature // 曲率(可选)
看到了吗?Apollo把时间戳放在每个点里,Autoware则依赖消息头的stamp。这个差异,我在第一次做接口适配时就踩过坑。
2. 路径点(PathPoint)的细节差异
路径点是轨迹的基本单元。两个系统都定义了路径点,但字段含义有微妙差别。
Apollo的PathPoint包含:
x, y, z:位置坐标theta:朝向角(弧度)kappa:曲率(1/m)s:沿路径的累积距离(m)dkappa:曲率变化率ddkappa:曲率二阶变化率
Autoware的TrajectoryPoint里,路径信息藏在pose里:
pose.position:位置pose.orientation:四元数表示的朝向twist.linear.x:纵向速度twist.angular.z:横摆角速度
我曾经在这里栽过跟头:Apollo用theta(欧拉角)表示朝向,Autoware用四元数。直接赋值会导致车辆方向错乱。正确的做法是:用tf::createQuaternionFromYaw(theta)做转换。别问我怎么知道的,问就是实车测试时车在原地转圈...
还有一个细节:Apollo的s字段(累积距离)在Autoware里没有直接对应。如果你需要这个信息,得自己算——遍历路径点,累加相邻点的欧氏距离。我一般会在适配层加一个computeCumulativeDistance()函数。
3. 速度曲线(Speed Profile)的表达方式
速度曲线,说白了就是告诉车“什么时候该跑多快”。
Apollo的做法很直接:每个轨迹点都带v(速度)和a(加速度)。你拿到轨迹点列表,按时间戳排序,就得到了完整的速度曲线。Apollo还会在ADCTrajectory里额外提供一个speed_profile字段,是一个SpeedPoint列表,每个点包含t(时间)和s(距离)。
Autoware呢?它的速度信息分散在twist.linear.x里。没有独立的速度曲线结构。如果你想获取“时间-速度”关系,得自己从轨迹点的时间戳和速度值里提取。
| 特性 | Apollo | Autoware |
|---|---|---|
| 速度存储位置 | 每个轨迹点的 v 字段 | 每个点的 twist.linear.x |
| 加速度存储 | 每个轨迹点的 a 字段 | acceleration 字段(可选) |
| 独立速度曲线 | 有 SpeedProfile 结构 | 无,需自行提取 |
| 时间基准 | 每个点有绝对时间戳 | 消息头时间戳 + 点索引 |
我建议:做接口适配时,统一用“时间戳 + 速度值”的键值对来传递速度曲线。这样不管对接哪个系统,都能保证信息不丢失。我在项目中写过一个SpeedProfileAdapter类,专门处理这种转换。
4. 核心差异总结与适配策略
啰嗦了这么多,咱们总结一下。两个系统在规划模块的数据结构上,主要有三个差异:
- 时间戳粒度:Apollo每个点都带时间,Autoware依赖消息头
- 朝向表示:Apollo用欧拉角,Autoware用四元数
- 速度曲线独立性:Apollo有独立的速度曲线结构,Autoware没有
适配策略其实不复杂:
- 写一个转换层,专门处理字段映射
- 对缺失的字段(如
s),用计算补齐 - 对格式不同的字段(如朝向),做显式转换
- 保留原始数据,别丢信息
下面这张图,是我做适配时画的整体思路,你看一眼就明白了:
嗯,这张图基本概括了适配层的核心工作。你实际做的时候,可能还会遇到一些边界情况,比如轨迹点数量不一致、时间戳精度不同等等。但掌握了这三个核心差异,剩下的就是体力活了。
最后说一句:别想着一次性搞定所有适配。我通常的做法是,先跑通一个最简单的场景(比如直线行驶),验证数据转换正确,再逐步增加复杂度。这样出了问题,排查范围也小。