简介:本资源是面向计算机视觉初学者与YOLO目标检测实践者的红绿灯识别专项数据集及配套开发套件,解决真实交通场景下小目标、多角度、光照变化等典型检测难点。压缩包共2000个文件,含1000张高质量实景红绿灯图像,以及对应VOC(XML)、COCO(JSON)、YOLO(TXT)三种主流格式的完整标注文件;另附3个Python划分脚本(支持按比例生成ImageSets或独立文件夹结构)、6个HTML教程文档(覆盖Windows/Linux双平台环境搭建、训练配置修改、自定义数据集迁移全流程),以及1个训练配置yaml模板。资源包大小487.95MB,结构清晰、开箱即用,显著降低从数据准备到模型训练的入门门槛。目前已有863人学习下载,特别适合课程设计、毕业项目、智能交通方向实训及YOLOv5/v8/v10算法迁移实践。
1. 红绿灯检测为什么总在路口“失明”?这个含1000张实拍图+三格式标签的数据集,能帮你绕开80%的标注和格式转换坑
你调过YOLO红绿灯检测模型吗?——不是demo里那几张干净截图,而是真实十字路口:强光直射下红灯泛白、阴天黄灯发灰、雨雾中绿灯边缘模糊、远处小目标像素不足20×20……很多团队卡在第一步:手头根本没有一张能直接喂进YOLOv8训练器的图片。要么标注工具导出格式不对(LabelImg默认XML但YOLO要txt),要么VOC转YOLO时坐标归一化错位,要么COCO的category_id和YOLO的class_id对不上导致训练时loss爆表。这个数据包不是“又一个红绿灯数据集”,它是把1000张实拍图(含早晚高峰、雨天、逆光、遮挡场景)+已校验的VOC/COCO/YOLO三格式标签+可复用划分脚本+本地可跑通的训练教程,打包成一个“开箱即用”的最小闭环。适合正在做智能交通边缘部署、高校课程设计(如信息技术优质课红绿灯项目)、或需要快速验证算法鲁棒性的工程师——它不解决模型结构创新,但能让你省下至少3天折腾数据格式的时间。
2. 从原始图片到YOLO可训数据:三格式标签生成与一致性校验
2.1 为什么必须同时提供VOC/COCO/YOLO三种格式?
这不是炫技。实际工程中,不同环节依赖不同格式:VOC(Pascal VOC XML)是标注阶段的事实标准(LabelImg/RectLabel默认输出),便于人工复核;COCO(JSON)是学术论文和预训练模型(如Detectron2、MMDetection)的通用输入;YOLO(TXT)才是YOLO系列模型(v5/v8/v10)训练时唯一接受的格式。若只给一种格式,你得自己写转换脚本——而90%的转换bug源于三个细节:① VOC的bbox坐标是[xmin,ymin,xmax,ymax],YOLO要求[center_x,center_y,width,height]并归一化到0~1;② COCO的category_id从1开始,YOLO的class_id从0开始;③ 图片尺寸未统一读取,导致归一化分母用错。本数据集所有标签经Python脚本交叉校验:同一张图的三种格式,用OpenCV加载原图后画bbox,三个框必须完全重叠(像素级误差≤1)。
2.2 VOC→YOLO转换:用这个脚本避免坐标偏移
核心逻辑是先读取原图尺寸,再按比例归一化。常见错误是直接用XML里的width/height(可能被编辑器篡改),或忽略图片实际分辨率。以下脚本已嵌入数据包convert_voc2yolo.py,参数说明见注释:
import xml.etree.ElementTree as ET import os from PIL import Image def convert_voc_to_yolo(xml_path, img_path, output_dir): tree = ET.parse(xml_path) root = tree.getroot() # 关键:从图片文件读取真实尺寸,而非XML中的<width>/<height> img = Image.open(img_path) img_w, img_h = img.size # 真实宽高,不可信XML # 获取所有object objects = root.findall('object') yolo_lines = [] for obj in objects: name = obj.find('name').text # class_id映射:red=0, yellow=1, green=2(固定顺序,非XML中文字) class_map = {'red': 0, 'yellow': 1, 'green': 2} cls_id = class_map.get(name, -1) if cls_id == -1: continue bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) # YOLO格式:中心点+宽高,全部归一化 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h # 限制归一化值在[0,1]内(防XML标注越界) x_center = max(0.0, min(1.0, x_center)) y_center = max(0.0, min(1.0, y_center)) width = max(0.0, min(1.0, width)) height = max(0.0, min(1.0, height)) yolo_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") # 写入YOLO标签文件 txt_name = os.path.splitext(os.path.basename(xml_path))[0] + '.txt' with open(os.path.join(output_dir, txt_name), 'w') as f: f.write('\n'.join(yolo_lines)) # 批量转换示例 voc_ann_dir = 'VOC_annotations/' img_dir = 'images/' yolo_out_dir = 'labels/' os.makedirs(yolo_out_dir, exist_ok=True) for xml_file in os.listdir(voc_ann_dir): if xml_file.endswith('.xml'): xml_path = os.path.join(voc_ann_dir, xml_file) img_name = os.path.splitext(xml_file)[0] + '.jpg' img_path = os.path.join(img_dir, img_name) if os.path.exists(img_path): convert_voc_to_yolo(xml_path, img_path, yolo_out_dir)提示:脚本中
class_map硬编码为{'red':0, 'yellow':1, 'green':2,这是YOLO训练必需的class_id顺序。若你的任务需增加“箭头灯”或“行人灯”,必须在此处扩展并同步修改训练配置文件中的nc(number of classes)。
2.3 COCO→YOLO:跳过JSON解析陷阱的轻量方案
COCO JSON结构复杂(含categories、images、annotations多层嵌套),直接解析易出错。本数据集采用反向生成法:不解析JSON,而是用pycocotools加载后,遍历annotations提取bbox,再按YOLO规则生成txt。关键优势是自动处理iscrowd=1的无效标注、过滤area<10的极小目标(红绿灯最小有效区域设为20×20像素)。脚本convert_coco2yolo.py核心段落:
from pycocotools.coco import COCO import numpy as np def coco_to_yolo(coco_json, img_dir, output_dir): coco = COCO(coco_json) cat_ids = coco.getCatIds() # [1,2,3]对应red,yellow,green # 构建类别映射:COCO category_id → YOLO class_id(从0开始) cat_map = {cat_id: idx for idx, cat_id in enumerate(sorted(cat_ids))} for img_id in coco.getImgIds(): ann_ids = coco.getAnnIds(imgIds=img_id, iscrowd=None) anns = coco.loadAnns(ann_ids) if not anns: continue img_info = coco.loadImgs(img_id)[0] img_name = img_info['file_name'] img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): continue # 读取真实图片尺寸(再次强调!) from PIL import Image pil_img = Image.open(img_path) img_w, img_h = pil_img.size yolo_lines = [] for ann in anns: # 过滤极小目标(红绿灯在640p图像中宽度<15像素视为噪声) if ann['area'] < 225: # 15*15 continue # COCO bbox格式:[x,y,width,height],需转为[xmin,ymin,xmax,ymax] x, y, w, h = ann['bbox'] xmin, ymin, xmax, ymax = x, y, x+w, y+h # 归一化 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = w / img_w height = h / img_h # 映射class_id cls_id = cat_map.get(ann['category_id'], -1) if cls_id == -1: continue yolo_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") # 写入txt txt_name = os.path.splitext(img_name)[0] + '.txt' with open(os.path.join(output_dir, txt_name), 'w') as f: f.write('\n'.join(yolo_lines))注意:
pycocotools安装需用pip install pycocotools-windows(Windows)或pip install pycocotools(Linux/macOS),若报错Microsoft Visual C++ 14.0 is required,先装 Build Tools for Visual Studio 。
3. 划分训练/验证/测试集:为什么随机切分在红绿灯场景会翻车?
3.1 时间序列相关性带来的分布偏移
红绿灯数据天然具有时间相关性:同一路口连续拍摄的图片,光照、天气、车流状态高度相似。若简单用sklearn.model_selection.train_test_split随机打乱,会导致训练集全是晴天图、验证集全是雨天图——模型在验证集上mAP暴跌20%以上。本数据包的split_dataset.py采用按场景聚类划分:先用OpenCV计算每张图的HSV通道均值(H色调反映光照色温,S饱和度反映天气通透度,V明度反映亮度),K-means聚为5类,再按类内7:2:1比例分配。代码关键逻辑:
import cv2 import numpy as np from sklearn.cluster import KMeans def cluster_by_hsv(image_paths, n_clusters=5): hsv_features = [] for img_path in image_paths: img = cv2.imread(img_path) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 提取全局HSV统计特征:均值+标准差(共6维) h_mean, s_mean, v_mean = np.mean(hsv, axis=(0,1)) h_std, s_std, v_std = np.std(hsv, axis=(0,1)) hsv_features.append([h_mean, s_mean, v_mean, h_std, s_std, v_std]) kmeans = KMeans(n_clusters=n_clusters, random_state=42) labels = kmeans.fit_predict(hsv_features) return labels # 使用示例 image_list = [os.path.join('images', f) for f in os.listdir('images') if f.endswith('.jpg')] clusters = cluster_by_hsv(image_list) # 按聚类标签分组,再每组内划分 train_files, val_files, test_files = [], [], [] for cluster_id in range(5): cluster_imgs = [img for img, label in zip(image_list, clusters) if label == cluster_id] n = len(cluster_imgs) train_end = int(0.7 * n) val_end = int(0.9 * n) train_files.extend(cluster_imgs[:train_end]) val_files.extend(cluster_imgs[train_end:val_end]) test_files.extend(cluster_imgs[val_end:])3.2 验证集必须包含“困难样本”的硬性规则
红绿灯检测的难点不在识别颜色,而在小目标+遮挡+低对比度。本数据集验证集强制包含三类样本:① 分辨率≤320×240的远距离图(占验证集30%);② 标注框面积<500像素的极小灯(占20%);③ 含车辆/广告牌遮挡的图(占20%)。脚本split_dataset.py中通过以下条件筛选:
def is_hard_sample(img_path, xml_path): # 读取XML获取bbox面积 tree = ET.parse(xml_path) root = tree.getroot() bboxes = [] for obj in root.findall('object'): bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) area = (xmax - xmin) * (ymax - ymin) bboxes.append(area) # 小目标:最大bbox面积<500 if bboxes and max(bboxes) < 500: return True # 低分辨率:图片宽或高<320 img = Image.open(img_path) if img.width < 320 or img.height < 240: return True # 遮挡:object数量≥3且存在重叠(简化判断:bbox中心距<50像素) centers = [] for bbox in bboxes: # 此处省略中心点计算,实际代码中实现 pass return False血泪经验:某次项目验证集没加遮挡样本,上线后发现公交车遮挡红灯时漏检率高达47%。从此立下规矩:验证集必须人工筛出10张最难的图,单独放入
val_hard/目录,训练后必须在这10张上跑infer看结果。
4. YOLOv8训练全流程:从环境配置到收敛监控的避坑指南
4.1 Anaconda环境配置:为什么不用pip install ultralytics?
Ultralytics官方推荐pip install ultralytics,但在Windows服务器上常因PyTorch CUDA版本冲突失败。本教程采用Conda精确控制,确保PyTorch、CUDA、Ultralytics三方兼容。命令如下(适配CUDA 11.8):
# 创建新环境(避免污染主环境) conda create -n yolov8 python=3.9 conda activate yolov8 # 安装PyTorch(指定CUDA版本) conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia # 安装Ultralytics(用conda-forge源更稳定) conda install -c conda-forge ultralytics # 验证安装 python -c "from ultralytics import YOLO; print('OK')"提示:若
conda install ultralytics报错PackageNotFoundError,改用pip install ultralytics --no-deps跳过依赖,再手动pip install opencv-python matplotlib PyYAML。
4.2 数据集配置文件:dataset.yaml的四个致命参数
YOLOv8要求dataset.yaml定义路径和类别,但新手常忽略这四点:
| 参数 | 正确值 | 错误示例 | 后果 |
|---|---|---|---|
train | ../images/train/(相对路径从yaml所在目录算起) | ./images/train/ | 训练时报错No images found |
val | ../images/val/ | val/ | 验证时找不到图片 |
nc | 3(红黄绿三类) | 1 | 训练时loss不下降,类别混淆 |
names | ['red', 'yellow', 'green'] | ['traffic_light'] | 推理输出全为同一类 |
完整dataset.yaml示例:
train: ../images/train/ val: ../images/val/ test: ../images/test/ nc: 3 names: ['red', 'yellow', 'green']4.3 训练命令与关键参数调优
基础命令:
yolo train data=dataset.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16但红绿灯场景需调整:
imgsz=640→ 改为imgsz=1280:小目标检测必须增大输入尺寸,否则红绿灯在640p中仅占20像素,特征提取失效;batch=16→ 改为batch=8:增大imgsz后显存占用翻倍,8卡A100可跑16,单卡3090建议8;- 加
--optimizer adamw:YOLOv8默认SGD,但红绿灯颜色区分需更精细梯度更新,AdamW收敛更快; - 加
--lr0 0.001:学习率从默认0.01降至0.001,防止强光下红灯过曝导致梯度爆炸。
最终命令:
yolo train data=dataset.yaml model=yolov8n.pt epochs=100 imgsz=1280 batch=8 optimizer=adamw lr0=0.0014.4 避坑:红绿灯训练中最常见的5个翻车现场
现象1:训练loss震荡剧烈,mAP在0.1徘徊
原因:dataset.yaml中nc设为1,但标签文件里class_id有0/1/2。YOLOv8将所有类别强行映射到class_id=0,导致分类头失效。
解决:检查labels/train/下任意txt文件,确认首列数字只有0/1/2;核对dataset.yaml的nc:3和names顺序。
现象2:验证时检测框全为绿色,红色灯被识别为黄色
原因:训练前未做白平衡校正,强光下红灯RGB值接近(255,200,200),被网络误判为黄色。
解决:在dataset.yaml中加--augment启用HSV增强,或预处理时用OpenCV做简单白平衡:
def white_balance(img): r, g, b = cv2.split(img) r_avg = cv2.mean(r)[0] g_avg = cv2.mean(g)[0] b_avg = cv2.mean(b)[0] avg = (r_avg + g_avg + b_avg) / 3 r = cv2.addWeighted(r, avg/r_avg, 0, 0, 0) g = cv2.addWeighted(g, avg/g_avg, 0, 0, 0) b = cv2.addWeighted(b, avg/b_avg, 0, 0, 0) return cv2.merge([r, g, b])现象3:训练100轮后,验证集mAP@0.5升至0.85,但测试集mAP仅0.42
原因:验证集和测试集来自同一时间段(如都是上午拍摄),而测试集实际需覆盖全天。
解决:重新划分数据集,确保测试集包含早/中/晚/夜四时段样本,并在split_dataset.py中加入时间戳过滤。
现象4:推理时GPU显存暴涨至98%,推理速度<1fps
原因:imgsz=1280时YOLOv8n仍需2.1GB显存,但OpenCV默认使用BGR格式(3通道),而YOLO输入需RGB。cv2.imread()读取后未转RGB,导致Ultralytics内部重复转换。
解决:推理代码中显式转RGB:
img = cv2.imread('test.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 关键! results = model(img)现象5:导出ONNX后,TensorRT推理结果bbox坐标全为0
原因:YOLOv8导出ONNX时未指定--dynamic,导致输入尺寸固定,而TensorRT引擎加载时输入shape不匹配。
解决:导出命令加--dynamic:
yolo export model=yolov8n.pt format=onnx dynamic=True5. 部署前必做的三件事:让红绿灯检测在真实路口不掉链子
5.1 光照鲁棒性测试:用Gamma校正模拟极端场景
实验室训练的模型,在真实路口常因光照变化失效。不要等上线后才发现问题——在部署前,用Gamma校正批量生成极端光照样本,测试模型稳定性。脚本test_gamma_robustness.py:
import cv2 import numpy as np def apply_gamma(img, gamma=1.0): inv_gamma = 1.0 / gamma table = np.array([((i / 255.0) ** inv_gamma) * 255 for i in np.arange(0, 256)]).astype("uint8") return cv2.LUT(img, table) # 测试gamma=0.5(暗场景)、gamma=1.5(过曝场景)、gamma=2.0(严重过曝) gammas = [0.5, 1.5, 2.0] model = YOLO('runs/train/exp/weights/best.pt') for gamma in gammas: img = cv2.imread('test_scene.jpg') img_gamma = apply_gamma(img, gamma) results = model(img_gamma) # 保存带bbox的图 annotated = results[0].plot() cv2.imwrite(f'test_gamma_{gamma}.jpg', annotated)玄学技巧:若gamma=0.5时漏检增多,说明模型对暗部特征提取不足,在训练时加
--hsv_h 0.015 --hsv_s 0.7 --hsv_v 0.4增强HSV扰动。
5.2 小目标召回率专项优化:添加FPN+PAN结构
YOLOv8n默认neck为PANet,但对红绿灯这类小目标,需强化高层语义与底层细节融合。在models/yolov8.yaml中修改neck部分:
# 原始PANet neck: - [-1, 1, Conv, [512, 1, 1]] - [[-1, 6], 1, Concat, [1]] # 改为BiFPN(加权双向特征金字塔) neck: - [-1, 1, BiFPN, [256, 128, 64]] # 新增BiFPN模块,参数为各层通道数BiFPN实现参考EfficientDet,核心是跨尺度特征加权融合。本数据包已提供bifpn.py,只需复制到ultralytics/nn/modules/并修改yaml即可。
5.3 边缘设备推理:量化后精度损失补偿策略
在Jetson Orin上部署INT8量化模型,mAP通常下降8~12个百分点。补偿方法不是调高置信度阈值(会增假阳性),而是后处理补偿:对量化后输出的bbox,用原始FP16模型对Top-5高置信度框做二次精修。伪代码:
# 量化模型推理 q_results = q_model(img) top5_boxes = sorted(q_results.boxes, key=lambda x: x.conf, reverse=True)[:5] # 对top5框裁剪ROI,用FP16模型精修 fp16_boxes = [] for box in top5_boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) roi = img[y1:y2, x1:x2] fp16_result = fp16_model(roi) # 将fp16结果坐标映射回原图 for fp_box in fp16_result.boxes: fx1, fy1, fx2, fy2 = fp_box.xyxy[0] fx1 += x1 fy1 += y1 fx2 += x1 fy2 += y1 fp16_boxes.append([fx1,fy1,fx2,fy2]) # 合并结果:量化结果+精修结果 final_boxes = q_results.boxes + fp16_boxes后悔药:我曾在一个路口项目中跳过这步,量化后漏检率达31%。补上精修后,漏检率压到4.2%,且推理速度仍保持23fps(Orin)。现在我的习惯是:任何边缘部署,必做量化+精修双路验证。希望帮到你。
本文还有配套的精品资源,点击获取