
简介本资源为面向目标检测学习者的YOLO红绿灯检测数据集适用于智能交通、自动驾驶感知等场景下的模型训练与课程实践可帮助读者快速获得高质量标注数据省去自行采集与标注的成本。压缩包共约2000个文件以1986个xml标注文件为主另含少量html说明文档、txt列表与py脚本整体约946.4MB涵盖voc、coco和yolo三种格式标签分别存放于不同文件夹可直接接入YOLO系列训练流程。资源同时附赠环境搭建与训练案例教程覆盖Windows与Linux版本并提供训练集、验证集、测试集划分脚本可按需生成ImageSets下的txt划分文件。目前已有757人学习下载适合希望快速复现红绿灯检测实验、验证模型效果或用于教学演示的读者参考使用。1. 红绿灯检测数据集到底解决什么问题5000 张图背后的真实门槛做交通视觉项目的人十有八九卡在同一个地方模型结构抄得飞快数据却凑不齐。红绿灯检测尤其如此——它不是「找红色圆形」这么简单白天逆光时红灯发白、夜里绿灯过曝成白团、雨雾天整个灯面糊成一团再加上远处灯只有十几个像素标注稍不留神就漏。你手上如果只有几百张网图训出来的模型换个路口就翻车这就是很多人做 YOLO 红绿灯目标检测时最真实的痛点。这个标题里的资源核心价值不在「5000 张」这个数字而在于它把三件事一次性打包了图片、VOC/COCO/YOLO 三种格式标签、划分脚本和训练教程。对一线落地来说这意味着你不用再花两三天写格式转换、手动切训练验证集拿到就能直接喂给 YOLO 系列模型跑 baseline。它适合三类人刚入门 YOLO 目标检测、想拿一个完整数据集练手的新手做智慧交通、辅助驾驶感知、路口信号识别需要快速验证方案的工程师以及想对比 VOC、COCO、YOLO 三种标注格式差异、搞清楚转换链路的学习者。下面我按「数据集长什么样 → 三种格式怎么用 → 划分脚本怎么写 → 训练怎么跑 → 坑在哪」的顺序把这条链路讲透。2. 先看清数据本身5000 张红绿灯图的类别、分布与标注质量拿到一个目标检测数据集别急着train.py先花半小时把数据摸清楚。红绿灯数据集最容易出问题的地方是类别定义和样本分布这两点直接决定你后面训练是顺利收敛还是反复调参。2.1 红绿灯检测的类别怎么定3 类还是 12 类红绿灯标注的类别设计业界常见有两种思路。第一种是按颜色分red、green、yellow三类简单直接适合只判断「当前能不能走」的场景。第二种是按颜色 方向/形状分red、green、yellow、red_left、green_left、red_countdown等能表达箭头灯、倒计时灯适合做精细化路口控制。我一般建议新手先用 3 类跑通因为类别越多每类样本越稀疏5000 张图摊到 12 类上某些方向灯可能只有一两百个实例模型学不动。判断数据集类别定义是否合理直接看标签文件里的类别名或类别 id 分布# 统计 YOLO 格式标签里每个类别的实例数 # 假设 labels/ 下是 .txt每行格式class_id x_center y_center w h awk {print $1} labels/*.txt | sort | uniq -c | sort -rn这段命令把每个class_id出现的次数统计出来输出第一列是实例数第二列是类别 id。如果某个 id 只有几十个实例说明这一类要么合并要么补充数据。逻辑很简单YOLO 的损失函数对每个正样本都算定位和分类损失样本太少的类会被大量背景和多数类淹没表现为「模型几乎不预测这个类」。参数上要注意YOLO 标签里的坐标是归一化的0~1 之间x_center y_center是框中心相对整图宽高的比例w h是框宽高比例。如果你看到标签里出现大于 1 的值说明这份标签没归一化直接训练会报错或框全乱。2.2 用脚本快速体检分辨率、长宽比、框大小分布红绿灯在画面里通常很小框的尺寸分布决定了你该用多大的输入分辨率。跑一段统计脚本把每张图的宽高和每个框的像素面积算出来import os from PIL import Image img_dir images sizes, box_areas [], [] for name in os.listdir(img_dir): if not name.lower().endswith((.jpg, .png, .jpeg)): continue img Image.open(os.path.join(img_dir, name)) w, h img.size sizes.append((w, h)) # 对应标签文件 label os.path.join(labels, os.path.splitext(name)[0] .txt) if os.path.exists(label): for line in open(label): parts line.split() if len(parts) 5: _, _, _, bw, bh map(float, parts) box_areas.append(bw * w * bh * h) # 还原成像素面积 print(图片数量:, len(sizes)) print(平均宽高:, sum(s[0] for s in sizes)//len(sizes), sum(s[1] for s in sizes)//len(sizes)) if box_areas: box_areas.sort() print(框像素面积 中位数:, box_areas[len(box_areas)//2]) print(框像素面积 最小:, box_areas[0], 最大:, box_areas[-1])这段脚本做两件事统计图片原始分辨率以及把归一化框还原成像素面积。为什么要还原因为 YOLO 训练时会把图缩放到固定输入尺寸比如 640小框缩放后可能只剩几个像素定位精度直接崩。如果中位框面积在 640 输入下小于 32×32 像素你就得考虑提高输入分辨率到 960 或 1280或者用带 P2 小目标检测层的 YOLO 变体。提示红绿灯数据集里如果混入了大量远景小灯和少量近景大灯框面积分布会非常分散这时候不要盲目调 anchor先确认标注框有没有把灯和灯杆一起框进去——框太大会让模型学到灯杆特征换路口就失效。2.3 标注质量抽查三类典型脏标注5000 张图不可能张张完美抽查 50 张就能发现规律性问题。红绿灯数据集最常见的脏标注有三种一是漏标远处小灯没框二是框偏移框住了灯但偏了半个灯身三是类别错标把黄灯标成红灯。漏标会让模型学到「这个位置没有目标」是最伤的一种。抽查方法很土但有效把图片和标签可视化叠在一起看。用 OpenCV 画框import cv2 def draw_yolo(img_path, label_path, class_names): img cv2.imread(img_path) h, w img.shape[:2] for line in open(label_path): cid, x, y, bw, bh line.split() cid int(cid); x, y, bw, bh map(float, (x, y, bw, bh)) x1 int((x - bw/2) * w); y1 int((y - bh/2) * h) x2 int((x bw/2) * w); y2 int((y bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cid], (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(check_ img_path.split(/)[-1], img)class_names按你的类别顺序传入比如[red, green, yellow]。跑完随机抽几十张看重点看远景小灯有没有框、框有没有贴边。如果发现某类系统性漏标别硬训先补标或把这类从评估里单独拎出来看。3. VOC、COCO、YOLO 三种格式区别、转换与各自适用场景同一个数据集给三种格式不是凑数是因为不同训练框架吃不同格式。搞清楚三者的结构差异你才能在换框架时不抓瞎。3.1 三种格式的结构对比格式标注文件坐标表示典型用途VOC每图一个 XML左上右下绝对像素xmin,ymin,xmax,ymax老牌框架、部分检测库、评估脚本COCO单个 JSON[x,y,w,h]绝对像素从 0 开始COCO 预训练权重微调、pycocotools 评估YOLO每图一个 txt归一化x_center,y_center,w,hYOLOv5/v8/v11 等直接训练关键差异在坐标VOC 和 COCO 用绝对像素YOLO 用归一化比例。所以转换的核心就是「除以宽高」或「乘以宽高」。另一个差异是 COCO 把所有图的信息塞进一个 JSON包含images、annotations、categories三个主键转换时要注意image_id和category_id的对应关系这是最容易出错的地方。3.2 VOC 转 YOLO一个脚本搞定如果你手上是 VOC 的 XML转 YOLO 的脚本逻辑是读 XML 拿到宽高和每个bndbox把绝对坐标转成归一化中心点格式。import os import xml.etree.ElementTree as ET classes [red, green, yellow] # 按你的类别顺序 xml_dir, out_dir voc_annotations, labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue cid classes.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) # 转归一化中心点 xc (xmin xmax) / 2 / w yc (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cid} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(lines))逻辑说明classes列表的顺序必须和训练时data.yaml里的names完全一致否则类别会错位。坐标保留 6 位小数是 YOLO 官方推荐的精度够用且不会让文件过大。if name not in classes这行是防御性的——VOC 里可能有ignore或其他杂类直接跳过避免污染。参数上唯一要改的就是classes和两个目录路径。如果你的 XML 里坐标有负数或超出图片范围标注工具手抖建议加一行 clampxmin max(0, xmin)否则归一化后会出现负值YOLO 训练时可能报 warning。3.3 COCO 转 YOLO注意 image_id 和类别映射COCO 的 JSON 结构更绕转换时要先建立image_id → 文件名和category_id → 连续索引两张映射表。import json, os with open(annotations/instances.json) as f: coco json.load(f) # image_id - file_name img_map {img[id]: img[file_name] for img in coco[images]} # category_id - 连续索引YOLO 要求从 0 开始连续 cat_ids sorted(c[id] for c in coco[categories]) cat_map {cid: i for i, cid in enumerate(cat_ids)} os.makedirs(labels, exist_okTrue) # 先按图分组 from collections import defaultdict per_img defaultdict(list) for ann in coco[annotations]: per_img[ann[image_id]].append(ann) for img_id, anns in per_img.items(): fname os.path.splitext(img_map[img_id])[0] .txt # 需要图片宽高从 images 里取 info next(i for i in coco[images] if i[id] img_id) w, h info[width], info[height] lines [] for ann in anns: cid cat_map[ann[category_id]] x, y, bw, bh ann[bbox] # COCO 是 [x,y,w,h] 绝对像素 xc (x bw/2) / w yc (y bh/2) / h lines.append(f{cid} {xc:.6f} {yc:.6f} {bw/w:.6f} {bh/h:.6f}) with open(os.path.join(labels, fname), w) as f: f.write(\n.join(lines))这里有两个坑点。第一COCO 的bbox是[x, y, width, height]x,y是左上角不是中心点所以要先加半个宽高再归一化。第二COCO 的category_id往往不连续比如 1、3、7YOLO 要求类别索引从 0 连续所以必须用cat_map重映射。如果你直接把category_id当类别索引写进去训练时会出现「类别 7 超出 nc 范围」的报错。3.4 该用哪种格式按框架选不按喜好选结论很直接训 YOLO 就用 YOLO 格式别绕弯。COCO 格式的价值在于能直接对接 COCO 预训练权重的评估流程以及用 pycocotools 算 mAP 时更标准。VOC 格式现在主要用于兼容一些老评估脚本。如果你只是想把模型训出来部署YOLO 格式 data.yaml是最短路径。三种格式都留着的好处是换框架、换评估工具时不用重新标注这也是这个数据集打包三种格式的实际意义。4. 划分脚本与训练教程从 5000 张图到能用的模型数据摸清了、格式也懂了接下来是把它切成训练集/验证集然后跑起来。这两步的细节决定了你的 mAP 是虚高还是真实。4.1 划分脚本怎么写别用随机划分糊弄很多人划分数据集就一句random.shuffle这在红绿灯场景下会出问题。因为同一路口的连续帧高度相似随机划分会让训练集和验证集里出现几乎一样的图验证 mAP 虚高实际部署一塌糊涂。正确做法是按场景/路口分组划分同一路口的图要么全在训练集要么全在验证集。如果数据集没提供路口信息退而求其次按文件名前缀或时间戳分组。一个可用的划分脚本import os, random, shutil random.seed(42) # 固定种子保证可复现 img_dir images val_ratio 0.2 imgs [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .png))] # 按前缀分组假设文件名前缀代表场景如 cross01_001.jpg groups {} for f in imgs: key f.split(_)[0] if _ in f else f[:6] groups.setdefault(key, []).append(f) keys list(groups.keys()) random.shuffle(keys) val_count int(len(keys) * val_ratio) val_keys set(keys[:val_count]) for split in [train, val]: os.makedirs(fdataset/images/{split}, exist_okTrue) os.makedirs(fdataset/labels/{split}, exist_okTrue) for f in imgs: key f.split(_)[0] if _ in f else f[:6] split val if key in val_keys else train shutil.copy(os.path.join(img_dir, f), fdataset/images/{split}/{f}) lbl os.path.splitext(f)[0] .txt src os.path.join(labels, lbl) if os.path.exists(src): shutil.copy(src, fdataset/labels/{split}/{lbl})逻辑说明random.seed(42)保证每次划分结果一致方便复现实验。按前缀分组是关键——groups把同场景图聚在一起val_keys从组级别抽取避免同场景泄漏。val_ratio0.2是常见起点数据量小可以调到 0.15数据量大可以到 0.3。注意如果你的文件名没有可分组的前缀至少用图片的感知哈希pHash做一次去重把相似度极高的图归为一组否则验证集就是自欺欺人。4.2 data.yaml 与训练命令参数怎么设YOLO 训练靠一个data.yaml描述数据位置和类别path: ./dataset train: images/train val: images/val nc: 3 names: [red, green, yellow]path是根目录train/val是相对路径。nc必须等于names长度写错会直接报错。然后跑训练yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/traffic_light参数逐个说modelyolov8n.pt是 nano 版预训练权重红绿灯这种类别少、目标明确的场景n 或 s 版通常够用先跑通再换大模型。imgsz640是输入分辨率前面统计过如果小目标多这里提到 960。batch16看显存8G 显存跑 640 一般能到 16。lr00.01是初始学习率用预训练权重时这个值合适从零训可以降到 0.001。patience20是早停20 轮验证指标不升就停省时间。4.3 训练过程看什么loss 曲线和 mAP 的读法训练日志里重点看三个量box_loss、cls_loss、mAP50。box_loss下降说明定位在收敛cls_loss下降说明分类在收敛。如果box_loss一直震荡不降多半是学习率太大或标注框质量差。mAP50是 IoU0.5 时的平均精度红绿灯这种规则目标正常能到 0.85 以上如果只有 0.5 左右回去查标注。一个常见现象是训练集 loss 一直降、验证集 mAP 早早到顶然后掉这是过拟合。对策是加数据增强YOLO 默认开了 mosaic、HSV 抖动、减小模型、或者提前停。红绿灯场景特别建议开hsv_h、hsv_s、hsv_v的抖动因为不同摄像头白平衡差异大颜色抖动能让模型对色偏更鲁棒。5. 避坑与排查红绿灯数据集训练最容易翻车的 5 个地方这一章是我自己踩过的坑按「现象 → 原因 → 解决」写你对照着排查能省不少时间。坑一训练一开始就报「Label class X is out of bounds」。现象是启动训练几秒后直接崩报类别越界。原因是标签里的class_id超过了nc-1通常是 VOC/COCO 转换时类别映射没做连续化或者data.yaml的nc写小了。解决用第 2 章的awk命令统计最大class_id确保nc比它大 1并检查转换脚本里的cat_map是否从 0 连续。坑二mAP 很高但实际部署漏检严重。现象是验证集 mAP 0.9装到路口摄像头上一半红灯没框出来。原因是验证集和训练集同源没覆盖真实部署的光照、角度、分辨率。解决划分时按场景分组第 4 章脚本并额外留一批完全不同路口的图做测试集别只看验证集数字。坑三小目标红绿灯几乎检测不到。现象是近处灯框得准远处小灯全漏。原因是 640 输入下小灯缩放到不足 16 像素特征在 backbone 下采样时就丢了。解决把imgsz提到 960 或 1280或者换带 P2 层的模型配置同时确认标注时小灯确实被框了而不是漏标。坑四模型把黄灯和红灯混为一谈。现象是混淆矩阵里 red 和 yellow 互相误判严重。原因是黄灯样本少且黄灯在过曝时颜色接近白/红。解决统计黄灯实例数如果明显偏少就补充数据或做类别加权训练时加强 HSV 抖动让模型不只依赖颜色还依赖灯的位置和形状。坑五换了个摄像头颜色全偏模型失效。现象是同一批模型A 摄像头正常B 摄像头红灯识别成绿灯。原因是模型过拟合了训练集的色调。解决训练时开hsv_h0.015, hsv_s0.7, hsv_v0.4这类较强的颜色增强并在推理前对输入做白平衡归一化。这是红绿灯检测里最容易被忽视、又最致命的一类问题。6. 进阶技巧用验证集反推标注问题把 mAP 从 0.85 推到 0.93训练跑通只是起点真正拉开差距的是「用模型反过来查数据」。我一般会在第一轮训练后把验证集里所有漏检和误检的图导出来逐张看往往能发现一批系统性标注问题。具体做法用训练好的模型在验证集上推理把预测结果和真值对比筛出「有真值但没预测」的图yolo detect predict \ modelruns/traffic_light/weights/best.pt \ sourcedataset/images/val \ conf0.25 \ save_txtTrue \ projectruns/val_checkconf0.25是置信度阈值调低能暴露更多漏检。save_txtTrue会把预测框存成 YOLO 格式方便和真值标签做 diff。跑完后对比runs/val_check/labels和dataset/labels/val找出真值有、预测没有的图重点看这些图里的小灯和逆光灯。我自己的习惯是第一轮训练后必做一次这个反查通常能揪出 5%~10% 的漏标或错标。修完标注再训第二轮mAP 往往能从 0.85 提到 0.9 以上。红绿灯检测的瓶颈从来不在模型结构而在数据质量——5000 张图如果标注干净、场景覆盖全nano 版模型就能跑出能用的效果标注脏换多大的模型都白搭。还有一个实用技巧是按距离分桶评估。把验证集里的框按像素面积分成小32²、中32²~96²、大96²三桶分别算 mAP。如果小目标 mAP 明显低就针对性提高输入分辨率或补小目标数据而不是盲目调模型。这个分桶评估用 pycocotools 或自己写脚本都能做比只看一个总 mAP 有用得多。最后说个习惯每次改完数据或参数把data.yaml、训练命令、随机种子一起记下来。红绿灯项目调参的玄学时刻不少没有记录两周后你自己都复现不出当时那个 0.93 的结果。希望帮到你。本文还有配套的精品资源点击获取