ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Robotaxi红绿灯检测实战:YOLOv8从数据到部署的完整复盘

Robotaxi红绿灯检测实战:YOLOv8从数据到部署的完整复盘

跑 Robotaxi 的工程师们私下有个共识:感知系统里,红绿灯检测是看起来最简单、实际最折磨人的模块。一个高清摄像头对着路口,画面里就是几个发光的圆点,YOLOv8 随便训一版都能检出框来;可一旦把这套模型放进开放道路的 Robotaxi 上跑,你会发现真正的难点全在“检测”之外——数据怎么定义、状态怎么判、时序怎么稳、地图先验怎么配合,每一环都会反过头来折腾模型。我参与过一整个 Robotaxi 感知项目,专门负责用 YOLOv8 搭建实时红绿灯检测模块,从数据标注一路做到实车部署,今天把整个决策过程踩过的坑、验证过的路径完整复盘出来。这不是一篇 API 调用教程,而是一条从实验室 Demo 到路测可用的完整路线图。

1. 场景认知先行:红绿灯检测在Robotaxi里到底难在哪

在动手训练模型之前,我们花了很长时间统一对问题的认识。这个阶段看起来很虚,实际上是后面所有选型的基础。

1.1 它是安全关键模块,不是“感知一个目标”那么简单

通用目标检测里,“检出目标 + 给个框”就算完成;但红绿灯不一样,它直接决定了车辆是走还是停,一旦判断错误,轻则造成乘坐体验崩溃,重则触发安全事故。这个定位决定了后续每一个设计都要考虑兜底:模型不确定时怎么办、检测丢失时怎么办、分类置信度低时怎么办。我见过一些团队把红绿灯检测当成普通检测任务来做,结果模型 mAP 很高,却在转灯瞬间给了规划模块一个震荡的状态,车辆在路口前急刹,乘坐体验很差。

所以做这个模块的第一原则:准确性和稳定性优先,召回率和速度是约束条件下尽可能去保证的指标,而不是反过来。

1.2 高精地图能帮忙,但不能背锅

Robotaxi 一般都有高精地图,上面标注了信号灯的三维位置、朝向、杆件关联、停止线位置。我们确实利用地图把“哪些灯管我这条车道”这件事前置,把检测范围收敛到地图投影的 ROI 附近。但必须想清楚:地图是静态的,灯的状态是实时的,地图不能告诉你现在是红灯还是绿灯。它的作用是把候选空间压缩,让模型少做无谓的全图搜索,同时在后处理里提供位置先验来过滤误检。

如果试图用地图投影直接代替视觉检测,就会在定位漂移、地图过期、施工变灯这些场景里吃大亏。我们的分工很明确:视觉负责“看到灯并判断状态”,地图负责“这盏灯在哪个位置、管哪条车道”,两者谁也别替代谁。

1.3 传统图像方案为什么会被淘汰

早期很多团队做过基于颜色阈值和形状匹配的红绿灯检测:把 RGB 转到 HSV,找红色和绿色的高亮连通域,再用圆形度、面积过滤。这个方案在固定摄像头、固定光照条件下确实能用,但开放道路上会遇到一连串让它崩溃的输入:逆光下绿灯发白、夜间车灯与红灯同色、LED 灯在传感器上产生频闪和色偏、雨天地面积水反光。

YOLOv8 这类数据驱动检测器的本质,是把“什么形状、什么颜色、什么上下文组合在一起才算红绿灯”这组判断交给大量标注数据去学习,泛化性和鲁棒性比手工特征高一大截。这也是我们选择它作为主检测器的根本原因。

2. 数据工程:小目标、状态定义、样本分布三个硬骨头

数据是红绿灯检测真正的主战场。模型结构做得再花哨,数据没理清楚,实车一定带不动。

2.1 公开数据集能用,但得会组合

我先后摸过几个公开红绿灯数据集,情况很不一样。有一个以远距离小目标标注著称,对训练小目标召回非常有价值;另一个来自固定路口摄像头,视角稳定但和车端前视视角差异大;还有一个包含大量复杂交叉口和不同类型信号灯,更接近实际运营路口的灯型组成。

我的用法是:几个数据集混合训练,但按比例控制,主力放在与车端视角接近的数据上。一个容易踩的坑是直接把某个数据集拿来训完就上车——数据采集视角不同,泛化几乎必然失败。

2.2 先量一下目标尺度,再谈模型配置

红绿灯的目标尺度问题比很多人想象得更严重。以 1080p 车规相机为例,50 米外的信号灯灯体在画面里通常只有 20×40 像素左右,比 YOLOv8 默认 640×640 输入下的很多常见目标小一个数量级。训练前我会先对标注框做一次宽高分布统计:如果大量框落在 32×32 以内,就必须认真考虑输入分辨率、P2 特征层、切片推理这类小目标优化手段,否则后面调模型结构全是白费力气。

