☰
YOLO红绿灯目标检测数据集:VOC/COCO/YOLO三标签格式转换与训练全流程
2026/10/11 5:47:09 网站建设 项目流程

简介:面向YOLO目标检测学习者和智能交通项目开发者,这份红绿灯数据集收录真实场景中的车辆信号灯图片,涵盖白天、夜晚、多路口等不同环境,适合用于模型训练、算法验证及课程设计。包内提供VOC(xml)与YOLO(txt)两种格式标注,分别存放于独立文件夹,标注框由LabelImg制作、质量较高,可直接接入YOLO系列训练流程。整个压缩包共2000个文件,其中1986个为xml标注文件,另有6个html图文教程、5个txt配置说明和3个Python脚本,总大小约946.4MB。附赠的教程覆盖Linux与Windows下的YOLO环境搭建、基于案例修改并训练自定义数据集的方法;同时提供训练集、验证集、测试集划分脚本,可按需灵活分配数据,方便扩展与复现实验。已有757人学习下载,适合需要完整红绿灯检测方案、希望快速上手YOLO数据准备与训练的入门及进阶用户。

1. YOLO红绿灯目标检测数据集:先别急着解压开跑,看清三套标签怎么选

第一次拿到“YOLO红绿灯目标检测数据集(含5000张图片)+对应voc、coco和yolo三种格式标签+划分脚本+训练教程.rar”,很多人第一反应是解压、扔进训练脚本里开跑,然后卡在路径不对、类别对不上、训练验证集重叠这些低级问题上。这个包真正的价值不只是5000张图和标注,而是同一批红绿灯图片被整理成了VOC、COCO、YOLO三种标签格式,外加划分脚本和训练教程,等于把“从数据到模型”的完整链路一次性补齐。这篇文章不聊虚的,就顺着这条链路讲清楚三套标签差在哪、怎么转换、训练前怎么排雷、参数怎么设。适合刚入门YOLO目标检测、想用现成数据跑通一遍的从业者,也适合自动驾驶和交通监控方向拿来做算法验证的人。

2. 三种标签格式怎么选:VOC、COCO、YOLO是同一批框的三种写法

2.1 VOC格式:一张图一个XML,坐标和尺寸都直白,适合当母版

PASCAL VOC是目标检测领域最老牌的标注格式,常见做法是一张图片配一个同名XML文件,用树形结构记录文件名、图像尺寸、物体类别和bounding box坐标。这种格式能在早期成为通用语言,是因为它足够直观:记事本打开就能看懂,不需要解析库,标注工具也大多原生支持导出,非常适合作为后续转换的“母版”。

给一个最小示例:

<annotation> <filename>1974_02_17_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>red_light</name> <bndbox> <xmin>623</xmin> <ymin>245</ymin> <xmax>651</xmax> <ymax>275</ymax> </bndbox> </object> </annotation>

逻辑说明:<filename>给图片名,<size>告诉解析器图像宽高,bndbox里四个值是绝对像素坐标,左上角为原点。XML格式的缺点是每张图一个文件,数据集大到几万张时文件数量惊人,很多分布式训练框架读起来也啰嗦,但这恰恰让它适合当母版。我一般会把XML原始标注长期保留,之后无论转COCO还是YOLO,都从它派生,而不是反过来。

2.2 COCO格式:一个大JSON管整批数据,类别号和bbox都有规矩

COCO格式是微软为Common Objects in Context数据集定义的JSON结构,和VOC最大的区别是把一整批数据收敛到一个JSON文件里。结构分成images、annotations、categories三大块,图片通过id关联,标注通过image_id和category_id关联,整个文件相当于一个小型关系数据库。很多开源工具箱比如MMDetection都默认吃COCO,这也是红绿灯数据包要附COCO标签的原因。

关键字段如下表:

字段含义值得注意的点
images.id图片唯一编号其他对象都靠它关联
images.file_name图像文件名相对json所在目录
annotations.bbox[x, y, width, height]绝对像素,左上角加宽高
annotations.category_id类别编号从1开始,不是0
categories.name类别名顺序要和训练脚本一致

