第1章:感知模块数据结构差异分析
各位同学好,我是老张。今天咱们来聊聊Apollo和Autoware在感知模块上的数据结构差异。说实话,这个坑我踩过不少次,尤其是做接口适配的时候,数据对不上,整个系统就崩了。
感知模块说白了就是自动驾驶的「眼睛」。它要识别三类东西:障碍物、车道线、红绿灯。两个框架都做这件事,但数据怎么存、怎么传,差别还挺大。
1.1 感知障碍物数据结构对比
先看最核心的障碍物。Apollo用PerceptionObstacle,Autoware用DetectedObject。名字不一样,结构也差很多。
| 对比项 | Apollo PerceptionObstacle | Autoware DetectedObject |
|---|---|---|
| ID标识 | int32 id | int32 id + uuid |
| 位置 | Point3D position(全局坐标) | geometry_msgs/Pose(局部坐标) |
| 速度 | Point3D velocity(m/s) | geometry_msgs/Twist(带角速度) |
| 形状 | Polygon polygon(多边形) | shape_msgs/SolidPrimitive(基本几何体) |
| 类型 | enum Type(车辆/行人/自行车等) | string label(自定义字符串) |
| 置信度 | double confidence | float32 confidence |
我个人习惯用Apollo的枚举类型,因为类型明确,不容易出错。Autoware用字符串虽然灵活,但我在项目中遇到过一个问题——不同传感器模块输出的label格式不统一,有的写"car",有的写"vehicle",适配起来很头疼。
核心差异点:
- Apollo用全局坐标系,Autoware用局部坐标系
- Apollo用多边形描述形状,Autoware用基本几何体
- Apollo类型是枚举,Autoware是字符串
1.2 车道线数据结构对比
车道线这块,两个框架的思路完全不同。Apollo用LaneLine,Autoware用Lanelet2。
Apollo的车道线是「点集」思路——每条车道线由一系列点组成,每个点包含位置、宽度、曲率等信息。代码大概长这样:
// Apollo LaneLine
message LaneLine {
repeated LaneLinePoint points; // 点序列
LaneLineType type; // 实线/虚线/双黄线等
LaneLineColor color; // 白色/黄色
double confidence; // 置信度
}
message LaneLinePoint {
Point3D position; // 位置
double heading; // 朝向
double curvature; // 曲率
double width; // 宽度
}
Autoware的Lanelet2是「地图」思路——它把车道线当作地图元素的一部分,有起点、终点、左右边界。说白了,它更像一个路网结构。
// Autoware Lanelet2 (简化)
struct Lanelet {
Id id;
std::vector<Point3d> leftBound; // 左边界
std::vector<Point3d> rightBound; // 右边界
std::vector<Attribute> attributes; // 属性(限速、车道类型等)
};
嗯,这里要注意。如果你要做接口适配,Apollo的LaneLine更适合做感知输出,因为它就是传感器直接检测的结果。Lanelet2更适合做规划输入,因为它包含了地图拓扑信息。
我的经验:曾经有个项目,我们直接用Lanelet2做感知输出,结果发现检测到的车道线无法和地图匹配。后来改成先输出Apollo格式的LaneLine,再转成Lanelet2,问题就解决了。
1.3 红绿灯数据结构对比
红绿灯这块,两个框架差异最大。Apollo用TrafficLight,Autoware用TrafficSignal。
| 对比项 | Apollo TrafficLight | Autoware TrafficSignal |
|---|---|---|
| 颜色 | enum Color(红/黄/绿/黑/未知) | int32 color(0=红,1=黄,2=绿) |
| 位置 | Point3D position(3D坐标) | geometry_msgs/PoseStamped(带时间戳) |
| ID | string id(地图关联ID) | int32 id(自增ID) |
| 置信度 | double confidence | float32 confidence |
| 跟踪状态 | 无 | int32 tracking_state |
你想想看,Apollo的红绿灯ID是字符串,用来关联地图中的红绿灯。Autoware的ID是整数,只是自增编号。这意味着什么?
如果你用Autoware的数据做规划,你得自己维护一个「ID到地图红绿灯」的映射表。我在项目中就吃过这个亏——Autoware输出的红绿灯ID每次重启都会变,导致规划模块找不到对应的红绿灯。
避坑指南:我曾经在适配时直接用了Autoware的ID做地图关联,结果系统跑了几小时后,红绿灯ID溢出重置,规划模块直接罢工。后来我改成用位置坐标做关联,才彻底解决。
1.4 整体结构对比图
下面这张图是我自己画的,把三个模块的数据结构关系梳理了一下。你看一眼就明白了:
1.5 适配要点总结
说了这么多,我总结几个关键点:
- 坐标转换是第一步——Apollo用全局坐标,Autoware用局部坐标。适配时先搞清楚坐标系,不然位置全错。
- 类型映射要小心——Apollo的枚举和Autoware的字符串,映射表一定要写全。漏掉一个类型,系统就报错。
- ID策略要统一——红绿灯的ID关联,建议用位置做key,别依赖自增ID。
- 置信度精度要匹配——Apollo用double,Autoware用float。直接赋值会丢失精度,建议做一次类型转换。
一句话总结:Apollo的数据结构更「工程化」,适合量产;Autoware更「学术化」,适合研究。适配时,以Apollo为基准,把Autoware的数据转过来,是最省事的做法。
好了,这一章就到这里。下一章咱们聊聊规划模块的数据结构差异,那个更刺激——路径、速度、轨迹,每个都不一样。