这一步看起来简单,但直接决定了后续模型选型和训练策略,我们项目初期没有意识到,等到夜间样本召回上不去才回头补功课,浪费了不少时间。

2.3 检测框怎么标,直接影响系统上下限

这是数据工程里最容易忽略的决策。不同数据集的标注习惯完全不同:有的框整个灯壳(含黑底边框),有的只框发光的灯珠,有的把同一个信号灯拆成多个箭头灯分别标注。我们最终统一为:检测器只负责框出整个灯面/灯壳,灯珠的状态交给后续分类逻辑。理由很简单——Robotaxi 需要知道一盏灯从头到尾的完整状态,包括故障和熄灭的情况;如果检测目标只是一颗发光的圆点,灯灭的时候就等于目标消失,系统会对“灯灭了”和“没有灯”完全分不清。

箭头灯的处理上,我们把箭头整体视为灯面的一部分,不在检测层拆细,拆细会让数据样本迅速稀疏,反而不利于检测泛化。

2.4 状态类别设计:检测和状态判定必须解耦

很多第一次做红绿灯检测的团队会直接把“红灯”“绿灯”“黄灯”“左转红灯”做成检测类别。我在项目初期也这样干过,后来发现数据分布极不均衡,尤其是左转箭头和右转箭头的样本少得可怜,模型很难在保持基础状态召回的同时学会细粒度箭头。后来调整为:检测器只输出一个通用类别 traffic light,再加一个独立的分类阶段做状态判定。这样检测器样本量大、类别均衡,分类器可以专门优化“这小片区域到底是什么颜色和方向”的问题,两个模型各自调优,整体效果提升明显。

2.5 样本分布与增强策略要贴着场景走

真实路采数据里,白天晴天的占比天然偏高,但真正让感知系统翻车的是傍晚逆光、夜间、雨雾这些低发样本。我们对训练集做了重采样:白天样本降权,黄昏、夜间、雨天样本升权,让模型在困难场景上学得更充分。数据增强方面,HSV 扰动对红绿灯特别有效,因为灯珠在不同相机曝光和白平衡下的色偏非常大;我还加了一种模拟过曝导致高光糊化的增强,否则模型看到强曝光下的“白红色大圆斑”时往往会不知所措。

3. YOLOv8选型与配置:一切选择围绕算力预算和小目标

模型方面,我们最终选定 YOLOv8,但过程中做过好几轮对比,这里把选型和关键配置讲清楚。

3.1 在检测器选型上我们是怎么权衡的

两阶段检测器精度有优势,但结构重,车端算力不允许;其他单阶段模型我们也都试过,YOLOv8 的 C2f 结构、解耦头、anchor-free 设计在速度和精度平衡上做得不错,而且官方生态完善,训练、导出、部署一条龙,节省了大量工程时间。对 Robotaxi 这种“红绿灯检测只是整个感知系统里一个小模块”的场景,工程效率和稳定性往往比某一个指标刷高分更重要。

我还试着推理过多帧信息融合方案,比如把时序信息引入检测器。后来发现收益有限,因为红绿灯的帧间变化更多是频闪和短暂遮挡,把这些交给时序后处理去解决比塞进模型里更可控。

3.2 模型大小和输入分辨率:最优解不是模型最大

一开始团队里也有人倾向直接上 YOLOv8x,觉得精度一定最高。实测对比下来,在相同输入下 x 相比 m/l 的 mAP 提升最多一两个点,推理耗时却几乎翻倍。真正带来质变的是输入分辨率:从 640 提升到 1280,小目标召回提升非常明显。原因不难理解——分辨率上来了,灯珠在特征图里保留的空间信息更多,模型更容易学到灯面结构和颜色的组合。

我们最终落地的组合是 YOLOv8m + 1280 输入。这个选择是在车端域控的算力预算下反复测出来的,单帧推理大概 15~20 ms,既保证了精度,也不至于挤占其他模块的计算资源。

3.3 小目标优化三件套:P2、切片推理、多尺度训练

YOLOv8 官方提供了带 P2 层的模型结构,在特征提取早期额外引出一条高分辨率特征图,对小目标检测增益明显。我们开启 P2 之后,小目标召回涨了一截,代价是训练和推理开销增加。切片辅助推理的思路则是把大图切成多个 tile 分别推理再融合,离线评测里能显著提升小目标召回,但由于实车算力和延时限制没法全程使用,我们把它定位为“离线难例挖掘工具”——路测数据拿回来先用切片推理跑一遍,找出模型最容易漏的帧,再针对性补充训练。

多尺度训练我建议无条件开启,YOLOv8 原生支持,对尺度变化的泛化帮助非常大,几乎零成本提升。

3.4 常被忽略的相机链路:曝光策略要先于模型优化

