YOLOv8红绿灯检测的Robotaxi实战:从数据标注到部署避坑
Robotaxi感知系统里的红绿灯检测看起来只是“把一个检测模型跑起来”但真正做过一次就会发现这大概是整个感知链路里最磨人的模块。白天阳光直射、晚上过曝、雨夜反光、远方像素只有几乘几的小灯体、黄灯闪烁时到底算不算可通行……任何一个小问题在真实道路上都会被放大成路段事故。这篇内容我围绕“基于YOLOv8的实时红绿灯检测”展开分享我们这个Robotaxi项目里从模型选型、数据标注、训练调参到部署上车的完整过程也会把踩过的坑、排查过的误检和最后沉淀下来的工程经验一并写出来。不管你是在做自动驾驶感知还是只是用YOLOv8做交通视觉识别这篇都值得花几分钟读完。没做过这个方向的人看完也能少走很多弯路。1. 这个项目到底在解决什么问题1.1 红绿灯识别为什么是Robotaxi绕不开的坎Robotaxi要跑在开放的城区道路交叉口是最高危的场景之一。车辆在路口的通行权直接由红绿灯决定这个信息一旦读错后果不亚于激光雷达漏掉了行人。很多人觉得“红绿灯不就是红灯停、绿灯行”但放到真实感知系统里完全不是这么回事。你面对的是一台车载摄像头不是人眼。摄像头安装在车顶或者前挡风玻璃位置信号灯通常距离车辆几十米到上百米在画面里可能只占十几个像素。更麻烦的是信号灯本身有圆灯、箭头灯、倒计时数字、多方向组合灯不同路口的灯杆高度、角度、周围背景五花八门。晴天逆光时灯体被阳光淹没晚上点亮的灯体又容易过曝成一片光晕雨夜地面反光、对向车远光灯、公交车LED广告都会出来“捣乱”。所以红绿灯检测本质上不是“识别颜色”那么简单。它需要做到三件事在远处提前发现信号灯区域在近处准确识别当前点亮的灯态同时还要区分方向语义——红箭头、红圆灯、绿箭头、绿圆灯对应的通行规则完全不一样。这后面还跟着车辆能不能走、什么时候走等规划层的问题。感知模块在中间的定位很明确必须实时、稳定、低延迟地把灯态结论送给决策模块。1.2 方案选型为什么选中YOLOv8而不是更重或更传统的方案项目启动时我们其实对比过好几条技术路线。第一类是传统视觉方案靠颜色阈值、边缘检测、HSV分割来提取红绿区域再结合连通域判断灯态。这种方案在固定场景的仿真里表现尚可但到了真实道路曝光变化、反光、色偏都会让颜色阈值瞬间失效。我们在一轮晴天黄昏测试里看到了大量误检就把它淘汰了。第二类是更重的人工特征加分类器方案比如HOG特征配支持向量机或者滑动窗口跑CNN分类。这类方案识别率还行但计算开销大窗口密度提不上去实时性和多尺度泛化都不够理想。第三类就是端到端的深度学习目标检测。候选里有Faster R-CNN这类两阶段模型精度是好但单帧在嵌入式平台跑进几十毫秒很难。也试过当时刚出的YOLOv5精度和速度相对均衡但后期在部署、动态形状支持上遇到一些限制。最终定在YOLOv8主要是看重它anchor-free设计简化了训练和部署的复杂度C2f结构在有限算力下特征提取效率高而且官方就带TensorRT导出支持后面上车推理省了不少事。这里要说一下我的选型原则自动驾驶感知里的目标检测模型不一定非得是精度最高的那一个但一定要是“能稳定跑到目标帧率、且后续迭代链路顺”的那一个。YOLOv8生态成熟训练、量化和部署的工具链都很完整社区资料也多遇到问题能快速找到参考。换一个再先进的模型如果部署和调试周期太长原本的好底子也白搭。1.3 系统的整体感知链路在展开讲模型之前有必要把红绿灯检测在Robotaxi感知系统里的位置说清楚。我们这套感知链路不是“一根摄像头进一个红绿灯结论出”的简单结构。车身有前视主相机、广角近距相机感知模块内部还分目标检测、语义分割、自车定位等若干子模块。红绿灯检测负责的是前向视觉里“路权信号”这一路。具体来说红绿灯检测链路包括相机图像采集附带传感器曝光参数和精确时间戳根据高精地图或语义地图的路口信息计算前方信号灯可能出现的感兴趣区域在感兴趣区内做高分辨率针对性检测在全图上做辅助校验检测结果带上一帧置信度和框坐标进入信号灯跟踪和状态管理模块跟踪模块输出稳定的灯态语义送进行为规划模块的交通状态机。这个链路和单纯“图像检测”最大的区别在于时间维度和实例维度。规划层需要的是“这个路口当前允许直行、禁止左转”这类语义而不是一帧帧零散的框。所以在检测模型后面我们还做了一套红灯绿灯的实例跟踪与状态保持机制这部分后面单独展开。2. 训练数据与标注效果好坏的大头2.1 数据来源与清洗策略很多人在做目标检测时容易轻视数据直接拿开源数据集跑YOLOv8跑完一看指标挺高一上路就露馅。红绿灯这种小目标、强光照变化的目标数据质量对模型效果的影响比模型结构大得多。我们最初的模型在公开数据集上mAP有0.85但实车路口远距离漏检率很高最后发现是训练集里大量样本都是近距离大头灯。我们的数据主要有三路来源。第一路是公开的自驾数据集里抽取的交通信号灯图像覆盖白天、黄昏、夜晚、雨天、雾天等常见条件。第二路是车队实测采集这里面有不少是同一路口不同时段反复跑的冗余数据。第三路是困难样本补采专门找复杂路口、逆光时段、多杆多灯的场景拍摄。数据清洗这一步非常关键。要去掉三类无效样本严重运动模糊导致灯体不可辨认的帧曝光异常导致整幅图白或黑的帧以及标注与可见灯态不一致的帧。清洗不干净模型学到的是“模糊照样给结果”真正上路后安全就是大问题。我们还按路口类型做过一次去重避免同一条路反复出现导致训练集和验证集高度相似否则验证指标会虚高实际泛化能力不足。2.2 标注细节类别、框定边界和特殊灯态红绿灯标注不是拿画框工具随手画个框就行。类和框的定义直接决定模型能学出什么。我们最终定义了六类基础灯态红色圆灯、绿色圆灯、黄色圆灯、红色箭头灯、绿色箭头灯、黄色箭头灯。部分场景还增加了“倒计时数字区域”标志但不在检测模型里单独分类而是放到后续后处理。这里有个和直觉不一致的点框的边界要以“点亮的灯体表面”为准不是整个信号灯箱体。因为颜色判断依赖的是发光区域如果框把黑色的背板、灯口边缘都包进来模型在训练时就会把大量背景信息也当成灯体特征反而干扰分类。刚开始我们用的就是整个信号箱框结果夜间误检明显增多后来严格改成灯体外切框整体准确率上升了一截。特殊灯态也要单独处理。比如红灯时长着左转绿箭头这时候画面里会同时存在红圆灯和绿箭头两个类都要标注不能觉得“矛盾”就只标其中一个。还有黄灯闪烁时整个路口四个方向都是黄闪模型要稳定输出黄灯而不是在红黄之间跳变。另外信号灯被树木、货车车身遮挡时能看见的发光局部也要正常标注让模型学会用小区域信息判断灯态。2.3 数据平衡与增强策略红绿灯样本天然不平衡黄灯出现时间短、占样本少夜间的样本由于采集风险高也比白天少。我们统计了一版训练集分布红绿黄三类占比大约是45%、45%、10%箭头灯整体只占15%左右。这个比例直接投进去训练没问题但箭头灯识别率会偏低后来通过复制增强和补采把箭头灯提升到25%左右才好转。数据增强方面YOLOv8自带马赛克、仿射变换、HSV扰动等手段。对红绿灯这个任务HSV扰动要格外谨慎。颜色是红黄绿分类的核心依据如果把色调饱和拉得太猛红灯可能被增强成橙色绿灯变得偏青模型反而学歪。我们只采用小幅HSV扰动更多依赖亮度对比、高斯噪声、运动模糊和随机光照模拟。夜间场景不够时会对正常图像做“模拟过曝”处理把灯体周边做径向模糊模拟镜头光晕。这种增强对夜间灯态识别帮助很大。还有一个细节是图像缩放增强我们刻意把灯体缩小到十几像素再放大回来训练提高模型对远距离小目标的适应力。这个操作一开始看起来很奇怪但确实让远距离召回率肉眼可见地提升了。3. YOLOv8模型的训练与迭代3.1 模型结构简述与选型理由YOLOv8的核心优势在结构上并不复杂主干部分用C2f模块替换了之前的C3结构通过更丰富的梯度流提升特征提取能力检测头的设计采用anchor-free方式每个位置直接预测目标的中心点和边界不需要预设锚框这样对不同尺寸的灯体更友好——红绿灯的大小变化范围其实很大从图片里几像素到几十像素都有anchor-free能少一步调锚框的功夫。我们在实际项目里用的是YOLOv8n到YOLOv8s这个量级。为什么不用更大的m或l因为Robotaxi车端算力还要跑分割、障碍物检测等一堆模型分给红绿灯的推理预算很有限。YOLOv8s在1280输入分辨率下的单帧推理耗时要做到几十毫秒以内实测基本够用。这里要补充的是模型输出不是“只有一个框”而是每个灯态类别都有检测框和置信度。我们会把“点头灯”“向左箭头灯”这类方向信息放在类别维度里而不是额外做一个方向回归头。因为方向在真实世界里是一个明确的离散语义让模型直接输出箭头类比回归角度更稳。3.2 关键训练参数与调参经验我们的训练配置经过多轮实验最终固定为输入尺寸1280批次16初始学习率0.01权重衰减0.0005训练轮数200轮左右。相比常规COCO训练红绿灯检测需要更高输入分辨率因为小目标在640分辨率下特征已经衰减得厉害模型很难学出“灯体发光区域”的细节。损失函数方面YOLOv8默认组合包括分类损失、边界框损失和DFL损失。其中DFL对边界框的精确定位贡献很大我们保留了默认权重。分类损失采用二元交叉熵多标签输出在一个灯箱中同时出现多个亮灯状态时也能正常工作。调参过程中印象最深的是置信度阈值和NMS参数。模型训练完刚推理时我们沿用默认阈值0.25结果一辆公交车的红色尾灯经常被当成红灯后来又调高到0.45误检少了但远距离真灯召回也下降了。最后把阈值设为0.4并配合动态置信度调整近距离信号灯阈值可以放宽到0.35远距离信号灯由于框小、像素少反而要把阈值收紧到0.5以上。这个策略后面在部署环节具体说。3.3 训练曲线怎么看过拟合、欠拟合与难例训练过程中不能只看训练集损失和验证集精度。红绿灯的任务特点在于mAP达到0.8以上后真正的瓶颈是那些“看起来简单但模型一直分不清”的难例。我们会定期从验证集里挑出模型预测错误的图片把它归成几类远距离小目标漏检、夜间过曝误检、雨天反光误检、多灯同时点亮时的框漂移。远距离小目标漏检常见原因是训练时小目标贡献的损失占比太低。YOLOv8在标准设定下对微小目标不敏感为了解决这个问题我们后面单独增加了“切片放大”策略不单靠全图检测而是将路口区域切成局部图块投入模型配合全局检测做融合。这个方案对小目标召回率的提升比单纯调参有效得多。过拟合在数据量足够时反而不是主要问题更常见的是“特异性过拟合”——模型在某一条测试路段表现很好换一条路就退化。我们后来在数据采样时有意做“按路口拆分”保证训练集和验证集中没有同一条路的重叠样本这样验证指标才真实反映泛化能力。4. 实时推理与工程化部署4.1 从模型到服务部署框架选型模型训练出来只是第一步上车部署才是真正考验工程细节的环节。我们部署时没有直接在PyTorch环境里跑而是把YOLOv8导出为ONNX再用TensorRT做FP16推理。FP16相比FP32精度损失很小但显存占用和延迟都下降一半左右在车端算力紧张时是必选项。TensorRT导出时最容易忽略的是动态输入和动态输出。我们输入分辨率固定为1280但切片放大模块要跑不同尺寸的图块所以输入张量仍然保留批大小维度和宽高维度的动态范围。输出端需要处理多个检测类别在不同批次下的数量变化导出时要适配检测输出层的Select节点否则部署时经常报错。实测下来YOLOv8s在FP16、1280输入下单帧推理时间大约在25毫秒到35毫秒之间。如果我们只用640输入推理能压到10毫秒以内但远距离召回损失明显。后面我们采用“主检测器640 感兴趣区域放大检测器1280”的组合路径综合延迟反而不高这正是要给规划层稳定输出所必须做的妥协。4.2 帧率与延迟预算拆解Robotaxi的红绿灯检测不能单独只看模型推理时间还要算上图像采集、前处理、后处理、跟踪和通信的完整链路。我们的延迟预算大致是相机曝光加采集耗时5-10毫秒图像前处理含缩放、通道转换、归一化耗时2-3毫秒模型推理25-35毫秒后处理含NMS耗时1-2毫秒跟踪与状态管理耗时2毫秒。整体链路从图像曝光到输出稳定的灯态结论大约需要40-50毫秒。这个延迟听起来不大但车辆以60公里时速行驶时50毫秒就对应接近1米的车程。所以我们的做法是对紧急制动类状态不等到拿到下一帧灯态再决策而是由跟踪模块在输出一次稳定结果后结合自车速度和距离信号灯的距离做短期保持。如果连续3帧检测到同一红灯即使后续某一帧漏检系统仍然维持“红灯”输出直到产生新的检测或超时。帧率方面主相机是30帧采集但红绿灯检测不需要每一帧都跑完整推理。我们采用“全图检测隔帧执行感兴趣区域检测每帧执行”的降载策略。因为路口信号灯位置相对固定高精地图能给出前方路口信号机的世界坐标投影到图像后形成一个稳定的区域全图检测只是兜底而已。4.3 时间平滑与追踪策略单帧目标检测天然存在抖动红灯在某一帧输出置信度0.6下一帧变成0.2再下一帧又不稳定这种输出直接给规划层是不行的。所以我们做了信号灯实例追踪模块不给每一个框都分配一个新ID而是用位置和类别做匹配在连续帧之间维持同一个信号灯的稳定状态。追踪匹配的核心思路很简单预测当前帧中信号灯的可能位置和高精地图投影结果结合再和当前帧检测框做最近的匹配。匹配上后更新该实例的连续命中帧数、最近置信度均值、灯态。只有当连续命中帧数达到3帧且平均置信度超过阈值这个灯的结论才会从“候选状态”变成“有效状态”。当某一帧漏检时实例不会立刻消失而是保留一定时间窗口用最近一次的有效灯态继续输出并标记“置信度随时间衰减”。整个过程类似卡尔曼滤波但我们在工程上先用了代码逻辑实现避免过度依赖复杂数学带来的调试成本。追踪模块还负责把多个检测结果组合成路口级状态。一个路口可能有十几个信号灯包括直行灯、左转箭头灯、右转箭头灯、人行横道灯。我们根据高精地图上信号灯的归属关系把检测实例分组到对应的交通流方向。规划层需要的是“对于当前车道的当前流这个灯是红还是绿”而不是一长串信号灯坐标。所以追踪模块的最后一件事是合并生成一个结构化的交通信号状态输出。4.4 实车感知链路整合整个部署链路跑通后我们在实车测试中额外加了一个“真值记录仪”的模块。每辆车跑路测时会把原始图像、检测框、追踪状态、车端其他传感器信息一并录制方便后续离线回放和问题分析。没有这个环节在线排查误检漏检会非常低效。信号灯检测模块和底盘控制模块之间还加了一层“路权状态机”。因为交通信号灯的特殊性在于红灯变绿灯、绿灯变黄灯、黄灯闪烁的含义各自不同。状态机负责把连续帧的灯态序列转化成可用的驾驶指示当检测到红灯且前序状态是绿灯时说明刚变灯当红灯持续超过若干帧后进入稳定的“停车等待”状态当黄灯闪烁时既不是可通行也不是停稳等待而是建议让车辆减速观察。这层状态机不属于感知模块但感知模块必须提供足够稳定的灯态序列它才能正确工作。我们在联调时发现的问题很多都不是单帧检测错误而是状态切换的时序问题所以一开始就做这层整合是值得的。5. 常见问题与排查技巧实录5.1 误检、漏检的典型场景误检和漏检在实车测试里的表现场景非常有规律。误检主要集中在三个地方第一个是公交车尾灯和危险报警闪光灯红色灯泡尺寸、位置和红绿灯灯体在某些距离下非常相似第二个是道路两侧的LED广告屏尤其当广告画面里出现红色圆形元素时模型极易低置信度误报第三个是夜间刹车灯在镜头里产生高亮光晕和红灯的光晕特征很像。漏检则多发生在远距离小目标特别是有逆光时信号灯被树冠、货车箱体部分遮挡雨天玻璃水渍加雨刷遮挡画面局部模糊以及高架上信号灯在画面边缘、超出常规模型感受野。这些情况下单靠调高模型精度很难完全堵住必须靠追踪和落地后处理一起解决。5.2 NMS与置信度阈值调整红绿灯目标尺寸小、数量多而且一个灯箱里可能同时亮起几个灯体NMS如果设置不当会出现“把两个相邻的灯体当成一个框”或者“把同一个灯体重复检测出多个框”的问题。我们维护了一组分路况的NMS参数对圆形灯因为灯体间距固定IoU阈值调到0.5对箭头灯因为箭头更细长IoU阈值调到0.4避免互相抑制。置信度阈值还有一个技巧就是给追踪模块反馈。检测器输出低置信度框时不直接丢弃而是作为“弱候选”传给追踪模块。只有当追踪模块连续多帧看到同一位置的“弱候选”且置信度趋势上升才会确认为新信号灯。这种做法比单纯提高置信度阈值更能兼顾召回和误检因为误检往往是孤立出现的而真实信号灯会在连续帧里稳定出现。5.3 追踪切换和状态机细节追踪模块最容易出现的一个问题是实例ID频繁切换。原因是信号灯距离变化时检测框大小不断变化如果匹配阈值设置过窄同一个灯可能在不同帧被当作两个实例。我们限制了相邻帧同一实例的位置变化范围并加入“最近邻匹配”只有在同一类别、同一交通流方向且位置漂移小于设定距离时才算是同一个灯。这个设定在车距变化快的高速匝道场景尤其重要否则状态机收到的ID是不断重连的根本没法做稳定性判断。另一个细节是灯态突变处理。正常灯态变化会经过黄灯过渡但真实路口里也会出现由红灯直接跳转绿灯、由绿灯直接转红灯的特情。状态机专门设了一个“灯态跳变验证”机制如果在连续两帧里信号灯从红变成绿但黄灯从未出现过系统不会立即批准通行而是要求额外确认一帧或继续观察零点几秒。这个机制在部分特种车辆优先信号灯控的路口出现过实测避免了至少一次潜在错误起步。5.4 夜间过曝与ISP参数联动夜间的红绿灯虽然灯体发光明显但摄像头为了看清非灯体区域的暗环境会自动拉高曝光时间或增益结果灯体会严重过曝。过曝后的灯体中间变成白色四周一圈光晕模型可能输出“白色灯”而不是红色或绿色本质上这就是自认为颜色特征消失了。这个问题不在模型层面能完全解决关键在于相机ISP参数要和感知算法联动。我们改变了单反自动曝光策略对包含信号灯感兴趣区域的画面降低曝光补偿让高亮灯体不过曝到丢失颜色。同时限制最大增益和最大曝光时间避免在暗场景下拉出太多噪声。这样做的副作用是车身周围暗部细节变少但感知主链路的障碍物检测有自己的相机和曝光策略红绿灯相机单独调优即可。6. 项目复盘与我的几点感触6.1 实测踩坑时间线如果按照时间线复盘这个项目有几个阶段记忆特别深刻。第一阶段是最初的“模型幻觉期”。训练完模型在仿真环境回放视频里远距离红灯识别效果很好我们一度以为很快就能上车。结果第一次实车晚高峰测试模型在距离路口五十米的地方对红灯的识别率直接降到六七成原因是仿真里的图片几乎都是清晰大灯体真实视频里过曝和抖动问题严重得多。第二阶段是做数据清洗和重新标注。我们花了两周时间把原本“按可见颜色”的标注规范改成“按亮灯灯体”的规范并且把箭头灯单独分离出来。这个改动让模型精度提升非常明显验证集mAP从0.82涨到0.88误检率下降三成。所以我一直觉得红绿灯检测项目里最值得投入的不是模型结构而是标注规范和数据分布。第三阶段是部署期的TensorRT适配。最开始按固定尺寸和固定批大小导入结果实际运行动态图块时频繁报错反复调了一周才理顺。这部分经历让我们后续所有模型都要求在上车之前跑一遍动态输入导出流程避免在车上调试浪费时间。第四阶段是夜间优化。我们一度尝试在模型层面增加更多夜间增强数据效果有但治标不治本。后来改成和相机工程师一起调曝光参数和ISP夜间误检率才真正降下来。这让我深刻体会到感知算法不是孤立的相机硬件参数、图像质量、甚至安装位置都会直接影响模型上限。6.2 如果重来一次我会怎么优化如果让我重新做一次这个项目有几个地方我会一开始就做好。第一在数据采集阶段就加入“按灯体而非信号箱”的标注规范不让自己走弯路。标注规范一旦定下来返工成本极高而它恰恰是红绿灯检测效果的分水岭。第二我会更早搭建追踪和状态管理模块。一开始我总以为检测模型过关了、追踪就是顺带做的事后来发现问题恰恰出现在“模型输出框抖动但规划层需要稳定结论”的中间地带。追踪模块不是锦上添花它和检测模型同等重要。第三我会从一开始就给每辆车录制带时间同步的原始图像流。前期的很多问题因为没有录制原始数据只能靠现场沟通效率太低。有了数据回放后排查一个误检定位到帧的现象基本一天就能完成。第四也是我特别想提醒后来人的一点不要只看mAP。红绿灯任务里mAP和实车安全性相关性不高。mAP反映的是“框画得多像”但不反映“灯态状态切换是否稳定、是否会瞬间跳变”。建议在模型评估之外增加一个序列级别的准确率指标连续十来帧为窗口统计“窗口内过度状态输出是否稳定”。这样评估结论才更贴近真实行驶需求。最后再分享一个实用技巧如果你也在用YOLOv8做交通灯识别可以把模型推理结果做一个简单的“短期滑动窗口投票”——对同一个信号灯实例取最近5帧的灯态做众数只有众数票数超过3票才更新输出。这个技巧不增加多少算力但对状态跳变的抑制非常有效。我是在一次连续黄闪测试里偶然试出来的后来一直保留在这个项目的正式版本里。红绿灯检测越做到后期越会意识到耐住性子打磨工程细节比追求模型极致精度更重要。