简介:这份资源面向计算机视觉入门与进阶开发者、自动驾驶或智能交通方向的学生与工程师,提供一套可直接用于YOLO系列目标检测训练的交通标志识别数据集。数据均来自真实道路场景,覆盖多种光照与视角,标注质量较高,并同时给出voc、coco和yolo三种格式标签,分别存放于独立文件夹,便于在不同框架间切换使用。压缩包共约2000个文件,以1985个xml标注文件为主,另含少量txt、html与py脚本,整体约807.7MB。资源附带数据集划分脚本,可按需生成训练集、验证集与测试集,并配套Windows与Linux环境搭建、训练案例教程,帮助读者快速跑通从环境配置到模型训练的全流程。目前已有801人学习下载,适合希望省去数据采集与格式转换环节、专注模型调优与实验对比的读者参考使用。
1. 从一张被漏标的限速牌说起:这套交通标志数据集到底能干什么
去年帮一个做辅助驾驶 demo 的团队看模型,验证集 mAP 卡在 0.62 上不去,可视化一拉出来,限速 80 的牌子十张里有三张框是歪的,还有两张压根没标。问数据哪来的,答曰网上随手扒的。这就是交通标志识别最真实的痛点——模型结构抄来抄去就那几套,真正拉开差距的是标注质量。今天拆的这套 YOLO 交通标志识别数据集,3000 张真实道路场景图,用 labelImg 逐张标过,同时给了 VOC 的 xml、COCO 的 json 和 YOLO 的 txt 三种标签格式,还配了划分脚本和 Linux/Windows 双平台的训练教程。它解决的不是"有没有数据"的问题,而是"数据能不能直接喂进 YOLO 且不用返工"的问题。适合谁?正在做交通标志检测课程设计的学生、要快速搭 baseline 的算法工程师、以及被格式转换折磨过的从业者。下面按"是什么 → 怎么用 → 坑在哪"的顺序,把这份资源拆开讲透。
2. 三种标签格式的取舍:VOC、COCO、YOLO 到底该用哪个
拿到数据集第一件事不是急着训练,而是搞清楚三种格式各自的坐标系和适用边界。选错了格式,后面转换脚本能让你怀疑人生。
2.1 三种格式的坐标定义差异
VOC 格式用 xml 存储,坐标是绝对像素值,<bndbox>里是xmin/ymin/xmax/ymax,左上角为原点。COCO 格式用 json 存储,bbox是[x, y, width, height],同样是绝对像素,但注意它是宽高不是右下角坐标,这个差异坑过不少人。YOLO 格式用 txt 存储,每行class_id x_center y_center width height,全部是归一化到 0~1 的相对值,且中心点坐标而非左上角。
| 格式 | 文件类型 | 坐标类型 | 表示方式 | 典型用途 |
|---|---|---|---|---|
| VOC | xml | 绝对像素 | xmin,ymin,xmax,ymax | 传统检测框架、labelImg 原生输出 |
| COCO | json | 绝对像素 | x,y,w,h | 多任务、实例分割、COCO 预训练权重 |
| YOLO | txt | 归一化相对值 | cx,cy,w,h | YOLOv5/v8/v11 直接训练 |
为什么这套资源三种都给?因为实际项目里你经常要在不同环节切换。比如用 COCO 预训练权重做迁移学习,就得先有 COCO 格式;用 labelImg 补标新数据,出来的是 VOC;最终训练 YOLO 又要转成 txt。三种格式齐备,等于省掉了中间所有转换环节。
2.2 用 Python 校验三种格式是否对齐
拿到数据先别信简介,自己写个脚本抽查同一张图在三种格式下的框是否一致。这是我最推荐的第一道质检。
import os import xml.etree.ElementTree as ET import json # 以一张图为例,校验 VOC 与 YOLO 标签是否对齐 img_name = "traffic_0001" img_w, img_h = 640, 480 # 替换为实际图片尺寸 # 读 VOC xml_path = f"VOC/Annotations/{img_name}.xml" tree = ET.parse(xml_path) root = tree.getroot() voc_boxes = [] for obj in root.findall("object"): bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) voc_boxes.append((xmin, ymin, xmax, ymax)) # 读 YOLO,反归一化回绝对像素 yolo_path = f"YOLO/labels/{img_name}.txt" yolo_boxes = [] with open(yolo_path) as f: for line in f: cid, cx, cy, w, h = map(float, line.split()) xmin = (cx - w / 2) * img_w ymin = (cy - h / 2) * img_h xmax = (cx + w / 2) * img_w ymax = (cy + h / 2) * img_h yolo_boxes.append((xmin, ymin, xmax, ymax)) # 逐框比对,容差 1 像素 for vb, yb in zip(voc_boxes, yolo_boxes): diff = max(abs(a - b) for a, b in zip(vb, yb)) print(f"VOC {vb} vs YOLO {yb} -> max diff {diff:.2f}px")这段脚本的逻辑是:把 YOLO 的归一化坐标反算回绝对像素,再和 VOC 的绝对坐标逐框比对。参数上,img_w和img_h必须换成图片真实尺寸,否则反算全错。容差设 1 像素是因为标注工具保存时可能有四舍五入。如果 diff 超过 2 像素,说明三种格式不是同一批标注导出的,得警惕。
2.3 类别映射表要先固定下来
交通标志的类别数因数据集而异,常见的有限速、禁止、警告、指示几大类。这套资源用的是 labelImg 标注,类别名写在classes.txt或predefined_classes.txt里。训练前必须确认 YOLO 的data.yaml里names列表顺序和 txt 里的class_id严格对应。我一般会先跑一遍统计:
from collections import Counter import glob counter = Counter() for txt in glob.glob("YOLO/labels/*.txt"): with open(txt) as f: for line in f: cid = int(line.split()[0]) counter[cid] += 1 for cid in sorted(counter): print(f"class {cid}: {counter[cid]} boxes")跑完你会得到每个类别的框数量。如果某个类别只有个位数,基本可以判定是长尾或者误标,训练时要么过采样要么直接合并。这一步不做,后面 mAP 低了你都不知道是模型问题还是数据问题。
3. 划分脚本怎么用:train/val/test 三分的正确姿势
数据集划分看着简单,实则是最容易埋雷的环节。随机切分导致同一场景的图片同时出现在训练和验证集,验证指标虚高,上线就翻车——这种血泪经验我见过太多次。
3.1 三个划分脚本的分工
资源里给了三个脚本,别混用,各管各的:
训练集、验证集、测试集划分脚本(图片标签划分写入新文件夹).py:三分,把图片和标签复制到 train/val/test 三个新文件夹,适合从零开始整理。训练集、验证集划分脚本(图片标签划分写入新文件夹).py:二分,只分 train/val,适合数据量不大、不单独留测试集的场景。split_train_val生成ImageSets下txt文件划分脚本.py:不移动文件,只在ImageSets/Main/下生成train.txt、val.txt等索引文件,适合 VOC 风格的数据管理。
选哪个取决于你的训练框架。YOLOv5/v8 默认读images/和labels/目录,用前两个脚本直接分文件夹最省事;如果你用的是 PASCAL VOC 那套评估流程,用第三个生成索引。
3.2 按比例划分并固定随机种子
以三分脚本为例,核心逻辑是打乱后按比例切分。关键参数是随机种子,必须固定,否则每次跑结果不一样,实验没法复现。
import os import random import shutil random.seed(42) # 固定种子,保证可复现 img_dir = "images" label_dir = "labels" out_dir = "dataset_split" train_ratio, val_ratio, test_ratio = 0.7, 0.2, 0.1 imgs = [f for f in os.listdir(img_dir) if f.endswith((".jpg", ".png"))] random.shuffle(imgs) n = len(imgs) n_train = int(n * train_ratio) n_val = int(n * val_ratio) splits = { "train": imgs[:n_train], "val": imgs[n_train:n_train + n_val], "test": imgs[n_train + n_val:] } for split, files in splits.items(): os.makedirs(f"{out_dir}/{split}/images", exist_ok=True) os.makedirs(f"{out_dir}/{split}/labels", exist_ok=True) for img in files: shutil.copy(os.path.join(img_dir, img), f"{out_dir}/{split}/images/{img}") txt = os.path.splitext(img)[0] + ".txt" src_txt = os.path.join(label_dir, txt) if os.path.exists(src_txt): shutil.copy(src_txt, f"{out_dir}/{split}/labels/{txt}") else: print(f"警告:{img} 没有对应标签")逻辑说明:先random.shuffle打乱,再按 7:2:1 切。random.seed(42)是后悔药,保证你明天再跑一次结果完全一样。参数上,比例可以根据数据量调,3000 张的话 7:2:1 比较稳,测试集留 300 张够评估了。代码里那个else分支打印缺失标签的图片,跑完一定要看输出,有图无标签的样本混进训练集就是纯噪声。
3.3 划分后必须做的两项检查
第一,确认没有图片同时出现在两个 split 里。第二,确认每个 split 的类别分布大致均衡。第二项尤其重要,如果验证集里某个类别一个框都没有,那这个类别的 mAP 就是 0,白评估。
# 检查各 split 的类别分布 for split in ["train", "val", "test"]: counter = Counter() label_path = f"{out_dir}/{split}/labels" for txt in os.listdir(label_path): with open(os.path.join(label_path, txt)) as f: for line in f: counter[int(line.split()[0])] += 1 print(split, dict(sorted(counter.items())))如果发现 val 里某类为 0,常见做法是手动从 train 里挪几张过去,或者改用分层抽样(stratified split)。交通标志数据集里限速类通常占大头,警告类偏少,分层抽样能明显改善小类评估的稳定性。
4. 环境搭建与训练:Linux 和 Windows 两条路
资源里给了两套环境搭建和训练教程,Linux 和 Windows 各一份。别跳过环境直接跑训练,CUDA 版本和 PyTorch 对不上是新手最常见的翻车点。
4.1 Linux 下的环境搭建要点
Ubuntu 环境下,先确认显卡驱动和 CUDA 版本。教程里给的是标准流程,我补充几个容易忽略的点。
# 查看显卡和驱动 nvidia-smi # 确认 CUDA 版本(驱动支持的版本,不是已安装的) nvcc --version # 创建虚拟环境,避免污染系统 Python conda create -n yolo python=3.9 -y conda activate yolo # 安装 PyTorch,注意 cu118 要和你的驱动匹配 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 验证 GPU 可用 python -c "import torch; print(torch.cuda.is_available())"参数说明:python=3.9是 YOLOv5/v8 兼容性最好的版本,3.10 以上偶尔有依赖冲突。cu118对应 CUDA 11.8,如果你的驱动只支持到 11.6,就换成cu116。最后那句验证必须输出True,输出False说明 PyTorch 装成了 CPU 版,训练会慢到无法接受。
4.2 Windows 下的差异与注意
Windows 教程的流程类似,但有两个坑。一是路径分隔符,脚本里写/还是\要统一,混用会报FileNotFoundError。二是num_workers参数,Windows 下多进程 DataLoader 容易卡死,建议设成 0 或 2,Linux 下可以设 8。
# Windows 下训练时建议加的参数 python train.py --data data.yaml --weights yolov5s.pt --epochs 100 --batch-size 16 --workers 2--workers 2就是针对 Windows 的妥协,牺牲一点数据加载速度换稳定。--batch-size 16是 8G 显存的安全值,显存够可以往上加,但要注意 batch 变了学习率最好同步调。
4.3 用案例配置改自己的数据集
教程里给的是案例配置,改成自己的数据集主要动三个地方:data.yaml里的train、val路径和nc、names;模型配置里的类别数;以及预训练权重的选择。
# data.yaml 示例 train: ../dataset_split/train/images val: ../dataset_split/val/images test: ../dataset_split/test/images nc: 4 # 类别数,按你的实际类别改 names: ['speed_limit', 'prohibition', 'warning', 'indication']nc必须和names长度一致,且顺序要和标注时的class_id对应。改完先跑 1 个 epoch 看看 loss 有没有正常下降,别一上来就 100 epoch,浪费时间。
5. 避坑与排查:那些让 mAP 莫名掉点的细节
这一章是我踩过的坑合集,每条都按"现象 → 原因 → 解决"写,照着排查能省不少时间。
5.1 训练 loss 正常但 mAP 极低
现象:训练 loss 稳定下降,但验证 mAP 一直在 0.1 以下。原因:data.yaml里的names顺序和标注class_id不一致,模型学的是错位的类别。解决:跑一遍第 2.3 节的类别统计脚本,把names按class_id顺序重排,重新训练。
5.2 图片和标签文件名不匹配
现象:训练时报No labels found或者大量图片被跳过。原因:图片是001.jpg,标签是001.txt,但中间多了空格或大小写不一致。解决:写个批量重命名脚本统一小写并去空格,或者用os.path.splitext严格配对后再划分。
5.3 验证集指标虚高
现象:验证集 mAP 0.9,换一批新图测试掉到 0.5。原因:划分时没打乱,或者同一段视频抽帧的图片同时进了 train 和 val,数据泄漏。解决:划分前先按场景或视频来源分组,组内不跨 split,再在组间随机分。
5.4 显存溢出(OOM)
现象:训练几个 batch 后报CUDA out of memory。原因:batch-size太大,或者图片分辨率没统一,个别大图撑爆显存。解决:先把batch-size降到 8 试,同时检查图片尺寸,训练前统一 resize 到 640。YOLO 默认会 letterbox,但原图过大仍会占内存。
5.5 中文路径导致读取失败
现象:Windows 下训练报编码错误或找不到文件。原因:数据集放在中文命名的文件夹里,OpenCV 或 PIL 读取时编码不兼容。解决:把数据集移到纯英文路径下,这是最省事的办法,别去改源码里的编码。
6. 进阶技巧:用 ONNX 导出验证数据集质量
训练完不是终点,导出 ONNX 做一次跨框架推理,能反过来验证数据集和模型是否真的可靠。这一步很多人跳过,但它是发现标注问题的最后一道关卡。
6.1 导出 ONNX 并检查输出维度
import torch # 加载训练好的模型 model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/train/exp/weights/best.pt') model.eval() # 导出 ONNX torch.onnx.export( model.model, torch.zeros(1, 3, 640, 640), "traffic_sign.onnx", opset_version=12, input_names=['images'], output_names=['output'], dynamic_axes={'images': {0: 'batch'}, 'output': {0: 'batch'}} ) print("ONNX 导出完成")opset_version=12是兼容性较好的选择,dynamic_axes让 batch 维度可变,方便部署时按需调整。导出后用onnxruntime跑一张测试图,对比 PyTorch 和 ONNX 的输出差异,如果框的位置差很多,说明导出有问题。
6.2 用 ONNX 推理反查漏标
把 ONNX 模型在测试集上跑一遍,挑出那些模型置信度很高但标注里没有对应框的图片。这些大概率是漏标样本。
import onnxruntime as ort import numpy as np import cv2 sess = ort.InferenceSession("traffic_sign.onnx") img = cv2.imread("test.jpg") img = cv2.resize(img, (640, 640)) img = img[:, :, ::-1].transpose(2, 0, 1) # BGR->RGB, HWC->CHW img = np.expand_dims(img, 0).astype(np.float32) / 255.0 outputs = sess.run(None, {'images': img}) preds = outputs[0] # 筛选高置信度预测 high_conf = preds[preds[:, 4] > 0.7] print(f"高置信度预测数:{len(high_conf)}")逻辑说明:preds[:, 4]是置信度列,阈值 0.7 是经验值,交通标志场景可以设高一点减少误报。把高置信度预测和标注框做 IoU 匹配,IoU 低于 0.3 且置信度高于 0.7 的,基本就是漏标。我一般会把这类图挑出来人工复核一遍,补标后重新训练,mAP 通常能再涨 2~3 个点。
6.3 一个我坚持了很久的习惯
从那以后我每次拿到新数据集,都强制走一遍"格式校验 → 类别统计 → 划分检查 → 导出 ONNX 反查"这四步,不管简介写得多漂亮。这套交通标志数据集三种格式齐备、脚本和教程都给了,底子是不错的,但数据质量最终得靠自己的流程兜底。希望帮到你。
本文还有配套的精品资源,点击获取