资讯详情

红绿灯颜色检测数据集:双格式标注与YOLO训练实战

📅 2026/10/11 16:16:25 | 华诺云谱 👁 阅读
红绿灯颜色检测数据集:双格式标注与YOLO训练实战
简介一套面向计算机视觉目标检测任务的交通信号灯红绿灯颜色检测数据集适合需要训练红绿灯识别模型的研究人员、算法工程师或高校课程实践使用。数据集包含19456张jpg图片并为每张图片提供Pascal VOC格式xml与YOLO格式txt两套标注文件类别覆盖green、red、yellow总框数30432个其中green框13164、red框15044、yellow框2224可直接用于监督训练与验证。资源压缩包约895.71MB文件总数2000个以xml标注文件为主附有说明txt文件便于理解标注规则与目录结构矩形框标注方式符合常见检测任务要求可快速划分训练集与验证集免去人工标注环节。已有366人学习下载适合有明确红绿灯检测需求、希望跳过数据整理环节的开发者直接取用。1. 交通信号灯红绿灯颜色检测19450 张双格式数据集到底能帮你省下什么同一个模型白天路口跑得好好的一进隧道就开始乱报——这是红绿灯检测项目最常见的痛点公开数据集往往只标了信号灯的位置没标红、黄、绿的颜色语义。这份交通信号灯红绿灯颜色检测数据集共 19450 张图、3 类颜色同时提供 VOC 和 YOLO 两种标注格式正好补上这个缺口。它解决的问题很实在省掉从零采集、标注的重复劳动让你只需要验证标注质量就能直接进入 YOLOv5/v8/v11 的训练流程。新手可以借它把 VOC 和 YOLO 两种格式的差异和转换原理彻底弄懂熟手可以省下好几天数据清洗的时间把精力花在小目标召回和颜色误判这类真正难啃的点上。2. VOC 标注怎么落到 YOLO 训练先搞清目录和 XML 结构收到这份资源的第一步不是急着训练而是把目录结构和标注内容摸清楚。红绿灯检测的数据链路里VOC 格式负责给人看YOLO 格式负责给模型吃两者之间靠文件名一一对应。下面先讲清楚双格式目录长什么样再给一个解析脚本把 XML 标签变成可统计的表格。2.1 双格式目录结构JPEGImages 与 labels 各管什么解压后按常规组织方式目录结构大致是下面这样。拿到手后先对着检查一遍缺了哪个目录后续训练就会在哪一步翻车。TrafficLight_Dataset/ ├── VOC/ │ ├── JPEGImages/ # 19450 张 jpg 原图 │ ├── Annotations/ # 19450 个 xml 标注与图片同名 │ └── ImageSets/ │ └── Main/ │ ├── train.txt # 训练集文件清单 │ └── val.txt # 验证集文件清单 ├── YOLO/ │ ├── images/ │ │ ├── train/ # 按 YOLO 惯例划分的图片 │ │ └── val/ │ ├── labels/ │ │ ├── train/ # 同名的 txt 标签 │ │ └── val/ │ └── data.yaml # 类别名与路径配置 └── classes.txt # 类别清单red, yellow, green重点看两处。第一JPEGImages 和 Annotations 必须完全同名一个 jpg 对应一个 xml数量对不上说明打包时有遗漏第二YOLO 侧的 labels 目录里每张图对应一个同名 txt每行是一个目标。如果这份资源自带的 YOLO 标签已经划分好了 train/val训练前只需要核对类别顺序不用重新转换如果只有 VOC 标注就按第 3 章的脚本自己做转换。两种格式的核心差异在坐标表达上。VOC 用的绝对像素坐标YOLO 用归一化后的中心点坐标这块是后面所有转换逻辑的根基先列一个对比表记住。对比项VOC XMLYOLO txt文件后缀.xml.txt坐标形式xmin, ymin, xmax, ymax 绝对像素class_id, cx, cy, w, h 归一化是否归一化否直接用像素值是除以图片宽高取值 0~1类别表达字符串如 red整数索引如 0典型工具链labelImg、VOC 系评测脚本YOLO 系训练框架记住一个关键点VOC 里 xmin/xmax 是框住车灯的矩形YOLO 里的 cx/cy 是同一个矩形的中心点w/h 是矩形宽高在图片宽高中的占比。坐标本身不变变的只是表达方式。2.2 把 XML 标签读进表格一个脚本看清三类颜色分布拿到 XML 先别急着转格式先做一次人口普查每张图有哪些类别的框、框的大小分布怎么样、有没有坐标越界。这个习惯能帮你提前发现标注里的大小坑。import glob import xml.etree.ElementTree as ET import pandas as pd rows [] # 注意路径换成你解压后的实际目录 xml_list sorted(glob.glob(VOC/Annotations/*.xml)) for xml_path in xml_list: tree ET.parse(xml_path) root tree.getroot() filename root.findtext(filename) img_w int(root.find(size).findtext(width)) img_h int(root.find(size).findtext(height)) for obj in root.findall(object): name obj.findtext(name).strip() # red / yellow / green bndbox obj.find(bndbox) xmin int(float(bndbox.findtext(xmin))) ymin int(float(bndbox.findtext(ymin))) xmax int(float(bndbox.findtext(xmax))) ymax int(float(bndbox.findtext(ymax))) rows.append({ filename: filename, class: name, img_w: img_w, img_h: img_h, xmin: xmin, ymin: ymin, xmax: xmax, ymax: ymax, box_w: xmax - xmin, # 框宽像素 box_h: ymax - ymin, # 框高像素 }) df pd.DataFrame(rows) # 1. 每个类别的目标数量 print( 类别分布 ) print(df[class].value_counts()) # 2. 极小目标数量宽度 15px 的信号灯 print( 小目标数量(框宽15px) ) print((df[box_w] 15).sum()) # 3. 坐标越界检查 print( 越界检查 ) print((df[xmax] df[img_w]).sum(), (df[ymax] df[img_h]).sum())这段脚本三个输出分别回答三个问题三类颜色是否齐全、分布是否极端不均、标注是否有越界。类别统计如果发现 red 有 5000 个框、yellow 只有 800 个框训练时就要考虑对黄色做增强否则模型会对黄灯视而不见。小目标数量统计则告诉你这份数据里远距离信号灯占比直接影响训练时 imgsz 该设多少。一个血泪经验XML 里的类别名字段经常出现拼写变体比如 red 和 red_light 混着标、green 标成 grean。出现这种情况时先用df[class].unique()打印全部类别名确认是三类还是五类再决定要不要做名字归一化。否则转成 YOLO 后模型会多出两个莫名其妙的类训练出来的结果全是玄学。3. 从 VOC 到 YOLO转换脚本、类别映射与数据划分如果资源里只带了 VOC 标注或者你想按自己的类别顺序重新生成 YOLO 标签这一步就得自己动手。整条转换链路就两步把 XML 的绝对坐标转成归一化中心坐标再把数据按训练/验证划分好。这个章节直接给可复制的代码和参数说明。3.1 xml2yolo 转换脚本坐标计算与 6 位小数约定VOC 和 YOLO 的转换公式并不复杂中心点 x 坐标等于(xmin xmax) / 2再除以图片宽度宽高分别除以图片宽高。真正的坑在类别映射顺序和坐标边界处理上下面这个脚本把这两件事都做了。import os import glob import xml.etree.ElementTree as ET # 类别映射表VOC 字符串名 - YOLO 整数 id # 注意这个顺序必须和训练时 data.yaml 里的 names 完全一致 CLASS_MAP {red: 0, yellow: 1, green: 2} def convert_xml_to_yolo(xml_path, label_dir): 单个 xml 转成同名 txt写入 label_dir tree ET.parse(xml_path) root tree.getroot() filename root.findtext(filename).strip() img_w int(root.find(size).findtext(width)) img_h int(root.find(size).findtext(height)) txt_path os.path.join(label_dir, filename.replace(.jpg, .txt)) lines [] for obj in root.findall(object): name obj.findtext(name).strip() if name not in CLASS_MAP: print(f[跳过] {filename} 出现未知类别: {name}) continue bndbox obj.find(bndbox) xmin int(float(bndbox.findtext(xmin))) ymin int(float(bndbox.findtext(ymin))) xmax int(float(bndbox.findtext(xmax))) ymax int(float(bndbox.findtext(ymax))) # 越界保护坐标溢出图片时裁回边界避免训练 loss 变 nan xmin max(0, min(xmin, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) xmax max(0, min(xmax, img_w - 1)) ymax max(0, min(ymax, img_h - 1)) # 核心转换中心点坐标和宽高均除以图片宽高得到 0~1 的归一化值 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h # 6 位小数是 YOLO 惯例精度足够且文件体积可控 lines.append(f{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: with open(txt_path, w) as f: f.write(\n.join(lines) \n) # 批量转换 xml_files sorted(glob.glob(VOC/Annotations/*.xml)) os.makedirs(YOLO/labels, exist_okTrue) for xml_file in xml_files: convert_xml_to_yolo(xml_file, YOLO/labels) print(f转换完成共处理 {len(xml_files)} 个 xml)这段代码里有三个细节值得单独说。第一个越界保护不是可选项标注工具里手抖画出的框偶尔会超出图片边界如果不在转换这步做 clip训练读到大于 1 的归一化坐标时轻则报 warning重则 loss 变成 nan。第二个类别映射表是整个链路的单一真源CLASS_MAP 和 data.yaml 的 names 顺序一旦不一致模型训练的标签就是错位的这个问题排查起来最费时间。第三个.6f是 YOLO 训练框架的常见写法位数太少框会抖动位数太多没有意义。转换完随手抽查一个 txt 文件内容应该是这样的格式每行五列第一列是类别整数。0 0.531250 0.472222 0.052083 0.083333 2 0.748800 0.351000 0.044800 0.067000第一行0表示红灯后面依次是 cx、cy、框宽、框高全部在 0~1 之间。如果看到某列大于 1说明分母除错了——常见是宽度和高度写反。3.2 训练集划分随机种子、8:2 比例与文件清单生成转换完成后要做的第二件事是划分训练集和验证集。19450 张图按 8:2 划分训练集约 15560 张、验证集约 3890 张这个量级对训练一个红绿灯检测模型是够用的。划分时最容易犯的错误是只移动了图片忘了同步标签所以脚本里要保证图片和 txt 一一对应。import os import glob import random import shutil random.seed(782) # 固定种子保证每次划分结果可复现 img_files sorted(glob.glob(YOLO/images/*.jpg)) random.shuffle(img_files) val_count int(len(img_files) * 0.2) # 20% 做验证集 val_files img_files[:val_count] train_files img_files[val_count:] for split, files in [(train, train_files), (val, val_files)]: os.makedirs(fYOLO/images/{split}, exist_okTrue) os.makedirs(fYOLO/labels/{split}, exist_okTrue) for img_path in files: # 文件名不含扩展名用来定位同名的 txt stem os.path.splitext(os.path.basename(img_path))[0] label_path fYOLO/labels_all/{stem}.txt # 上一步转换出的标签目录 shutil.move(img_path, fYOLO/images/{split}/{stem}.jpg) shutil.move(label_path, fYOLO/labels/{split}/{stem}.txt) print(f训练集 {len(train_files)} 张验证集 {len(val_files)} 张)划分逻辑本身简单但有两个习惯我建议你养成。第一固定随机种子否则每次跑脚本划分结果都不同前后两次实验就没有可比性。第二划分完之后立刻数一遍 labels 目录里的 txt 数量跟 images 数量对上差一个文件都要找出来。train/val 比例也可以按需调整如果验证集太小、评估结果噪声大可以把比例调到 0.15如果数据里场景多样性强保持 0.2~0.25 更稳。如果是做交叉验证或需要精细评测可以在划分前加一层场景去重先把同一路口、连续帧的图片按文件名前缀分组再按组划分避免同一路口的相似帧同时出现在训练集和验证集里导致指标虚高。这份数据集的图片如果有按时间序列命名的规律这个去重步骤值得做。4. 避坑从标注清洗到训练翻车的五个常见问题数据准备到这一步最脏最累的活已经完了。但根据我自己的经历真正让项目延期的大概率不是模型结构选型而是下面这五类问题。每一条都按现象 → 原因 → 解决写清楚你在训练阶段遇到类似状况直接对号入座。4.1 训练时类别数量对不上3 类变 1 类现象训练日志里nc3但数据集里的标签文件读出来却有 5 类或者训练过程中报 warning提示某些 txt 里的类别 id 超出范围。原因VOC 转 YOLO 时用的类别映射顺序和训练时 data.yaml 的 names 顺序不一致。最常见的是转换脚本里写死red:0, yellow:1, green:2而 data.yaml 里把 red 放在第 2 位导致所有标签整体错位。解决训练前先写一段扫描脚本遍历所有标签 txt用集合统计出现过的类别 id。# 快速扫描 labels 里所有 txt 的第一列看看实际出现哪些类别 id cat YOLO/labels/train/*.txt | awk {print $1} | sort -n | uniq -c输出结果应该只有 0、1、2 三类。如果出现 3、4 等超范围值先回去核对 CLASS_MAP 和 data.yaml 的 names 顺序如果出现 0 和 2 但没有 1说明样本里可能没有黄灯需要检查原始 XML 的类别名是不是拼写不一致。4.2 归一化坐标越界训练 loss 直接变成 nan现象训练刚开始几个 batchloss 就跳成nan或者训练能跑完但验证时画出的框全部偏到图片边缘外。原因VOC 标注里 bndbox 坐标超出了图片宽高范围转换时没有做越界保护。另一个原因是转换公式里分子分母用错比如cx (xmin xmax) / 2.0 / img_h用了高度去归一化 x 坐标。解决转换脚本里给 xmin/xmax/ymin/ymax 加 clip把坐标限制在0 ~ img_w-1和0 ~ img_h-1之间。训练前再用第 2.2 节的统计脚本跑一遍越界检查确保xmax img_w的数量是 0。如果发现越界标注优先定位是哪几个 xml 文件用脚本自动修正边界值而不是整份数据重标注。4.3 黄灯样本太少整体 mAP 好看per-class AP 露馅现象训练结束看验证结果mAP50 有 0.85 以上觉得模型不错但打开 per-class AP 一看green 和 red 都在 0.9 上下yellow 只有 0.2。原因交通信号灯本身的颜色出现频率就极不均衡。红灯和绿灯持续时间长黄灯只闪几秒数据采集时自然就少。模型在类别不均衡下会把弱势类学得保守相当于变相忽略了黄灯。解决先看统计脚本的类别分布确认失衡程度然后对黄灯样本做针对性补充。常用手段有两个一是对黄色样本做过采样复制几份放进训练集二是对黄色做轻量增强比如小范围亮度扰动、旋转 10~15 度。评估时必须看 per-class APUltralytics 训练完的results.csv里可以直接拉出每一类的 AP 曲线。4.4 小目标消失远距离信号灯在 640 分辨率下被压没现象原图上能看清的远距离红绿灯训练时用imgsz640缩小后只有两三个像素模型训练完对远处信号灯几乎不检。原因直接 resize 把小目标的信息压没了。信号灯本身就小远距离时在 1920x1080 原图里可能只有 20x30 像素缩到 640 后变成 7x10 像素CNN 下采样几次后特征就消失了。解决先用第 2.2 节的脚本统计框宽分布如果大量目标小于 15 像素直接把训练 imgsz 拉到 960 或 1280同时开启 letterbox 而不是直接拉伸。更高阶的做法是先按 2x 切块训练推理时再滑窗检测但对大多数项目来说imgsz960 配合 YOLOv8 已经能缓解绝大部分小目标丢失问题。4.5 颜色增强把语义搞坏红灯被改成了橙黄灯现象训练时开了默认的数据增强验证阶段红灯的误报率突然变高模型把红灯检测成黄灯precision 掉得明显。原因数据增强里的 HSV 扰动尤其是 hue 通道的随机偏移会把红灯的色相改到橙色区间绿灯改到黄绿区间。信号灯检测里颜色是最核心的语义通用增强策略在这里不适用。解决在 data.yaml 里显式调小增强参数把 hue 扰动关掉或限制在极小范围。用 Ultralytics 训练时可以在augment参数上做约束常见的做法是hsv_h0.0, hsv_s0.2, hsv_v0.2只保留少量饱和度和亮度扰动完全关闭色相扰动。亮度扰动其实对红绿灯有益——它能模拟白天逆光和夜晚两种光照但一定要控制幅度。5. 把数据集跑起来一次验证标注质量的 YOLOv8 实验与进阶思路前面几步做好了训练本身反而是最省心的环节。这一章用 YOLOv8 官方仓库跑一次完整实验顺带验证数据集质量到底行不行再讲一个按颜色拆指标排查问题的进阶习惯。5.1 一份最简训练配置data.yaml 与命令行参数先把数据集的配置文件写好。Ultralytics 系的 YOLOv5/v8/v11 都认这种 YAML 格式路径和 names 一定要和转换脚本的 CLASS_MAP 对齐。# data.yaml path: ./YOLO # 数据集根目录相对路径 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 names: 0: red 1: yellow 2: green训练命令用一句话就能起yolo detect train datadata.yaml modelyolov8s.pt imgsz960 epochs80 batch16 device0几个参数按项目实际调整imgsz用 960 而不是 640是考虑到第 4.4 条的小目标问题epochs初期验证 50 轮就够看趋势正式训练拉到 100~120 轮batch按显存来16 是一个 24G 显存下的稳妥值显存小就降到 8。如果只是想快速验证标注质量把model换成yolov8n.pt更快s 模型的精度更高但训练时间多一倍。训练完成后优先打开runs/detect/train/results.csv看两个指标mAP50和mAP50-95。mAP50 高说明大目标检测没问题mAP50-95 高说明框定位准、小目标也活下来了。如果 mAP50 不错但 mAP50-95 明显低大概率是标注框边界不够精细或者小目标占比高、定位偏差大。5.2 进阶按颜色拆指标别被整体数字骗了整体 mAP 会掩盖类别不均衡的问题所以我的习惯是训练完立刻按颜色分别看指标。用 Ultralytics 的 val 接口可以直接拿到每个类别的 APfrom ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.val(datadata.yaml) # 按类别打印 AP50 names results.names for i in range(len(names)): name names[i] ap50 results.box.ap50[i] print(f{name}: AP50 {ap50:.4f})如果 yellow 类的 AP50 明显低于另外两类先回到第 4.3 条做样本补充或增强如果 green 和 red 之间有交叉误判打开验证集上的混淆矩阵确认是不是增强导致颜色漂移。这也是我摸到的一个规律红绿灯检测项目里绝大多数模型不听话的问题根源都在数据处理而不是网络结构。做完上面这轮验证整个数据链路就闭环了。顺手提一个能再进一步的方向在视频场景里做时序去抖比如 T4 1080p 25 帧的推理环境下对连续帧的目标框做跟踪和投票能显著减少单帧误检。但颜色判断尽量留在单帧做多帧平均反而会把黄灯闪烁的节奏抹掉。从那以后我每次拿到新数据集都强制先跑完统计脚本再谈训练确认类别分布和框质量绝不在黑匣子上盲训。这份红绿灯数据集你也按这套流程走一遍能省下不少返工的时间希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