简介YOLO红绿灯目标检测数据集采用真实道路场景中的高质量图片面向目标检测初学者、课程实训及算法调优人员解决红绿灯识别训练中标注数据不足、格式不统一的问题。数据使用LabelImg标注标注框质量高并已整理为VOC、COCO和YOLO三种格式标签分别存放于独立文件夹可直接用于YOLO系列模型训练。整套资源共2000个文件核心由xml标签、txt标签和yaml配置组成另有html环境搭建与训练教程、py数据集划分脚本压缩包大小约487.95MB。配套内容覆盖Linux与Windows双平台的YOLO环境搭建、训练案例讲解以及将图片和标签自动划分到训练集、验证集、测试集的工具脚本方便按项目需求灵活拆分数据。目前已有863人学习下载适合希望以完整真实场景数据快速上手YOLO红绿灯检测任务的用户。1. 红绿灯目标检测数据集拿来就能训但先别急着解压前阵子接了个智慧交通的活要在路口摄像头画面上把红绿灯检测出来红灯、绿灯、黄灯三种状态还得分开。模型好说YOLO 系列挑一个就行难的是数据——公开的目标检测数据集里交通标志一大堆红绿灯真正能用的很少要么是欧洲路口风格要么是无人机俯拍跟国内平视摄像头拍出来的画面差很远。后来我搞到一份红绿灯目标检测数据集真实场景图片配齐了 VOC、COCO、YOLO 三种格式标签还带了划分脚本和训练教程。这篇文章就把这个资源拆开讲清楚三种标签怎么对、划分脚本该用哪个、训练参数怎么设、哪些坑必须绕开。适合正在做自动驾驶感知、智慧交通项目或者刚学 YOLO 想找个能直接落地数据集练手的工程师。2. 三种标签格式VOC、COCO 与 YOLO 的差异以及一个能抄走的转换脚本2.1 为什么同一份数据要做成三种格式解压压缩包后你会看到标签被分成了三个文件夹分别对应 VOC、COCO、YOLO 三种格式。第一次用的人容易犯嘀咕为什么不统一成一种因为不同训练框架和代码库认的格式不一样。YOLO 系列原生用的是 txt每行一个目标SSD、Faster R-CNN 这类老代码常读 VOC 的 xmlmmdetection、Detectron2 则习惯吃 COCO 的 json。同一份数据切成三种格式本质上就是让你拿到任何项目都能直接塞进去训练不用再折腾一遍转换。三种格式的核心差异在于边界框的表示方式。VOC 的 xml 记录的是像素绝对坐标也就是 xmin、ymin、xmax、ymax 四个整数单位是像素读起来直观但文件体积大一个 xml 要嵌套一堆节点。COCO 的 json 里 bbox 字段是 [x, y, width, height] 的浮点数组x、y 是框左上角坐标。YOLO 的 txt 则是归一化坐标一行一个目标五个数字依次是类别 ID、中心点 x、中心点 y、框宽、框高全部除以图片宽高范围在 0 到 1 之间。格式文件后缀bbox 表示典型使用场景训练时需要额外注意VOCxmlxmin, ymin, xmax, ymax像素老版 Faster R-CNN、SSD需解析 xml 树COCOjsonx, y, width, height像素mmdetection、Detectron2需按 categories 匹配 IDYOLOtxtx_center, y_center, w, h归一化YOLOv5、YOLOv8、YOLOv11类别 ID 与 names 顺序强相关这里有个小细节压缩包里说明文档把标注软件写成了 “lableimg”实际是 LabelImg文档错别字不影响使用。但你拿到数据后建议先自己做一次质量校验别只信说明文档。2.2 先用脚本确认数据质量统计各类目标数量训练之前我习惯先统计一遍各类目标框数量心里有底再决定要不要做类别平衡。YOLO 的 txt 标签最好统计每行代表一个目标。下面这段脚本扫一遍标签目录输出每个类别的框数import os label_dir labels/train # 改成你的实际标签路径 class_names [red, green, yellow] # 以数据集实际的 names 顺序为准 counts {name: 0 for name in class_names} total_boxes 0 for txt_name in os.listdir(label_dir): if not txt_name.endswith(.txt): continue with open(os.path.join(label_dir, txt_name), r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) if cls_id len(class_names): continue counts[class_names[cls_id]] 1 total_boxes 1 print(总目标框数:, total_boxes) for name, cnt in counts.items(): print(f{name}: {cnt})这段代码的逻辑很简单遍历标签目录下所有 txt每行按空格拆成五段第一段是类别 ID用它去 class_names 里查名字。注意这里假设了类别 ID 从 0 开始且顺序固定如果数据集里黄灯样本特别少后面划分数据集时就要小心。统计结果出来后如果红灯两千个、绿灯一千五、黄灯只有两三百那就别指望黄灯能训出高精度这种真实场景数据的不均衡是常态不是模型 bug。这个数据集因为是真实路口采集大概率存在类似情况先有预期后面才不会慌。2.3 自己标完新图怎么转VOC XML 转 YOLO TXT 的脚本这份资源本身三种格式都齐了直接用即可。但等你上手跑完一轮大概率会想往数据集里补自己的图片LabelImg 默认存的是 VOC xml这时候就得转成 YOLO txt。坐标转换的核心是归一化公式x_center 等于 (xmin xmax) 除以 2 再除以图片宽度宽高同理。import os import xml.etree.ElementTree as ET from glob import glob def voc_to_yolo(xml_file, out_dir, class_names): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_name os.path.splitext(os.path.basename(xml_file))[0] .txt lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines)) class_names [red, green, yellow] # 改成你的实际类别名 os.makedirs(labels_out, exist_okTrue) for xml_file in glob(annotations/*.xml): voc_to_yolo(xml_file, labels_out, class_names)这段脚本里有个容易忽略的判断if name not in class_names。你标注的时候可能顺手标了别的物体比如行人、车辆训练红绿灯模型时这些目标不应该参与直接跳过最省事。另一个要注意的点是 LabelImg 导出的 xml 里一定有size/width和size/height但如果你用其他工具生成过 xml结构可能略有差异建议转换前打印一根 xml 看看字段名。3. 划分训练集、验证集、测试集三个脚本的分工、顺序和参数3.1 先分清三个脚本哪个复制文件、哪个只生成列表压缩包里带三个划分脚本名字很像第一次接触的人很容易混一个叫“训练集、验证集、测试集划分脚本图片标签划分写入新文件夹.py”一个叫“训练集、验证集划分脚本图片标签划分写入新文件夹.py”还有一个叫“split_train_val 生成 ImageSets 下 txt 文件划分脚本.py”。三者的核心区别在于前两个会把图片和标签物理复制到新文件夹第三个只生成一行行图片路径的 txt 列表文件不复制任何数据。脚本输出结果是否复制文件适合的训练方式train/val/test 三份划分三个独立文件夹是YOLOv5/YOLOv8 的 data.yaml 方式train/val 两份划分两个独立文件夹是不需要测试集的场景split_train_val 生成 txt若干 txt 列表文件否老式 VOC 训练流程如果你打算用 YOLOv8 或 YOLOv5推荐先用“复制到新文件夹”的脚本因为 data.yaml 里直接写train: images/train就能用干净利落。split_train_val 那个脚本是给需要手动读列表的代码准备的用得少但存在即合理。下面重点讲两个复制型脚本怎么调参数。3.2 推荐先跑的脚本训练集/验证集/测试集划分脚本我一般优先用三份划分的脚本因为测试集从一开始就独立出来后面验证模型泛化能力才有依据。这类脚本的核心逻辑是读入所有图片文件名按比例随机打乱用 random.shuffle再把对应图片和标签复制到目标目录。脚本开头通常是这样的结构import os import random import shutil random.seed(42) # 固定随机种子每次划分结果一致 train_ratio 0.7 val_ratio 0.2 test_ratio 0.1注意这个 train_ratio、val_ratio、test_ratio 三个值加起来必须等于 1否则脚本会报错或者少一类数据。如果数据集只有几百张建议把测试集比例降到 0.1把更多数据留给训练一千张左右的话 7:2:1 是合理选择。random.seed(42) 这行特别重要不设置随机种子的话每次运行划分结果都不一样后面排查问题时分不清是数据变了还是代码变了。脚本运行前要改三处路径图片源文件夹、标签源文件夹、输出根目录。图片和标签必须放在不同的两个源文件夹里比如 images/ 和 labels/脚本按同名前缀去找对应文件。这个数据集里图片和标签是配套的不会出问题但你自己扩充数据后就要注意同名检查最稳妥的做法是先跑一段校验代码找出只存在图片或者只存在标签的文件名import os img_dir images label_dir labels img_names {os.path.splitext(f)[0] for f in os.listdir(img_dir)} label_names {os.path.splitext(f)[0] for f in os.listdir(label_dir)} print(缺标签的图片:, img_names - label_names) print(缺图片的标签:, label_names - img_names)这一跑如果有输出先把数据补齐了再划分不然后面训练会遇到图片有标签但对不上框这种玄学问题。3.3 只生成列表文件的 split_train_val 脚本什么场景才需要这个脚本生成的 txt 文件每行是一个图片路径格式类似JPEGImages/traffic_001.jpg。老版本的 YOLOv3、YOLOv4 训练经常需要这种列表YOLOv5 之后已经不太用了。如果你手头的代码还是读 txt 列表的老流程可以跑它生成 train.txt、val.txt、trainval.txt。跑这个脚本之前先确认一件事txt 里的路径相对于谁的根目录。脚本内部通常写的是类似JPEGImages/xxx.jpg的短路径你需要保证训练代码的工作目录能根据这个相对路径找到图片。很多人在这里翻车生成完 txt 之后发现路径前面缺了一截训练一启动就报错找不到图片。对策是生成后用文本编辑器打开 train.txt随机抽三行手动去文件系统里验证路径能否对得上。如果你的最终目标是 YOLOv8这个脚本基本不需要碰知道它是干嘛的就行。真正要避免的错误是把复制型脚本和列表型脚本混着用——先复制完数据又跑列表脚本列表里的路径还是老路径配合训练代码一读就乱这属于典型的顺序灾难。4. 从环境搭建到跑通 YOLO 训练以 YOLOv8 为例的完整流程4.1 环境搭建Windows 与 Linux 的差异资源包里有四个 HTML 教程分别覆盖 Windows、Linux 的环境搭建和训练流程。两个系统的本质区别不大核心就三步装 Anaconda、建虚拟环境、装 PyTorch 和 ultralytics 包。Linux 服务器上我一般这样建环境conda create -n yolo python3.9 -y conda activate yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics解释一下这几条命令第一条创建名为 yolo 的虚拟环境指定 Python 3.9避免和系统 Python 环境互相污染第二条激活环境第三条装 GPU 版 PyTorchcu118 表示 CUDA 11.8 对应的版本如果服务器 CUDA 是 12.x把 cu118 换成 cu121第四条装 YOLOv8 的官方包。Windows 上操作一样只是命令行换成 Anaconda Prompt路径反斜杠多一些。没有 NVIDIA 显卡也能跑把 torch 装成 CPU 版模型能训练速度慢几十倍适合先走通流程。装完以后必须验证 GPU 状态python -c import torch; print(torch.__version__); print(torch.cuda.is_available())输出第一行是 PyTorch 版本号第二行输出 True 说明 GPU 可用输出 False 说明装的 CPU 版或者显卡驱动不对。这里卡住最常见的原因是 CUDA 驱动版本太老PyTorch 要求的驱动和系统驱动不匹配。实在装不上 GPU 版就先跑 CPU 版别在环境上耗太久。4.2 整理目录结构并修改 data.yaml用划分脚本跑完之后数据目录应该是这样的结构datasets/traffic_light/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/YOLOv8 训练时会在 images 的同级目录找 labels自动读取同名 txt不需要你在配置文件里写标签路径。对应的 data.yaml 这样写path: D:/datasets/traffic_light # 数据集根目录改成你的实际路径 train: images/train val: images/val test: images/test names: 0: red 1: green 2: yellowpath 是根目录train、val、test 都是相对 path 的路径。这里有个细节必须强调YOLO 的标签 txt 里第一行数字如果是 0那就对应 names 里的第一个类别因此 names 里的顺序不能乱写。很多人在这一步把 red 写在 1 的位置训练完预测时红灯变绿灯就是这个原因。另外 Windows 路径里反斜杠容易出转义问题yaml 里统一用正斜杠或者双反斜杠路径不要带中文这是老生常谈但每次都有人踩。4.3 启动训练epochs、batch、imgsz 怎么定环境就绪、yaml 改好之后训练命令其实很短yolo detect train datatraffic_light.yaml modelyolov8s.pt epochs150 imgsz640 batch16model 参数可以填预训练权重文件路径yolov8s.pt 是轻量级版本红绿灯检测用 s 足够了。追求精度可以换 yolov8m.pt但训练时间和显存占用会上升。epochs 设 150 轮红绿灯任务不算复杂100 到 150 轮基本能收敛再大容易过拟合。imgsz 是输入图片分辨率默认 640如果画面里红绿灯目标很小可以调成 960代价是训练变慢。batch 大小取决于显存6G 显存就设 812G 可以设 16装完环境不确定就先从 4 开始。训练过程中重点看两样东西loss 曲线是否持续下探mAP 是否在验证集上稳步上升。如果 loss 降到某一步不动了不要反复加 epochs先看是不是学习率设太高或者数据有问题。训练结束后模型存在runs/detect/train/weights/best.pt和last.ptbest.pt 是验证集上 mAP 最高的权重last.pt 是最后一轮的权重。后续推理、验证、部署都用 best.pt 就对了。如果习惯写 Python 脚本而不是敲命令等价的方式是这样from ultralytics import YOLO model YOLO(yolov8s.pt) model.train(datatraffic_light.yaml, epochs150, imgsz640, batch16)两种方式完全等价训练参数都写在 train 函数的入参里。中途断了想接着训用model.train(resumeTrue)即可。训练完别急着部署先跑下一章的验证检查不然模型表面指标好看现场一用就露馅。5. 红绿灯数据集训练避坑指南五条血泪经验5.1 标签类别号和 names 顺序不一致mAP 曲线反常但 loss 正常现象训练过程 loss 一路下降看着挺正常但验证集 mAP 一直在低位徘徊或者预测时红灯被识别成绿灯路灯被识别成红灯。原因标签 txt 里的类别 ID 和 data.yaml 中的 names 顺序对不上。常见于两种情况一是你改过 yaml 里的 names 顺序二是划分脚本里硬编码的类别列表顺序和训练配置不一致。解决训练前先把标签里实际出现的类别 ID 统计出来和 names 逐项对照。前面 2.2 节的统计脚本就能干这个活。发现不一致后要么改 yaml 里的 names 顺序要么把标签文件里的 ID 批量替换不要手工人肉改几百个 txt 会改到怀疑人生。我习惯的做法是写一个循环脚本把类别 ID 换掉先打印前三个文件的修改前后对照确认无误再全量跑。5.2 图片带 EXIF 旋转信息导致标注框偏移现象图片在电脑上看是正的LabelImg 里标注也对但训练出来的模型检测框整体偏移框住的位置偏上或偏下尤其集中在某几类图片上。原因手机或部分相机拍出来的 JPG 带 EXIF 方向信息。LabelImg 显示图片时自动应用了方向校正但保存的 xml 坐标是基于原始像素方向算的。YOLO 读取图片时直接用 OpenCV 解码不处理 EXIF两者方向不一致框自然就错了。解决训练前把图片全部用 PIL 转正。代码非常短from PIL import Image, ImageOps import os for img_name in os.listdir(images): path os.path.join(images, img_name) img Image.open(path) img ImageOps.exif_transpose(img) img.save(path)exif_transpose 会根据 EXIF 信息把图片像素真正转正再保存覆盖原图。处理之前先备份原文件夹这类操作不可逆万一转出来色调变了还有后悔药可以吃。红绿灯数据集如果来源是监控抓拍基本不会有这个问题但只要你往里加过手机拍摄的图就必须走这一步。5.3 划分后小类别样本分布失衡验证集虚高现场拉胯现象验证集 mAP 高达 0.95但是拿到真实路口测试黄灯漏检严重甚至一张图里三个灯漏了两个。原因划分脚本按图片维度随机切分没有按类别做分层抽样。如果黄灯样本本身就少又恰好大量落在训练集验证集里黄灯少模型只需要学会红灯绿灯就能拿高分真实场景黄灯一出现就抓瞎。解决先跑 2.2 节的统计脚本看看每类目标框数量。如果某类占比低于 10%不建议纯随机划分改为手动挑选包含该类目标的图片按比例分配到训练集、验证集、测试集。常见做法是写脚本按类别索引图片把包含黄灯的图片单独拎出来先按 7:2:1 分好再把其余图片随机填进三个集合。这类脚本一次性的用 Python 写清楚逻辑跑一遍就行别靠手在文件管理器里拖图片几百个文件拖下来必出错。5.4 显存溢出和 BN 崩溃训练中断的两个高频原因现象训练到某个 epoch 突然报CUDA out of memory进程直接终止或者 loss 毫无征兆变成 nan日志里跟着一串 RuntimeError模型权重炸掉。原因显存溢出是 batch size 或 imgsz 设太大显卡物理显存扛不住。BN 崩溃的原因比较复杂常见的是数据里混入了全黑图片或损坏图片导致 batch 统计量异常也可能是学习率设得过高梯度爆炸冲垮了 BN 层的统计参数。社区里把这个现象叫 BN 崩溃论文里解决方案五花八门工程上最有效的办法是先把脏数据揪出来删掉再降学习率重训。解决显存问题把 batch 降到 4imgsz 降到 416或者开梯度累积一句话就是让显存放得下。BN 崩溃时先跑一个数据完整性检查把尺寸异常、全黑、全白的图片找出来find images -name *.jpg -size -5k小于 5KB 的 jpg 基本都是坏的删掉对应的标签文件再续训。续训用下面的命令直接跑yolo detect train datatraffic_light.yaml modelruns/detect/train/weights/last.pt epochs150 imgsz640 batch16 resumeTrueresumeTrue 表示从 last.pt 接着训而不是从头开始。这是碰到意外中断后最常用的恢复手段比重新跑一遍省几个小时。5.5 用错划分脚本测试集里混进了训练数据现象测试集 mAP 高得离谱接近 0.99换到没见过的摄像头视频上一测性能直接崩盘和测试集结果完全不在一个水平。原因把两份划分和三份划分脚本混着用了。比如先跑了三份划分又跑了两份划分输出目录里残留了旧文件后一次划分没清空之前的数据导致测试集里混入了训练集图片。或者直接把“训练集验证集划分脚本”当成了“训练集验证集测试集划分脚本”压根就没生成真的测试集。解决划分前强制清空输出目录划分后用一段小命令检查三个集合有没有重复文件名ls images/train images/val images/test | sort | uniq -duniq -d 会输出出现重复的图片名只要有任何一行输出说明集合之间有交叉这次划分作废清空重来。这个命令值得养成肌肉记忆我每换一个数据集跑划分必跑一次检查十秒的事能省后面几小时的返工。6. 用 best.pt 做验证与推理先把 mAP 放一边用十张图自测训练完拿到 best.pt先别急着看 mAP 数字那个指标太宏观掩盖了太多细节。我会先跑一轮批量推理用测试集做底观察每张图的检测框位置和类别是不是符合直觉from ultralytics import YOLO import cv2 model YOLO(runs/detect/train/weights/best.pt) results model.predict(test_images, conf0.25, save_txtTrue, save_confTrue) for r in results: boxes r.boxes print(len(boxes), 个目标)conf0.25 是置信度阈值低于这个值的框会被过滤掉。红绿灯检测别一上来就拉高阈值目标本身在画面里尺寸就小置信度天然不会太高拉到 0.5 会漏掉一半目标。先跑一遍看检出情况再根据实际效果决定阈值放在哪。工程上我一般会存一批预测结果图单独建个文件夹逐张翻一遍。mAP 的问题在于它是个全局统计量验证集里红灯多绿灯少时模型只需要把红灯学扎实就能拿高分绿灯的错误被淹没在均值里。YOLOv8 训练完会在runs/detect/train下生成confusion_matrix.png那个图比 mAP 曲线诚实得多一眼就能看出红灯和绿灯之间是不是在互相混淆。如果混淆矩阵对角线很干净再谈部署不迟。我在真实项目里吃过亏当时 mAP 刷到 0.96觉得稳了结果拿去跑一个偏傍晚的摄像头黄灯几乎全漏红灯框还上下抖动。后来养成一个习惯每次训练完从测试集里挑十张有代表性的图包括晴天逆光、夜晚、红灯绿灯同时出现的场景打印出来一张一张对照标注和预测框。框中心有没有落在灯体上、类别标得对不对、有没有一个灯框出来三个重叠框这些细节比任何指标都真实。从那以后我每次换数据集都强制走一遍这个十张图自测再开始调参。希望帮到你。本文还有配套的精品资源点击获取