模型再强,也架不住输入图像本身出了问题。夜间红灯过曝成一团白色光斑、强逆光下绿灯完全看不清颜色,这些情况里模型的失败根源在相机。我们后来跟相机团队一起调整了曝光权重,让曝光策略更照顾交通灯区域,尽量避免高光饱和,模型的表现立刻上了一个台阶。这个经验很重要:做自动驾驶感知,ISP 和曝光参数本身就是感知系统的一部分,别只在模型里找原因。

4. 训练与评估:mAP是面子,实车曲线才是里子

数据、模型选好以后,进入训练和评估阶段。这一阶段最大的认知更新是:mAP 会骗人,不能只看它。

4.1 训练流程与验证集划分

训练基本沿用 YOLOv8 官方推荐的超参,SGD 优化器、约 200 epoch、带动量,先在混合数据集上预训练,再在自有场景数据上微调,最后用难例集做第二轮微调。有一个关键细节是验证集必须按场景而不是按帧随机切分。按帧随机切分会把同一路口的连续帧同时分到训练集和验证集,模型等于提前见过答案,验证 mAP 会虚高不少。按场景切分后,验证结果才代表真实泛化能力。

4.2 除了 mAP,还要看哪些指标

红绿灯是小目标密集的任务,mAP@0.5 到 0.8 都很常见,但安全实车场景下,我建议额外盯住这几个指标:低置信度下的小目标召回率,防止模型在关键时刻漏检;夜间、雨雾、逆光子集的单独 AP;状态分类的混淆矩阵,尤其是红灯和绿灯、左转箭头和直行灯的混淆。更重要的是,把评测从单帧指标推进到时序指标,看整个视频片段里状态切换是否稳定、有没有闪烁。

这些指标单看 mAP 是发现不了的,我们项目后期绝大部分打磨时间都花在这些“看不见”的指标上。

4.3 难例挖掘:把离线推理变成数据闭环

训练集不是攒完就完事,我们后面建立了一条固定流水线:路测视频定期跑离线模型,把所有低置信度、漏检、错检的帧自动抽出来,人工筛查后进入训练集。这件事看着土,却是项目后期精度提升最大的单一来源。特别是那些罕见灯型(新建路口的倒计时灯、临时信号灯),只有靠这种闭环不断积累样本,模型才会越来越稳。

我建议把难例挖掘做成固定机制,而不是项目里的临时任务。红绿灯场景的角落很多,靠静态数据集永远覆盖不全。

5. 后处理才是真正的护城河:状态判定、时序稳定、地图匹配

模型输出一个框,离车辆能用的“红灯/绿灯/黄灯 + 方向语义”还有很长一段路。这段路就是后处理,也是整个模块工程含量最高的部分。

5.1 把状态判定从检测器中拆出来之后,分类器怎么设计

检测器输出灯面后,我会对灯面区域做 ROI 裁剪,再送入一个轻量状态分类网络。状态类别要覆盖实际路口的常见组合:圆形红、圆形绿、圆形黄、左转箭头红/黄/绿、右转箭头红/绿、直行箭头绿,以及“熄灭/未知”。这里有个容易翻车的细节:不同状态类别的样本量差异极大,左转红灯和右转绿灯都非常稀有,需要单独做样本扩充。分类器输入不一定要很大,因为灯面区域通常已经比较小,关键是从原始分辨率中直接裁剪,不要经过去噪和缩放损失细节。

5.2 状态机与滑动窗口:平衡快速响应和防抖

单帧分类结果直接给规划模块,车辆会像一个神经质乘客——红灯转绿灯瞬间模型犹豫几帧,车辆就开始猛地加速又刹车。我们设计的后处理很简单但很有效:对每个跟踪灯维护一个状态计数器,状态一致连续两帧才切换;而黄灯只需要一帧就生效,因为黄灯窗口太短,必须立即响应。不同状态的确认帧数不完全一样,这个参数要和规划团队一起定,他们最清楚车辆能容忍多少延迟。

这里用一段简化的伪代码表示这个逻辑:

# 简化版红绿灯状态机:连续确认机制 def update_state(obs, prev_state, counter): if obs == prev_state: return prev_state, counter + 1 else: # 黄灯单帧生效,红灯/绿灯需要连续CONFIRM_FRAMES帧确认 if counter >= CONFIRM_FRAMES[obs]: return obs, 0 return prev_state, counter + 1

这样处理下来,状态输出又稳又快,车辆在路口的决策节奏明显改善了。

5.3 地图先验、目标跟踪和车道约束

