第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 整体结构对比图

下面这张图是我自己画的,把三个模块的数据结构关系梳理了一下。你看一眼就明白了:

Apollo 感知数据结构 PerceptionObstacle 全局坐标 + 多边形形状 枚举类型 + double置信度 int32 ID LaneLine 点序列(位置+曲率+宽度) 实线/虚线 + 颜色 double置信度 TrafficLight 枚举颜色(红/黄/绿/黑) string ID(地图关联) 3D坐标 + double置信度 Autoware 感知数据结构 DetectedObject 局部坐标 + 基本几何体 string label + float置信度 int32 ID + uuid Lanelet2 左右边界点集 地图拓扑结构 属性列表(限速等) TrafficSignal int32颜色(0/1/2) int32 ID(自增) 带跟踪状态 坐标系统不同 表示方式不同 ID策略不同

1.5 适配要点总结

说了这么多,我总结几个关键点:

  1. 坐标转换是第一步——Apollo用全局坐标,Autoware用局部坐标。适配时先搞清楚坐标系,不然位置全错。
  2. 类型映射要小心——Apollo的枚举和Autoware的字符串,映射表一定要写全。漏掉一个类型,系统就报错。
  3. ID策略要统一——红绿灯的ID关联,建议用位置做key,别依赖自增ID。
  4. 置信度精度要匹配——Apollo用double,Autoware用float。直接赋值会丢失精度,建议做一次类型转换。

一句话总结:Apollo的数据结构更「工程化」,适合量产;Autoware更「学术化」,适合研究。适配时,以Apollo为基准,把Autoware的数据转过来,是最省事的做法。

好了,这一章就到这里。下一章咱们聊聊规划模块的数据结构差异,那个更刺激——路径、速度、轨迹,每个都不一样。

可可精品资料库