两个容易踩坑的细节:COCO的bbox不是角点坐标而是“左上角+宽高”,转YOLO时不能直接套用VOC的角点逻辑;category_id从1开始计数,而YOLO框架的class_id从0开始,转换时必须做减一操作,否则类别整体错位。另外,COCO允许area、iscrowd这些附加字段,如果只跑普通检测,忽略它们问题不大,但要拿COCO预训练权重做迁移学习,字段语义尽量保持一致。

2.3 YOLO格式:txt每行一个目标,归一化坐标直接喂给训练器

YOLO标签是每张图配一个同名txt文件,每一行代表一个目标对象,五个数字分别是:类别序号、归一化中心点x、归一化中心点y、归一化宽度w、归一化高度h。举例:

0 0.324479 0.252314 0.008854 0.018519 1 0.585937 0.367593 0.009896 0.016204

逻辑说明:第一个数字0是red_light的类别序号,0.324479是框中心离左边界的距离占图片宽度的比例,0.252314是中心点离上边界的距离占高度的比例,后两个数是框的宽、高分别占图片宽、高的比例。这样设计有个核心好处:标签不依赖图片实际分辨率,无论原图是1920×1080还是被缩放到640×640,标签都能直接复用。

这里有一个经常翻车的约束:txt里五个数字都应该在0~1之间,但原始标注只要有一个坐标超出图片边界,或者宽高算成负数,转出来的归一化值就会大于1或者小于0。转换脚本里必须加边界保护,同时把越界框单独打印出来人工复核,第3章的脚本就是这么处理的。

2.4 选型建议:什么工具箱对应什么格式,以及怎么验证三套标签对齐

拿同一个数据包之前,先明确自己用什么训练框架,格式选择一下就清楚了。Ultralytics YOLO系列和大部分YOLO生态项目都直接吃YOLO格式的txt;MMDetection这类工具箱同时支持COCO和VOC;如果目标是做跨数据集测试或加载COCO预训练权重,COCO更通用。想省事就用YOLO格式,想灵活就保留VOC母版并在需要时生成COCO。

打开压缩包时还有一个动作别省:确认三套标签来自同一批图片。我见过有数据包为了显得“全”,把不同来源的图各自用不同格式包装,标签内容对不上。验证方法很简单,随机挑三张图,把VOC的bndbox画上去,再把YOLO标签换算回像素坐标画上去,两个框如果不在同一个灯体位置,说明这套标签有问题。这个操作几十秒就够,能提前避开后续所有“丢失标签”“错位标签”的坑。

3. 把VOC转成YOLO格式:转换脚本、划分脚本与4个必调细节

3.1 为什么自己写转换脚本:在线转换工具处理不了边界越界

网上有不少VOC转YOLO的在线工具,粘贴XML、下载txt,看起来很方便,但实际用起来有三个痛点:一是批量转换时一张一张操作效率太低;二是工具通常不做归一化越界检查,坐标超边界时直接输出脏数据;三是类别名只能手填,遇到red_light和red混用的数据集就不知道怎么处理。自己写脚本,本质上是把这个黑匣子打开,让每一条转换逻辑都可控、可打印、可断点续跑,后面换数据集时改改类别白名单就能复用。

3.2 先摸清目录和类别名:三条命令顶过半小时手动翻

写转换脚本之前,先用几条命令把数据底数摸清楚,顺序是:统计文件类型、看标注文件名、抽样看单个XML内容:

find . -type f | awk -F. '{print $NF}' | sort | uniq -c ls annotations/ | head -20 cat annotations/1974_02_17_0001.xml | head -40

逻辑说明:find按扩展名统计文件类型,确认jpg、xml、txt数量是否基本对齐;ls看标注文件命名规则;第三行直接查看XML内容,确认<name>标签是red_light还是red。按经验,这三条命令能挡掉八成低级错误,比如解压后多了一层嵌套目录、XML文件名和JPG文件名命名规则不一致、类别名里混着全角和半角空格。

3.3 XML转YOLO脚本:边界保护、类别白名单和断点续转

把VOC转成YOLO的核心脚本如下,类别白名单放在文件顶部,方便改成你自己的类别集合:

