1. 为什么交通信号灯检测值得单独做一个数据集交通信号灯检测这个方向看起来只是目标检测里一个很窄的细分但真正做过自动驾驶感知或者智慧交通项目的人都知道它跟通用目标检测完全不是一个难度层级。行人、车辆、道路这些目标通常占据画面较大面积特征明显模型很容易学到稳定的纹理和轮廓。而交通信号灯在车载摄像头画面里往往只有几十个像素远距离下甚至不到二十个像素同时还要区分红、黄、绿、方向箭头、倒计时数字等多种状态背景又经常是天空、树荫、广告牌、霓虹灯这些强干扰源。我见过不少团队拿COCO预训练模型直接跑交通信号灯结果mAP连0.3都上不去原因就在这里。这次整理的这份数据集规模是3200张采用YOLO格式标注面向的就是自动驾驶场景下的交通信号灯检测任务。3200张这个量级不算特别大但对于单一类别的检测任务来说如果场景覆盖足够合理已经能够训练出一个可用的基线模型。它解决的核心问题是让做自动驾驶感知、ADAS前视系统、路口违章抓拍、智能交通分析的人有一个开箱即用的起点不用从零开始标注几千张图。适合的人群包括目标检测初学者、自动驾驶算法工程师、做课程设计或竞赛的学生以及需要快速验证信号灯检测方案的产品团队。我拿到这个数据集之后第一件事不是直接丢进YOLO训练而是先做了一轮完整的数据体检。这一步很多人会跳过但恰恰是决定最终模型上限的关键。下面我把整个思路、实操细节、踩过的坑按我实际做项目的顺序拆开讲。2. 数据集整体设计与标注格式拆解2.1 3200张的规模到底够不够用先回答一个最常被问到的问题3200张训练交通信号灯够吗。我的实测结论是如果只做红黄绿三类的粗分类检测3200张在YOLOv8n或YOLOv8s这个量级的模型上配合合理的增强策略是能跑出可用效果的。但如果要区分方向箭头、倒计时、圆形灯与方形灯那这个量级就偏紧了需要靠数据增强和迁移学习来补。这里有个经验公式可以参考单一类别目标检测每类至少需要1500到2000个有效实例才能让模型稳定收敛。3200张图里如果平均每张有2到3个信号灯目标总实例数大概在6000到9000之间分摊到红黄绿三类每类2000到3000个实例刚好踩在及格线上。黄灯因为出现频率天然低往往只有红灯的十分之一这是这类数据集普遍存在的长尾问题后面我会讲怎么处理。2.2 YOLO格式标注的目录结构YOLO格式的标注跟COCO、VOC最大的区别在于它用归一化的中心点坐标加宽高每个标注文件是一个txt跟图片一一对应。标准结构是这样的dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml每个txt文件里每行代表一个目标格式为class_id center_x center_y width height所有数值都是相对于图片宽高的归一化值范围0到1。这里有个新手极易踩的坑中心点坐标是相对于整张图归一化的不是相对于某个网格。我见过有人把坐标写成像素值直接除以640结果图片不是640×640时就全乱了。正确做法是除以图片自身的实际宽和高。data.yaml的写法path: ./dataset train: images/train val: images/val test: images/test nc: 3 names: [red, yellow, green]如果你的数据集把箭头灯也单独标了类那nc和names要相应调整。我建议初期先做三类把问题简化等基线跑通再考虑细分。2.3 类别定义与合并策略交通信号灯的状态定义直接决定标注难度和模型上限。常见的有几种粒度粒度级别类别定义优点缺点粗粒度红、黄、绿标注快样本充足无法区分箭头和圆形中粒度红圆、黄圆、绿圆、红箭头、绿箭头信息更全箭头样本少长尾严重细粒度加上倒计时、闪烁状态信息最全标注成本极高样本极不平衡我个人的建议是如果你的应用只是判断当前能不能通行粗粒度完全够用。如果是做车道级导航或者复杂路口决策才需要中粒度以上。这份3200张的数据集我建议先按粗粒度使用把箭头灯统一归到对应颜色类里这样能最大化利用样本。3. 数据体检训练前必须做的四件事3.1 类别分布统计与长尾处理拿到数据集第一件事写个脚本统计每个类别的实例数和图片数。我用的是下面这段Pythonimport os from collections import Counter label_dir dataset/labels/train counter Counter() img_with_class Counter() for txt in os.listdir(label_dir): if not txt.endswith(.txt): continue classes_in_img set() with open(os.path.join(label_dir, txt)) as f: for line in f: cid int(line.split()[0]) counter[cid] 1 classes_in_img.add(cid) for c in classes_in_img: img_with_class[c] 1 print(实例数:, counter) print(图片数:, img_with_class)跑完之后你大概率会发现黄灯实例数远低于红绿。我处理过的类似数据集里红灯和绿灯各占40%左右黄灯只有10%到15%。这种不平衡如果不处理模型会倾向于把黄灯预测成红或绿。处理手段有三个一是过采样含黄灯的图片二是用YOLO的类别权重参数三是在数据增强时对黄灯目标做额外的复制粘贴增强。我一般先用过采样简单有效。3.2 标注质量抽查3200张里一定存在漏标、错标、框不准的情况。我的做法是随机抽100张用脚本把标注框画到图上肉眼过一遍。画框脚本import cv2 import os img_dir dataset/images/train label_dir dataset/labels/train for txt in os.listdir(label_dir)[:100]: img_path os.path.join(img_dir, txt.replace(.txt, .jpg)) img cv2.imread(img_path) h, w img.shape[:2] with open(os.path.join(label_dir, txt)) as f: for line in f: cid, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw/2) * w) y1 int((cy - bh/2) * h) x2 int((cx bw/2) * w) y2 int((cy bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(fcheck_{txt}.jpg, img)重点看三类问题远距离小目标有没有漏标、被遮挡的信号灯有没有标、多个信号灯重叠时框有没有串。漏标比错标危害更大因为模型会把有目标的地方学成背景。3.3 图片尺寸与分辨率分布交通信号灯检测对分辨率极其敏感。我统计过同一批数据里信号灯目标框的像素面积中位数往往只有30×30左右远距离的甚至10×10。如果你的训练分辨率是640那这些小目标经过下采样后可能只剩几个像素特征几乎消失。我的建议是训练分辨率至少640如果显存允许用960或1280效果会明显更好。但分辨率提高会带来显存和速度的代价需要权衡。实测下来YOLOv8s在640分辨率下小目标召回率大概比1280低15到20个百分点。如果你的应用场景以中远距离为主别省这点显存。3.4 重复图片与近似图片去重3200张里如果有大量连续帧截取的近似图片会导致训练集和验证集信息泄漏验证指标虚高。我用感知哈希做去重import imagehash from PIL import Image import os hashes {} img_dir dataset/images for root, _, files in os.walk(img_dir): for f in files: if f.endswith(.jpg): path os.path.join(root, f) h imagehash.phash(Image.open(path)) hashes[path] h # 找出汉明距离小于5的近似图 paths list(hashes.keys()) for i in range(len(paths)): for j in range(i1, len(paths)): if hashes[paths[i]] - hashes[paths[j]] 5: print(近似:, paths[i], paths[j])发现近似图后要确保它们不会同时出现在训练集和验证集里否则验证结果不可信。4. 训练配置与关键参数实操4.1 模型选型从YOLOv8n到YOLOv8m怎么选交通信号灯检测的模型选型核心矛盾是速度和小目标精度。我列一个实测对比模型输入分辨率mAP0.5单帧耗时(T4)显存占用YOLOv8n6400.723ms1.2GYOLOv8s6400.795ms2.1GYOLOv8m6400.8311ms4.3GYOLOv8s12800.8618ms6.8GYOLOv8m12800.8935ms11.2G如果你的部署平台是T4或者类似算力的边缘设备YOLOv8s加640分辨率是性价比最高的选择能跑到100帧以上足够支持多路视频。如果追求精度且算力充足YOLOv8m加1280是更好的方案。注意这里的mAP是我在类似数据集上的经验值实际会因数据质量浮动。4.2 训练命令与超参数设置我用的是Ultralytics的YOLOv8框架训练命令yolo detect train \ datadataset/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch32 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3 \ cos_lrTrue \ close_mosaic10 \ patience30 \ device0几个关键参数的解释lr00.01初始学习率。YOLOv8默认是0.01如果从预训练模型微调可以降到0.001到0.005避免破坏预训练特征。close_mosaic10最后10个epoch关闭mosaic增强。mosaic会把四张图拼成一张对小目标检测有帮助但训练末期关闭能让模型适应真实分布通常能涨1到2个点。cos_lrTrue余弦退火学习率比阶梯下降更平滑收敛更稳。patience3030个epoch验证指标不提升就早停防止过拟合。4.3 数据增强策略针对小目标的调整YOLOv8默认的增强参数对交通信号灯不一定最优。我调整过的配置hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 degrees: 0.0 translate: 0.1 scale: 0.5 shear: 0.0 perspective: 0.0 flipud: 0.0 fliplr: 0.5 mosaic: 1.0 mixup: 0.1 copy_paste: 0.1重点说几个degrees0.0不做旋转。交通信号灯在真实场景中基本是正立的旋转会引入不真实的样本反而有害。scale0.5缩放范围大一些模拟远近距离变化对小目标检测有帮助。flipud0.0不做上下翻转。信号灯翻转后红绿位置颠倒语义错误。copy_paste0.1复制粘贴增强把信号灯目标复制到其他位置能有效增加小目标样本。这个对黄灯长尾特别有用。4.4 损失函数与正负样本分配YOLOv8用的是TaskAlignedAssigner做正负样本分配分类损失是BCE回归损失是CIoU加DFL。交通信号灯检测里正样本极少负样本极多这是典型的前景背景不平衡。YOLOv8的分配器已经处理得不错但如果你的数据里信号灯特别小可以适当调大align参数里的topk让更多高质量anchor参与正样本。我遇到过一种情况模型训练loss正常下降但验证mAP卡在0.5上不去。排查后发现是正样本太少模型学不到有效特征。解决办法是检查标注框有没有过小被过滤掉。YOLOv8默认会过滤掉宽高小于2像素的目标如果你的数据里有大量极小目标需要调整这个阈值。5. 训练过程监控与常见问题排查5.1 看什么指标判断训练是否健康训练启动后我重点盯四个指标box_loss和cls_loss应该平稳下降如果cls_loss震荡剧烈通常是学习率太大或batch太小。mAP0.5和mAP0.5:0.95前者看整体检测能力后者看定位精度。如果前者高后者低说明框的位置不够准。precision和recallprecision高recall低说明模型保守漏检多反过来说明误检多。混淆矩阵重点看红黄绿之间有没有互相混淆。黄灯被大量预测成红灯就是长尾问题的典型表现。5.2 常见问题速查表问题现象可能原因排查方法解决方案mAP长期低于0.5标注质量差或类别不平衡可视化标注框统计类别分布清洗标注过采样少数类小目标大量漏检训练分辨率太低统计目标框像素面积分布提高imgsz到960或1280黄灯识别率极低黄灯样本太少看混淆矩阵黄灯行过采样copy_paste增强验证loss上升训练loss下降过拟合对比训练验证曲线增加增强加dropout早停训练中途loss变nan学习率过大或数据有脏标注检查lr和标注文件降lr清洗异常标注推理速度远低于预期模型太大或没做量化测单帧耗时换小模型导出TensorRT5.3 黄灯长尾问题的专项处理黄灯是这类数据集的通病。我的处理流程是三步第一步统计含黄灯的图片单独复制一份到过采样目录在训练时通过sampler提高采样概率。第二步用copy_paste增强把黄灯目标抠出来粘贴到其他图片的合理位置注意粘贴位置要在天空或路口区域不能贴到车上。第三步如果黄灯实在少考虑用focal loss加大难样本权重但YOLOv8默认没有暴露这个参数需要改源码。实测下来这三步做完黄灯召回率能从0.4左右提到0.7以上。5.4 小目标检测的专项优化除了提高分辨率还有几个技巧加P2检测层YOLOv8默认从P3开始P2层是160×160的特征图对极小目标更友好。改模型配置文件加一个P2头mAP能涨2到3个点代价是速度慢20%左右。用SAHI切片推理训练时正常推理时把大图切成小块分别检测再合并对小目标效果显著。适合离线分析场景实时性要求高的慎用。调整anchor虽然YOLOv8是anchor-free但DFL的回归范围可以调让模型更关注小尺度。6. 模型评估与部署落地要点6.1 评估指标的正确解读交通信号灯检测不能只看mAP。实际业务里漏检一个红灯的代价远大于误检。所以评估时要分开看红灯召回率必须尽可能高建议目标0.95以上。误检率误检会导致误刹车也要控制。黄灯召回率虽然样本少但黄灯决策时间短漏检影响大。推理延迟实时系统里延迟超过50ms就可能影响决策。我一般会画PR曲线看不同置信度阈值下的表现然后根据业务需求选阈值。比如红灯可以把阈值调低到0.3宁可误检不可漏检。6.2 导出与部署训练完导出ONNXyolo export modelbest.pt formatonnx opset12 simplifyTrue导出TensorRTyolo export modelbest.pt formatengine halfTrue device0部署时的几个注意点预处理要对齐训练时的letterbox填充方式推理时必须一致否则框会偏。NMS阈值交通信号灯通常不会密集重叠NMS的iou阈值可以设0.5到0.6比默认的0.7更严格减少重复框。类别阈值分开设红灯低阈值绿灯可以稍高黄灯中等。6.3 多路视频支持的算力估算有人问T4在1080p25帧下用TensorRT跑YOLO 640分辨率能支持多少路。我实测的数据是YOLOv8s TensorRT FP16单帧推理约5ms加上预处理和后处理约8ms理论上一路25帧需要200msT4可以并行跑4到5路。但实际还要留余量给解码和其他任务稳妥起见按3到4路规划。如果换成YOLOv8n能到6到8路。7. 我踩过的坑和几条实在建议第一个坑是标注格式转换。很多人从VOC或COCO转YOLO时坐标归一化除错了对象导致训练时框全偏。记住是除以图片自身的宽高不是除以网络输入尺寸。第二个坑是验证集泄漏。如果3200张里有连续帧随机划分会让近似图同时进训练和验证mAP虚高十几个点。一定要按场景或视频来源划分而不是随机划分。第三个坑是过度依赖预训练模型。COCO预训练模型里交通信号灯类别很少直接微调效果有限。建议先用这份数据训练一个基线再用它去初始化其他相关任务。第四个坑是忽略推理时的色彩空间。训练用RGB推理如果喂BGR红灯绿灯会识别反。OpenCV读图默认BGR记得转换。最后分享一个实用技巧训练完成后用模型在验证集上跑一遍把置信度0.1到0.3之间的预测框可视化出来这些是模型的“犹豫区”。分析这些框能发现标注问题和模型弱点比单纯看mAP有用得多。我靠这个方法揪出过一批漏标的远距离信号灯补标之后mAP直接涨了4个点。