高精地图给了每盏信号灯的三维位置和朝向。推理时先把灯的三维点投影到图像平面,得到预期位置,再和当前帧的检测框做匹配。这一步能解决很多问题:一是过滤掉远处广告牌、刹车灯等非信号灯的高亮物体;二是把检测框和地图里的具体灯绑定,从而知道这盏灯是否管当前车道。实际工程里,投影会受定位标定误差影响偏移几十像素,因此匹配搜索框要适当放大,不能死抠投影给出的精确坐标。

没有地图的场景我们也做了一版纯视觉版本,依靠灯的位置聚类和跟踪来近似,但可靠性明显弱一档,说明地图先验对这个模块的价值很高。

5.4 数字倒计时:可用但要放在安全边界之外

国内不少路口的信号灯带数字倒计时,这个信息对舒适性很有用——知道还剩三秒红灯,车辆可以提前准备起步。但我们在工程上严格区分:倒计时只参与舒适性策略,不参与任何安全判定,因为倒计时屏可能存在故障、或者与主信号灯不同步,如果把安全建立在倒计时上,一次不同步就可能造成误判。倒计时识别相对独立,用轻量 OCR 或数字分类器就能完成,输出给规划模块做参考。

6. 实车部署与踩坑:从离线优秀到路测靠谱的距离

模型训好、后处理调完,接上真车才是真正考验的开始。这一阶段把我在项目里遇到的坑按影响程度排了个序。

6.1 部署链路与算力分配

我们把 PyTorch 训练的模型导出成 ONNX,再转成推理引擎以 FP16 精度推理。刻意没有用 INT8,因为小目标对量化噪声敏感,INT8 掉点明显,不值得为一点延时牺牲精度。在车端整体算力分配上,红绿灯检测与目标检测、车道线、可通行空间等模块是并行跑的,红绿灯模型占大约 15~25ms 的帧预算,通过独立进程推送结果,避免任何一个模块抖动拖垮整条链路。

6.2 LED频闪和曝光问题:实车第一个大坑

我们第一次路测就碰上了 LED 频闪:肉眼看着常亮的绿灯,在摄像头采集序列里却一颗亮一颗灭,甚至颜色从绿变成黄绿再变回绿。原因是部分 LED 信号灯采用交流供电或脉宽调制,刷新频率和相机曝光节拍打架。这个问题靠模型解决不了,要从两个方面下手:一是与相机团队协商,针对信号灯场景调整曝光时间和白平衡策略;二是后处理里做帧间容忍——一帧没看到灯不立即判定为“灯灭/故障”,允许连续少数帧缺失。更彻底的方案是让相机帧同步跟随市电频率,这需要硬件支持,有条件的话务必和相机供应商联调。

6.3 误检黑名单:刹车灯、广告屏、地面反光

夜间误检主要集中在红色高亮区域:前方排队车辆的刹车灯经常被当成红灯,尤其当车辆刚好停在路口等待区时,刹车灯的位置与信号灯位置在画面上很接近。我们通过在匹配阶段引入地图杆件先验,把不在信号灯杆位附近的红色检测直接过滤掉。其他常见误检还包括路边 LED 显示屏、施工警示灯、雨天积水里的灯光倒影。对这些,单纯依赖模型不太现实,通常需要结合位置先验、尺寸过滤和跟踪一致性来共同压制。

误检来源现象主要处理手段
刹车灯夜间红色亮点出现在路口附近,被识别为红灯地图杆件先验 + 位置匹配过滤
LED广告屏 / 施工警示灯高亮色块随机出现尺寸过滤 + 稳定跟踪
积水反光地面出现第二个“信号灯”利用灯面位置高于路面的先验过滤
白色故障灯体灭灯后白色灯珠被识别为绿灯状态分类器强制白灯不参与绿判断

6.4 安全兜底:模型不可用时的默认行为

最后说一个容易在设计阶段被忽略的问题:当检测器持续丢失信号灯或置信度过低时,车辆该按什么状态行驶?我们的做法是引入“信号灯不可用”状态,当连续多帧无法可靠确认当前灯色,模块主动上报不可用,规划模块进入保守模式——宁可停在路口等待,也不猜测绿灯通行。这个兜底设计可能让通行效率偶尔降低,但从安全角度看,却是整个模块最有价值的一块。

整个模块从无到有跑下来,我最深的体会有两个。第一,红绿灯检测真正的技术含量不在模型,而在围绕模型构建的那一整条链路——数据怎么定义、状态怎么解耦、时序怎么滤波、地图先验怎么配合、相机曝光怎么调,每一环出了问题,最后都会反馈到模型的输出上。第二,别被训练榜单上的漂亮 mAP 迷惑,Robotaxi 要的是连续视频片段里稳定、可解释、有兜底的状态输出,这个标准比任何论文指标都苛刻。YOLOv8 在这个项目里只是一个相当可靠的零件,真正让系统跑起来的,是那些文档里不会写、只有踩过坑才知道的工程细节。希望这篇复盘能帮你把这条路走得顺一点。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进