import os import xml.etree.ElementTree as ET CLASS_NAMES = ['red_light', 'yellow_light', 'green_light'] def convert_xml_to_yolo(xml_path, out_dir): tree = ET.parse(xml_path) root = tree.getroot() img_w = float(root.find('size/width').text) img_h = float(root.find('size/height').text) if img_w <= 0 or img_h <= 0: print(f"[跳过] 图像尺寸无效: {xml_path}") return [] lines = [] for obj in root.findall('object'): name = obj.find('name').text.strip() if name not in CLASS_NAMES: print(f"[跳过] 未知类别: {name} -> {xml_path}") continue cls_id = CLASS_NAMES.index(name) bnd = obj.find('bndbox') x1 = float(bnd.find('xmin').text) y1 = float(bnd.find('ymin').text) x2 = float(bnd.find('xmax').text) y2 = float(bnd.find('ymax').text) cx = min(max((x1 + x2) / 2.0 / img_w, 0.0), 1.0) cy = min(max((y1 + y2) / 2.0 / img_h, 0.0), 1.0) w = min(max((x2 - x1) / img_w, 0.0), 1.0) h = min(max((y2 - y1) / img_h, 0.0), 1.0) if w == 0 or h == 0: print(f"[警告] 零宽高框: {xml_path} -> {name}") continue lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") if not lines: return [] txt_name = os.path.splitext(os.path.basename(xml_path))[0] + '.txt' os.makedirs(out_dir, exist_ok=True) with open(os.path.join(out_dir, txt_name), 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) return lines if __name__ == '__main__': xml_dir = 'Annotations' txt_dir = 'labels' for xml_file in os.listdir(xml_dir): if xml_file.endswith('.xml'): convert_xml_to_yolo(os.path.join(xml_dir, xml_file), txt_dir)

逻辑说明:脚本先读取XML里的size,再对每个object做坐标变换。边界保护部分用min/max把值限制在0~1之间,同时打印警告,避免某个越界标注直接污染整个txt。类别白名单用CLASS_NAMES管理,如果包里的类别名是red而不是red_light,改这一处就行,不需要动转换主体。

几个参数的取舍值得说清楚:img_w和img_h必须和实际图片分辨率一致,如果XML里的size写错,转换后所有框都会偏移,所以3.2里抽样打开XML非常重要;continue本质是放弃当前目标而不是报错退出,跑完统计一下日志里“跳过”的次数,心里才有数;txt文件名取XML主文件名是为和jpg一一对应,不要擅自加前后缀。

3.4 划分8:2脚本:固定随机种子,图片和标签同步搬家

标签转完后,要按比例做训练集和验证集划分。常见做法是80%训练、20%验证,测试集从验证里再拆或者先用train加val过一轮。下面这个脚本会同时移动jpg和txt,保证标签不丢:

import os import random import shutil seed = 42 train_ratio = 0.8 img_dir = 'images' label_dir = 'labels' out_dir = 'split_data' random.seed(seed) if os.path.exists(out_dir): shutil.rmtree(out_dir) files = [f for f in os.listdir(img_dir) if f.lower().endswith(('.jpg', '.png', '.jpeg'))] random.shuffle(files) train_n = int(len(files) * train_ratio) train_files = files[:train_n] val_files = files[train_n:] def move_file(base, filename, subset, subdir): src = os.path.join(base, filename) dst_dir = os.path.join(out_dir, subset, subdir) os.makedirs(dst_dir, exist_ok=True) shutil.move(src, os.path.join(dst_dir, filename)) for subset in ['train', 'val']: file_list = train_files if subset == 'train' else val_files for f in file_list: move_file(img_dir, f, subset, 'images') txt = os.path.splitext(f)[0] + '.txt' txt_src = os.path.join(label_dir, txt) if os.path.exists(txt_src): move_file(label_dir, txt, subset, 'labels') else: print(f"[缺失] {f} 没有对应标签,移动后训练时会出现警告") for subset in ['train', 'val']: img_count = len(os.listdir(os.path.join(out_dir, subset, 'images'))) lab_count = len(os.listdir(os.path.join(out_dir, subset, 'labels'))) print(f"{subset}: images={img_count}, labels={lab_count}")

