说实话,这个系列写到第八十一期,我原以为读者已经审美疲劳了,结果后台私信问得最多的还是感知模块。也不奇怪,感知是整个自动驾驶链路里离“真实世界”最近的一层,所有下游决策都建立在它的输出之上。很多时候,规划和控制做得再漂亮,感知一旦看错,车照样会做出离谱的动作。所以我干脆把Apollo中感知模块的设计细节、工程取舍和我在实际部署中踩过的坑,一次性摊开来聊透。
1. 感知模块在Apollo里的“势力范围”:从原始传感器到下游接口
1.1 感知能力对系统决策的直接约束
先明确一件事:感知模块不是一个孤立的算法库,它直接决定了车的“上限”。举一个我常用的比喻——整个自动驾驶系统像一个人,定位是方向感,感知是眼睛,规划是大脑,控制是手脚。眼睛看错了,大脑算得再快也没用。
在Apollo的架构里,感知模块处于传感器和下游模块之间的夹层。上游是激光雷达、摄像头、毫米波雷达这些硬件,下游是预测、路由、规划、控制。感知模块要做的是把原始传感器数据翻译成“可被决策使用的语义信息”,具体来说就是:道路上有哪些障碍物、它们是什么类型、速度多少、在哪条车道上、红绿灯什么颜色、车道线怎么走、可行驶区域在哪里。
边界也很重要。感知模块不负责告诉你“车该往左还是往右”,那是规划的事;也不负责“当前速度和目标速度怎么切换”,那是控制的事。感知的职责是尽可能完整、准确、及时地把环境状态交出去,然后由下游去做判断。理解这条边界,你在调试时就不会抓错方向——很多新手一遇到车行为异常就怪感知,结果查到最后是预测模块的匹配逻辑出了问题。
1.2 输入侧:三类传感器各司其职
Apollo对传感器是“多模态融合”的思路,核心传感器有三种,各有各的脾气:
- 激光雷达:输出三维点云,测距非常准,直接给目标提供距离、位置、尺寸、速度。缺点是视野有限、怕雨雾、点云稀疏时小目标易丢。
- 摄像头:输出图像,纹理信息最丰富,能识别颜色、类型、车道线、红绿灯。缺点是单目测距天然存在不确定性,对光照变化敏感。
- 毫米波雷达:输出稀疏目标回波,对速度测量非常直接,而且几乎不受恶劣天气影响。缺点是角度分辨率低,横向位置精度差,点迹质量不如激光雷达“干净”。
这三者放到一个系统里,不是简单堆叠,而是要做时间同步、空间对齐和数据融合。时间同步解决“同一时刻看到的是不是一个世界”的问题,空间对齐解决“图像上的像素和点云里的点是不是同一个物体”的问题。后面我会单独讲这两件事,因为它们才是融合落地里最耗人的部分。
1.3 输出侧:一条感知消息被下游怎么消费
感知模块的输出在Apollo里最终会打包成类似障碍物列表的结构化数据,里面包含每个障碍物唯一的ID、类型标签、三维框、速度、加速度、朝向、所在车道、轨迹历史等等。这些消息不是只给规划看一眼就完事,预测模块会接着吃这份数据,去推演障碍物未来几秒可能的位置;路由和规划模块则会结合地图,判断这辆车要不要避让、什么时候变道。
从工程角度看,感知模块的输出质量不是只被“有没有检出目标”决定,还被“检出的目标干不干净”决定。什么叫干净?一个障碍物的ID能不能稳定维持、类型会不会在帧与帧之间跳变、位置输出是否抖动、速度估计是否有延迟,这些对下游系统的影响甚至超过单帧检测精度。我见过很多项目在检测指标上刷得很高,但整个系统跑起来还是不舒服,问题基本就出在这里——感知输出的“时序一致性”不够。
2. 一条感知管线要串起五件事:分割、检测、分类、跟踪、融合
2.1 点云分割与地面滤除:先把背景摘出去
激光雷达一帧点云通常有几万到几十万个点,里面真正属于障碍物的可能不到十分之一。大部分点来自地面、绿化带、路沿、建筑立面。如果直接把全部点云丢给检测算法,计算量上先炸一次,检测精度也会被无关点拉低。
所以Apollo里有一套点云预处理流程,最核心的一步是地面滤除。常见做法是先用栅格化把点云按高度切分,然后对每个栅格做平面拟合,把高度连续且坡度平缓的区域标记为地面,剩下的非地面点才进入障碍物检测。这一步很像打扫房间时先把地板上散落的杂物清掉,桌面上真正要处理的东西才看得清。
地面滤除的参数很敏感:坡道场景里网格分辨率要调小,不然会把坡顶误判成障碍物;城市隧道里地面反射点噪声大,阈值设不好就容易把地面点当成悬空物体。我的经验是,宁可让地面稍微多留一点噪声点,也不要激进地把地面全部滤掉,否则减速带和低矮路沿会被直接当成地面抹掉,代价其实更高。
2.2 目标检测和分类:模型选型上的选择
在Apollo各版本中,激光雷达侧的检测网络经历了从点云分割后聚类再分类,到端到端3D检测的演进。早期做法是对非地面点做体素化,然后跑一个类似VoxelNet或PointPillars的网络,产出带朝向的三维框;后面的版本逐渐引入CenterPoint等基于中心点的检测头,省去了大量先验锚框设计,对小目标召回率也更好。
相机侧走的则是2D检测加语义分割的双线并进。2D检测用来给出目标的图像框,语义分割用来做车道线、可行驶区域这些像素级任务。这里有个工程细节我提醒一下:相机模型只给2D信息,3D位置要靠多视角几何或者雷达融合来补。很多人一开始天真地以为单目图像能端到端回归出很准的深度,实测下来离量产要求还差得远,尤其是中远距离的误差会快速爆炸。
分类方面,Apollo对障碍物的典型类别包括车辆、行人、骑行者、卡车、锥桶、未知物体。类型的准确度直接决定规划模块的应对策略——一辆静止自行车和一棵树在规划上的风险等级完全不同。所以分类任务不单看准确率,还需要模型对“未知类别”有兜底机制,不能把没见过的物体硬塞到一个熟悉类别里。
2.3 多目标跟踪:为什么ID不跳比检测准更重要
很多从竞赛模型转来做量产感知的人,会低估跟踪模块的份量。检测网络负责“这一帧哪里有什么”,跟踪模块负责“这个目标从哪里来、到哪里去、是不是已经消失”。两者的关系可以这样理解:检测是快照,跟踪是电影。
Apollo里的跟踪链路通常包含几个环节:数据关联(把新帧检测框和已有轨迹匹配起来)、运动预测(用恒速度或卡尔曼滤波外推目标位置)、轨迹管理(新建轨迹、平滑轨迹、删除丢失轨迹)。数据关联最常用的手段是计算检测框和预测框之间的交并比或中心距离,然后用匈牙利算法做全局最优匹配。这步看着是个经典组合拳,实际跑起来麻烦很多——遮挡时的短暂丢失要不要保留轨迹、两个目标贴得太近时会不会互相串ID、目标重新出现时是续上老轨迹还是建新轨迹,每个决策都在打磨系统的“手感”。
我在实际项目里最深的体会是:对下游来说,ID稳定比位置精确更重要。如果一辆重型车每两帧就换一次ID,规划模块就完全无法预测它的轨迹,甚至会被迫频繁重规划,车开起来会一顿一顿的,非常危险。
2.4 融合输出:给预测一个干净的名单
到了这一层,系统手里其实已经有多个传感器各自的检测结果。融合模块要做两件事:一是合并同类目标,去掉重复检测;二是综合各传感器的优势给出一个更可靠的输出。比如摄像头能在远距离认出“这是一个行人”,激光雷达能给出这个行人精确的距离和速度,融合之后,预测模块拿到的就是一个“类别可信、位置精确、速度平滑”的障碍物。
Apollo的常见融合策略是目标级后融合:先让每个传感器独立跑完自己的检测和跟踪,再把它们的目标列表按时空对齐后合并。融合不只是选一个置信度高的把另一个丢掉,而是要做匹配、交叉验证、甚至协商出一个综合结果。举个例子,雷达输出的速度很可靠但位置横向飘,激光雷达输出的位置准但高速场景下点云容易被拉花,融合后就应该由雷达主导速度、激光雷达主导位置,而不是各取一半导致两头都歪。
融合结果再经过一个质量门控——比如最小置信度阈值、最小生命周期约束——最后打包成障碍物列表交给下游。这一步的目的就是让下游拿到的名单“干净”:没有一帧出现的幽灵目标、没有两帧存活的噪声点、没有同一个目标被重复报告。
3. 激光雷达和相机谁说了算:融合策略里的工程取舍
3.1 前融合听起来很美,工程上很贵
融合策略大体分前融合和后融合两个方向。前融合是指把点云和图像在特征层面连接,输入一个神经网络,直接输出融合目标框。理论上这是更好的方案,因为网络能在底层学到底层视觉特征和几何特征的互补关系,而且能避免后融合里“两个传感器各说各话”导致的信息损耗。
但前融合在工程上代价很重。首先你需要一套极其可靠的传感器标定和时间同步系统,任何抖动都会让融合网络学到错误的对齐关系;其次这是一个多模态大模型,训练数据需要同时覆盖图像和点云的多样性,采集和标注成本翻几倍;最后是可解释性降低,你很难定位到底是哪个传感器把模型带偏了。
量产项目里,纯前融合方案现在依然少见。大多数团队会把前融合用在一个相对受限的任务里,比如对特定类型目标的近距离识别,而全场景的融合仍靠后融合兜底。我在实际项目里碰到的方案组合基本是:主路线后融合,关键场景小范围前融合强化,效果和复杂度之间取一个平衡。
3.2 后融合在Apollo里的实际做法
Apollo里的后融合,一般会经历“局部融合”和“全局融合”两步。局部融合发生在同类传感器之间,比如多个摄像头看到同一个目标时,先合成一个更可信的视觉目标;多个激光雷达数据也会先做一轮合并。全局融合发生在跨传感器之间,把激光雷达目标、视觉目标、雷达目标统一到一个时空坐标下,两两之间做匹配。
匹配的核心就是算“相似度距离”。这个距离通常由位置差、朝向差、类型一致性共同组成,位置差的权重最大。匹配完成后,每个融合目标会携带一个“来源传感器组合”的属性,比如有的目标来自“激光+视觉+雷达”三源确认,有的只来自“单摄像头”。来源越多、置信度越高,目标被输出给下游的优先级就越高。
融合还有一个隐含收益:它提供了降级能力。某个传感器突然松动或者被遮挡时,只要其他传感器还在,系统不至于直接“瞎”掉。这在实际路测中太重要了,我后面关于“传感器丢失后的降级策略”还会再展开。
3.3 时间对齐和空间对齐:融合的隐形门槛
只要做过一次多传感器融合,你就会承认:融合的难点不在匹配算法,而在对齐精度。时间对齐要求不同传感器在同一时刻观测同一场景,但传感器各自有触发延迟、曝光时间、传输耗时,必须做外推或插值,让它们“看起来”是在同一时刻采的样。空间对齐要求所有传感器坐标能高精度转换到同一个车体坐标系下,这依赖出厂外参标定和运行时在线标定监控。
空间对齐最怕的是标定参数漂移。车辆长期运行后,摄像头支架可能被震动震歪零点几度,激光雷达也可能因为磕碰产生外参偏移。零点几度的偏差在近距离看不出来,到50米开外可能就是半米的横向误差,融合结果就会发生错配——明明是并排的两个人被匹配成前后两个目标,下游预测模块将得到一个完全错误的运动关系。
所以我的习惯是:在融合模块前面加一个在线对齐校验,每几分钟估算一次各传感器之间的投影一致性,一旦误差超限就标记传感器“失配”,降级使用并提醒重新标定。这套兜底机制在真实路测里为我省下了大把排查时间。
4. 最容易拖后腿的细活:红绿灯、车道线与静态障碍物感知
4.1 红绿灯感知:时序、位置和绑定逻辑
红绿灯感知看起来是“图像分类”的一个小分支,实际上它是个完整的状态机工程。系统必须先知道红绿灯在图像里的哪个位置——Apollo的做法是结合高精地图先给出目标红绿灯在地图中的世界坐标,然后投影到图像上,生成一个裁剪区域,再在区域内做灯色识别。这样能大幅缩小搜索范围,提升识别准确率。
但真正麻烦的不是识别颜色,而是灯和车道的绑定关系。一个路口往往有多个红绿灯,分别控制不同方向的车道。感知模块必须判断“当前我所在车道对应的到底是哪个灯”。这个判断如果错了,即使红绿灯识别是100%准确,车也会在绿灯时停下、在红灯时起步。Apollo的做法通常依赖地图给定的红绿灯配测关系和车道的关联,当地图数据不完整时,就会进入退化模式,只把灯色结果输出,由规划模块做保守处理。
灯色识别还有一个隐蔽问题:LED灯的扫描频率和摄像头帧率之间可能产生闪烁,导致某几帧拍到的灯色是错的或黑的。工程上不能只看当前单帧,而要做时间维度的投票和状态滤波,结合灯色的周期变化规律做平滑,才能稳定输出。
4.2 车道线感知:从分割结果到可行驶区域
车道线感知的输出主要有两类:一是像素级车道线分割图,二是拟合后的车道线曲线参数。Apollo的相机感知里,车道线任务通常跑一个语义分割网络,把图像中的车道线像素单独标出来,然后通过后处理把像素点拟合成若干条三次曲线或样条曲线。
这个环节有个老生常谈但不可不说的坑:自车运动会让同一根车道线的图像投影每一帧都变,如果不用上一帧的拟合结果做先验,输出就会在弯道里抖来抖去。Apollo的做法是在帧间对车道线曲线做传播和更新,相当于给车道线也做了一个“跟踪”,这样规划模块拿到的是一条时域稳定的车道中心线,而不是一条每帧都换皮筋的线。
车道线缺失也是一种常态化场景,比如老旧道路、施工改道路段、雨雪覆盖。感知模块必须同时输出“车道线置信度”和“可行驶区域”,当置信度低时,规划模块可以切换到自由空间规划模式,而不是因为没有车道线就呆在原地。
4.3 静态障碍物和路沿:用栅格换思路
动态障碍物靠的是检测和跟踪,而静态障碍物、路沿、可行驶区域这些“背景语义”,Apollo里经常用动态栅格图来建模。栅格图把车周围切分成均匀网格,每一格维护“有障碍物”“空闲”“未知”三个状态,然后综合点云高度、雷达回波、视觉分割结果做更新。
栅格图的好处是能统一表达动态和静态障碍物,而且天然适合做可行驶区域推理。规划模块查询某个位置是不是可走时,直接查栅格状态就行,不需要对每个静态物体单独维护一条轨迹。
但栅格图也有麻烦:分辨率太大则小障碍物会被吃掉,分辨率太小则计算量暴涨。城市拥堵路况下,行人贴着车头走,栅格更新频率不够就会在栅格图上留下“尾影”,误导规划模块制造幻影障碍。实际处理时我会把栅格更新和检测目标的结果做交叉校验,检测目标进入栅格后,动态栅格会锁定对应区域,防止状态被旧数据覆盖。
5. 实车路测复盘:我踩过的感知模块五大坑
5.1 时间戳乱套:最先咬人的坑
第一次跑多传感器融合路测时,我遇到过一类诡异现象:障碍物位置倒着跳,明明向前开的行人,输出坐标却一帧前两帧后。排查到最后发现,相机和激光雷达两者系统之间的时间同步偏差,在目标高速运动时直接导致目标位置出现明显错位。时间戳乱套就像几个人各看各的表汇报同一场比赛,下游终端完全没法把信息拼在一起。
解决这类问题没有捷径,必须一环一环排查:传感器本身的触发机制、数据的采集与传输时延、消息队列是否积压、各模块处理耗时是否在帧周期内完成。Apollo里的时间同步机制通常以激光雷达的采集时刻为主基准,把其他传感器的数据外推或插值到这个时刻。我在实际项目里还会额外加一个同步误差监控,统计设备日志里时间偏移是否超过阈值,一旦超限立即告警。这套机制看似多余,但在方向盘后面坐一整天时,它比任何模型的准确率数字都更能救你的命。
5.2 标定漂移:所有感知问题的最大嫌疑人
路测中遇到突然的“目标识别错乱”,我通常会先把所有算法层面的猜测放一边,直接去检查标定参数。有一次,车道线在直道上看起来朝左侧倾斜,所有人都怀疑车道线模型崩了,结果查下来是相机外参里有一个轴的偏航角差了几十分。几十分的反向偏角会让40米外的目标横移一米多,几乎能毁掉整个融合结果。
标定漂移的常见诱因包括:长时间颠簸引起的机械形变、高低温变换造成的支架微小位移、车轮撞击路肩带来的扰动振动。所以我在团队里推动了一个习惯:每次路测开始前先做一次快速在线标定校验,不重新做完整标定,只验证投影一致性。方法也很简单:找一根静止的路灯杆,分别用激光雷达和相机探测它,看两个传感器给出的位置是否吻合。如果不吻合,就先重新标定,再谈算法优化。
5.3 小目标和远处遮挡:模型在人/车之外失灵
常规数据集里大量标注的是普通车辆和行人,但真实路面上,小体积或低反射率的物体很容易被模型无视。比如马路中间的锥桶、被前车遮挡的路沿上的儿童、草丛里的猫狗,这些目标要么在图像里只有几十个像素,要么在点云里只有几个亮点。检测模型面对这些目标时,召回率会急剧下滑。
有一次实车遇到施工路段的塑料水马,系统完全没感知到,车差一点直接压过去。事后排查发现,激光雷达点云里这个目标只有不到十个点,在常规聚类算法里被当成噪声滤掉了。从那以后,我们的策略是对小目标专门做一版“高灵敏度配置”,在近距离区域使用更低的置信度阈值,同时把栅格图作为兜底,确保单帧检测漏掉时,栅格上的“非空闲状态”仍然能让规划模块做出避让。
5.4 仿真不背锅:场景回放和实车差异
仿真和回放测试做得再顺利,也别急着开路上线。我在实际项目中经历过“仿真通过、实车出错”的典型场景——回放历史数据跑感知模块时一切正常,但到了新路段,方案在环岛场景里频繁丢目标。原因在于回放数据本身已经采集自同一个感知软硬件,相当于“背题考试”被自己人泄露,测试有效度有限;实车场景变量则远比回放丰富。
仿真和回放的价值是回归验证,它的使用场景是:验证老功能有没有退化、新参数有没有引入新问题。真要把感知能力往上推,还是要构造多样化的实车测试矩阵,覆盖隧道、逆光、雨雾、环岛、夜间、施工改道等等,这些真实环境因素对传感器的干扰是无法被回放数据完全模拟出来的。
5.5 单传感器丢失后的降级策略
传感器本身也会出问题:进隧道后GPS漂移,暴雨水雾打糊摄像头,高压线附近雷达出现虚假目标,激光雷达电机故障导致丢帧。感知模块必须预设降级策略,而不是等传感器故障发生了再临时抱佛脚。
我在项目里总结出的标准降级等级是:三传感器全正常时按全量融合策略跑;丢失一个传感器时切换到“双源确认”模式,置信度阈值提高,允许部分目标以单源输出;当置信度不足或传感器连续异常时,直接调用安全停车策略,把控制权交还给驾驶员或尽快靠边。另一个关键点是传感器故障要能主动反馈给系统,而不是让感知模块“硬扛”。这些逻辑看着简单,但在真实路测中经历过灯光闪烁、暴雨淋透镜头之后,你就会明白它们是有救命作用的。
6. 怎么量化感知好与坏:线下评测和上线前回归
6.1 检测指标之外,要加跟踪指标
很多人评测感知只报一个精度和召回率,这对量产决策远远不够。精度和召回率只回答“单帧检出准不准”,而系统的最终体验更取决于“目标跟踪稳不稳”和“轨迹顺不顺滑”。Apollo的感知评测链路里,我至少会同时看四类指标:
| 指标类型 | 典型指标 | 描述 |
|---|---|---|
| 检测精度 | 检测准确率、召回率、F1 | 单帧检出与漏检的水平 |
| 定位精度 | 3D框交并比、中心点误差 | 位置和尺寸的精度,决定规划能否避让 |
| 跟踪稳定性 | 目标ID切换次数、轨迹生命周期 | 目标是否被持续稳定追踪 |
| 时延表现 | 端到端延迟、最大帧间隔 | 感知到输出的时效性,延迟超过200毫秒就会吓坏规划 |
跟踪稳定性这个指标经常被忽略,但实车测试时感受最明显。如果目标在图片里稳稳地走了20米,而感知模块输出的ID换了好几轮,规划模块的轨迹预测就会被反复打断,车辆行为会表现得犹犹豫豫。
6.2 离线评测集怎么建才不偏
评测集的数据构成直接决定评测结果的有效性。我的经验是:评测集必须包含“普通场景”和“疑难场景”两层,而且疑难场景要单独统计,不能混在一起平均,否则会被看似不错的均值掩盖真实问题。
具体来说,普通场景负责守住基本盘,采集典型城市道路、快速路、停车场这些高频环境;疑难场景则重点覆盖极端天气、夜间、逆光、遮挡、施工、隧道出入口等低频但高风险的情况。我会按业务侧的需求给场景打权重,比如当前目标是跑城区开放道路,那么行人密集和路口博弈场景的权重就要显著拉高;如果目标是跑高速辅助驾驶,那么车道线磨损和中远距离货车识别就是权重最高的类别。
6.3 上线前回归:用历史数据测新模型
新模型上线之前,最稳妥的流程是先过一个“历史数据回归”。把所有已经采集并标注好的老数据统一跑一遍新版感知模块,对比旧版和新版的指标变化。这里面有一个铁律:任何新模型都不能让老场景的核心指标明显变差。宁可新功能加得不激进,也不能用一项小指标变好换几项重要指标变差。
我见过一个团队上线了一个新的检测模型,新场景的检出率提升了8%,看似喜人,但回归测试发现老城区窄路的行人检出率下降了5%,因为新模型对近距离遮挡更不敏感。这个结果在热力图里很难发现,恰好是回归测试把它给拦住了——否则上线之后十万公里的路测才能暴露出问题。
上线前还会做压力测试,比如把真实路测录制的长时数据按三倍速度注入感知模块,观察模块是否有内存泄漏、帧率抖动、队列积压。这类稳定性测试往往比精度提升更能决定项目进度的稳定性,感知模块是7x24小时运行的软件,不能只保证“评测集上好看”。
回归通过之后,我通常还要求做一轮“影子模式”验证——让新版本在后台实时运行,但输出不接入驾驶决策,只与线上版本做对比。影子模式能暴露回放数据覆盖不到的真实分布变化,而且没有驾驶风险。跑满一段里程后,再评估要不要切主线上线。
聊到这里,基本把Apollo感知模块的架构、关键环节和工程落地讲完了。最后分享一点个人体会:感知这个模块,技术深度藏在模型里,但可靠性和工程性都藏在数据处理和调试流程里。我踩过坑之后最大的经验就是,障碍物列表出来之前的所有空转环节,都值得花时间和精力去抠干净——时间同步做扎实了,标定漂移盯住了,融合输出规则定清了,整条感知链路的稳定性会有一个质的提升,远比单纯刷模型精度来得扎实。这也是为什么我始终建议团队里同时养着算法工程师和标定工程师,谁都缺不了谁。