逻辑说明:random.seed(42)这行不要删,它让每次运行的结果一致,实验之间才能对比。末尾重新统计划分后的图片和标签数量,如果数量对不上,说明有jpg缺txt,要回到3.2排查。脚本开头清空out_dir是为了防止重复运行时把已移动的文件再次移动导致报错。注意这行shutil.rmtree会删除目录,路径不要写错。

注意:固定随机种子是划分脚本里最容易忽略却最值得保留的一行。没有它,每次划分结果都不同,训练和验证集组合一直在变,模型精度无法横向比较。

3.5 转换后的自检:画框回看、统计各类别框数

转完格式不是结束,有两个自检动作必做。第一,画框回看,用OpenCV把txt坐标还原成像素框画到图里,重点看三个地方:交通灯是否被完整框住、框是否明显偏大偏小、夜间图上灯体周围光晕有没有干扰。第二,统计标签数量,命令如下:

cat labels/train/*.txt | awk '{print $1}' | sort | uniq -c

跑完之后你会看到类似3000 0 / 1800 1 / 1200 2这样的输出。如果green_light数量只有red_light的三分之一,先别急着训练,回到第4章把类别均衡处理一下再继续,否则损失函数很容易被大类别带偏。

4. 训练前的地雷排查:红绿灯数据常见的5个翻车点

4.1 夜间小目标一直漏检:多半是标签框中心没对准灯体

红绿灯是典型的小目标,1920×1080的图里灯体往往只有30×30像素,去掉光晕之后有效特征更少。到了夜间场景,灯体整体偏亮、背景对比强烈,按理说应该好检测,实际模型却经常漏检。原因有两层:第一,很多数据集夜间占比不够,5000张里如果只有几百张夜景,模型对夜间的特征拟合就不充分;第二,夜间标注质量通常偏低,标注员把光晕也算进框里,导致框中心点偏移到高光区域,真实灯体只占框的一小块。

解决思路分两步:先统计夜间图片数量,如果明显不足,考虑补充;如果不想动数据,把imgsz从默认640提高到960,红绿灯这类小目标在缩放过程中不容易被降采样丢掉。更重要的是,抽查夜间图片的标签中心点是否落在灯体中心,偏差超过半个灯体就重新核定这些样本。夜间图是红绿灯方向的硬骨头,把时间花在标签复核上,比事后反复调参省事得多。

4.2 刹车灯车尾灯被当成红灯:正样本里没给干扰物留位置

黄灯、红灯和车尾灯都有红色特征,如果训练集里只标了红绿灯、没有任何刹车灯负样本,模型会把所有红色圆形发光体都当成候选框。验证集上mAP很好看,是因为验证集也来自同一批数据、本身没有刹车灯图片,一旦换到真实路测就疯狂误报。这个问题表面看是模型问题,本质是数据标注时没有把背景中的红色干扰物显式建模。

解决方式有两种:一是收集一批包含刹车灯、车尾灯、红色广告灯箱的图片放在训练集中但不打框,让模型把这些区域当作背景学习,YOLO的训练本质是正样本框和全图背景做区分,背景一旦丰富,误检会明显下降;二是单独加一个red_lamp类别并给刹车灯打框,效果最直接,但需要额外标注成本。就像车牌领域绕不开CCPD、遥感检测绕不开HRSC2016和DOTA一样,红绿灯方向要上线,刹车灯这关必须主动解决,不能指望网络自己学会区分。

4.3 三套标签类别错位:先做映射表再转换,不要直接套白名单

不少数据包里的VOC、COCO、YOLO三套标签不是同一套命名规范生成的。可能VOC里叫red,COCO里叫red_light,YOLO里叫0,只有映射关系对得上才有意义。实际常见的翻车是类别名不统一,转换时用白名单直接过滤,结果几百个目标被静默跳过,模型还能训练、mAP还很高,只是输出类别全部错位。

解决方法是先建一张类别映射表再做转换,例如{"red": 0, "red_light": 0, "green": 1, "green_light": 1, "yellow": 2, "yellow_light": 2}。脚本里不要直接索引白名单,而是先走一遍映射表。多格式的数据包尤其要验证:从三套标签里随机各抽50张图,检查同名图片的框是否一致,不一致就以像素级原始标注为准重新生成。

4.4 黄灯样本天然少:类别不均衡拉偏分类头

5000张红绿灯图,三类灯的数量几乎不可能均衡。红灯和黄灯在路口的出现频率天然不同,标注员也更愿意给红灯打框,如果黄灯样本太少,类别不均衡就会让分类头偏向样本多的类别。这和YOLO损失函数的计算方式有关,多数类在总loss里贡献大,少数类的梯度被淹没,训练完的模型对少数类不敏感,训练日志里最常见的表现是loss曲线收敛但绿灯AP连0.5都到不了。

解决方向有三个:数据侧把小类图片重复采样或做复制增强;训练配置侧给少数类提高损失权重;模型侧从yolov8n换成yolov8m增加容量,观察分类损失是否下降。最笨但最有效的方法还是回到数据本身,如果黄灯帧只有几十张,怎么调参都救不回来,直接补充这个类别的样本才是正路。

4.5 图片和标签对不上:训练时报图像数为零或loss不降

从网上下载的红绿灯数据集经常出现这种情况:图片5000张,标签却只有4800个txt文件,或文件名中间有几张对不上。Ultralytics在训练启动时会打印train: images=5000, labels=4800之类信息,很多人不看这行,结果训练过程中不断跳过无标签图片,模型收敛速度明显变慢甚至loss不降。

排查方法很简单,写个脚本对images和labels两个目录的文件主名取交集,把只有图没有标签的文件挑出来单独放一个文件夹,人工决定是补标签还是丢弃。后面训练时每轮都会快一点,日志也干净很多。

5. 用Ultralytics YOLO跑通训练:环境配置、data.yaml与3个必调参数

5.1 0基础也能照做的环境配置:conda、pip、一条命令验证安装

Ultralytics YOLO是目前社区应用最多的训练框架,对纯新手也确实是最友好的。这里给一套适合0基础小白的配置方式,Windows和Linux通用:

conda create -n yolo python=3.10 -y conda activate yolo pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

逻辑说明:conda建环境是为了隔离系统Python,避免依赖冲突;pip install ultralytics会同时安装yolo命令行工具和Python包;torch单独安装是为了让CUDA版本跟显卡驱动匹配,这里用的cu118对应CUDA 11.8。装完以后跑一句验证:

yolo detect predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpg

能正常打印出检测框,说明环境已经通了。如果没装成功,最常见原因是torch版本和显卡驱动不匹配,用nvidia-smi查驱动支持的CUDA版本再选对应安装源。

5.2 目录结构:images和labels必须平行且同名,框架才找得到标签

数据划分完后,需要把目录整理成Ultralytics认识的固定结构,最终长这样:

traffic_light/ ├── data.yaml └── datasets/ ├── train/ │ ├── images/ │ └── labels/ └── val/ ├── images/ └── labels/

逻辑说明:框架自动到labels同级目录找标签,图片和标签只靠主文件名关联,比如images/train/1974_02_17_0001.jpg对应labels/train/1974_02_17_0001.txt。三套数据格式里的YOLO标签目录,就是这里的labels,不需要额外处理。如果你的包解压出来是Annotations和JPEGImages这种VOC命名,先按第3章脚本转换再放进来。

5.3 data.yaml:路径、names顺序和类别序号是三位一体

data.yaml是整个训练入口,三个地方最容易写错:path路径、names顺序、train和val目录。给一个和3.4节划分脚本配套的示例:

path: ./traffic_light/datasets train: train/images val: val/images names: 0: red_light 1: yellow_light 2: green_light

参数说明:path是数据集的根目录,可以用绝对路径也可以用相对路径,但相对路径是相对你运行yolo命令时的目录,而不是相对data.yaml的位置,这点新手最容易晕。train和val填图片所在目录,框架会去同级labels目录找txt。names的顺序必须和txt文件第一列的类别序号完全一致,比如3.3节脚本里CLASS_NAMES顺序是red_light, yellow_light, green_light,这里第0类就必须是red_light,不能调换。类别名和路径都建议用英文,中文容易触发编码问题。

5.4 训练命令与3个必调参数:epochs、batch、imgsz

训练命令本身很短,参数才是要花时间理解的。以红绿灯这种小目标任务为例,先给一个最小可跑配置:

yolo detect train \ model=yolov8n.pt \ data=traffic_light/data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ device=0

逻辑说明:model=yolov8n.pt表示从COCO预训练权重继续训练而不是随机初始化,能显著提高收敛速度和小样本表现,比从零训练少烧很多时间。data指向data.yaml,epochs=150对5000张图的场景属于合理范围,偏少欠拟合,太多容易过拟合。imgsz=640是默认值,但对红绿灯这类小目标,建议后面试imgsz=960,模型在降采样阶段不容易把小目标丢掉。device=0指第一块GPU,没有GPU就把这行删掉,torch会自动用CPU,速度会慢很多但能跑。

三个必调参数里,epochs决定你等多长时间,我一般会开早停patience=20让它自己在loss不再下降时停下来;batch受显存限制,8G显存从batch=16起步,显存不够就调小一半,而不是去缩小图片尺寸;imgsz对小目标影响权重最高,960训练可能比640的mAP高3至5个点。训练中断之后加resume=True可以继续,这是Ultralytics自带的后悔药,不用从头再跑。

5.5 训练期常见报错:显存不足、No labels found、loss为nan

训练过程中最常碰到的三个报错和处理方式:

CUDA out of memory:现象是启动后几秒内直接报错退出。原因是batch或imgsz设置超过显存上限。解决方式:batch减半,或imgsz降到512,优先减batch,因为它对模型精度影响小。

No labels found in dataset:现象是训练一启动就提示找不到标签。原因是data.yaml里的train指向了图片目录,但图片没有对应的labels目录,或者labels目录里是空的。解决方式:先手动确认datasets/train/labels里有没有txt,再用3.4脚本重新划分一次。

loss变成nan:现象是训练几轮后loss打印为nan。原因是混合精度训练下个别样本数值溢出。解决方式:在训练命令里加amp=False关掉混合精度,通常就能恢复。关掉后训练速度会慢一点,但数值稳定是优先的,等确定不是数据问题再重新打开。

6. 训练完先别急着部署:mAP之外的三个验证动作

6.1 用验证集完整推理一遍,重点看0.3~0.5置信度区间的框

训练结束后runs/detect/train目录里会留下best.pt和last.pt,先用best.pt把验证集完整推理一遍,把每张图的检测结果画出来浏览,重点看置信度在0.3到0.5之间的框。这些模糊框往往决定部署时要不要保留,也最容易暴露刹车灯误检和黄灯漏检这类问题。我习惯把高置信度误检单独挑出来合并成一张大图,所有红色物体放一起看,比翻训练曲线直观得多。

6.2 单独测夜间、远距离、遮挡三个边界场景

mAP是一个整体平均数字,但红绿灯任务真正考验的是边界情况。把夜间、远距离、部分遮挡三种场景各挑5到10张图放到单独文件夹里跑预测,看漏检是否集中在同一类场景。如果夜间普遍漏检,就按4.1节方法复核夜间标签并发起重训;如果远距离全部漏检,优先提高imgsz;如果遮挡场景灯体残缺,就需要补充部分遮挡样本。这三个维度的结论直接决定你敢不敢把这个模型放到真实决策链路上。

6.3 导出ONNX看一眼单帧耗时,提前暴露部署链路问题

检测模型不只要准,还要跑得动。训练完成后做一次部署格式导出,是最省事的验证动作:

yolo export model=best.pt format=onnx imgsz=640

导出后再用一批连续帧在目标设备上跑一遍,记录单帧耗时。红绿灯应用通常要求单帧30ms以内完成,如果超标,优先换更小的模型结构或降低推理分辨率。这一步能提前暴露ONNX、TensorRT、OpenVINO等链路里的兼容问题,别等到上车才发现。

我这几年做目标检测项目最深的体会是,数据里的坑远多于模型结构里的坑。拿到的数据包哪怕转好了三种格式,也值得先花一小时做格式检查和标签抽样复核,这比之后调三个月参值钱得多。希望这份红绿灯数据能成为你跑通YOLO全流程的第一块跳板,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询