简介:这份YOLO交通标志检测数据集面向计算机视觉学习者、目标检测项目开发者与自动驾驶感知方向的研究人员,用于训练和验证交通标志识别模型,覆盖直行、左转、右转、禁止停车、鸣笛等常见道路标志类别。压缩包共139个文件,包含46张jpg原始图像、46个xml标注文件与47个txt标签文件,同时提供VOC与YOLO两种标签格式,方便直接接入不同检测框架,整体约218.61MB。数据集按0至9共10个类别组织,涵盖Right、NO-Right、Parking、STOP、Left、NO-Straight、Honking、NO-Left、NO-Parking、Straight,类别命名清晰,便于快速划分训练集与验证集。目前已有988人学习下载,适合作为课程设计、毕业设计或算法对比实验的数据基础,读者可据此完成数据加载、格式转换、模型训练与指标评估等完整流程,减少自行采集与标注的成本。
1. 从一份只有 10 张图的 YOLO 交通标志数据集说起
如果你正在找一份能直接跑通 YOLO 交通标志检测流程的小型数据集,dataset.rar这个包值得先看一眼。它包含 10 张实拍道路场景图(1.jpg、5.jpg、7.jpg、8.jpg、23.jpg、26.jpg、36.jpg、37.jpg、41.jpg、45.jpg),每张图都配了 xml 和 txt 两种格式的标注,覆盖 Right、NO-Right、Parking、STOP、Left、NO-Straight、Honking、NO-Left、NO-Parking、Straight 共 10 个类别。这个规模不大,但恰好够用来验证 YOLO 训练链路是否通畅——从数据加载、标签解析、类别映射到 loss 收敛,全流程都能跑一遍。
它适合两类人:一是刚接触 YOLO 目标检测、想先跑通一个完整训练流程再上大规模数据集的开发者;二是需要快速验证某个 YOLO 改进点(比如换损失函数、调 anchor 策略)是否有效的研究者。10 张图不会让你等太久,但足以暴露标签格式、类别索引、数据增强配置里的常见问题。下面按「资源结构 → 标签转换 → 训练配置 → 避坑 → 验证技巧」的顺序拆开讲。
2. 拆开 dataset.rar:目录结构、标签格式与类别映射
2.1 压缩包里的文件组织与命名规律
拿到dataset.rar后先解压,你会看到图片和标注文件混在一起,没有按 train/val 分目录。常见做法是手动建images/和labels/两个文件夹,把 jpg 和 txt 分别放进去,xml 单独存一份备用。文件名是纯数字编号,没有前缀,这意味着你在写数据加载脚本时不能靠文件名区分图片和标签,必须靠扩展名过滤。
图片编号不连续(1、5、7、8、23、26、36、37、41、45),说明这是从更大数据集中抽样出来的子集。这种编号方式有个隐患:如果你用glob按文件名排序后直接切分训练集和验证集,编号跳跃会导致某些场景分布不均。我一般会先统计每张图的类别分布,再决定怎么切。
xml 文件是 Pascal VOC 格式,txt 是 YOLO 格式。两者内容对应同一张图,但坐标表示方式不同。xml 里是绝对像素坐标(xmin, ymin, xmax, ymax),txt 里是归一化后的(class_id, x_center, y_center, width, height)。如果你只打算用 YOLO 训练,txt 可以直接用;如果要做数据清洗或可视化校验,xml 更方便读。
2.2 十个类别的索引与语义对应
摘要里给出的类别映射是:
| class_id | 类别名 | 中文含义 |
|---|---|---|
| 0 | Right | 右转 |
| 1 | NO-Right | 禁止右转 |
| 2 | Parking | 停车 |
| 3 | STOP | 停车让行 |
| 4 | Left | 左转 |
| 5 | NO-Straight | 禁止直行 |
| 6 | Honking | 鸣笛 |
| 7 | NO-Left | 禁止左转 |
| 8 | NO-Parking | 禁止停车 |
| 9 | Straight | 直行 |
这个映射顺序不能改。YOLO 训练时,txt 里的第一个数字就是 class_id,如果你在data.yaml里把names列表顺序写错,模型学到的就是错位的类别。比如把NO-Right和Right调换,模型会把禁止右转识别成右转,这种错误在混淆矩阵里表现为两类互相误判,但 loss 曲线可能看起来很正常,属于典型的「玄学翻车」。
另外注意NO-前缀的类别有 5 个(NO-Right、NO-Straight、NO-Left、NO-Parking、NO-Parking),它们和对应的正向类别在视觉上高度相似——都是圆形或方形标志,主要区别在红色斜杠或颜色反转。10 张图里如果某些类别只出现一两次,模型很难学到区分特征。我建议先统计每个类别的实例数,如果某个类别少于 3 个实例,要么补充数据,要么在训练时降低该类别的 loss 权重。
2.3 用脚本快速校验标签一致性
在训练之前,先跑一段校验脚本,确认每张图都有对应的 txt,且 txt 里的 class_id 在 0~9 之间、坐标在 0~1 之间。这一步能帮你提前发现标注遗漏或格式错误。
import os import glob img_dir = "images" label_dir = "labels" valid_classes = set(range(10)) for img_path in glob.glob(os.path.join(img_dir, "*.jpg")): base = os.path.splitext(os.path.basename(img_path))[0] label_path = os.path.join(label_dir, base + ".txt") if not os.path.exists(label_path): print(f"[缺失标签] {base}.jpg 没有对应的 txt") continue with open(label_path, "r") as f: for line_no, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: print(f"[格式错误] {base}.txt 第 {line_no} 行不是 5 列") continue cls_id = int(parts[0]) coords = list(map(float, parts[1:])) if cls_id not in valid_classes: print(f"[类别越界] {base}.txt 第 {line_no} 行 class_id={cls_id}") if any(c < 0 or c > 1 for c in coords): print(f"[坐标越界] {base}.txt 第 {line_no} 行坐标超出 0~1") print("校验完成")这段脚本做了三件事:检查图片和标签是否一一对应、检查每行是否恰好 5 列、检查 class_id 和归一化坐标是否在合法范围内。参数方面,img_dir和label_dir按你实际解压后的路径改;valid_classes固定为 0~9,如果你后续增删类别,这里要同步改。跑完如果没有输出任何错误行,说明标签格式基本可用。
3. 从 xml 到 YOLO txt:转换脚本与坐标归一化细节
3.1 为什么不能直接用 xml 训练 YOLO
YOLO 系列(v5/v8/v11)默认读取的是归一化 txt,不是 Pascal VOC xml。虽然有些第三方库支持直接解析 xml,但训练时的数据加载器会多一层转换开销,而且容易在collate_fn里出问题。常见做法是先把 xml 批量转成 txt,再让 YOLO 只读 txt。这样训练脚本干净,排查问题时也少一个变量。
转换的核心是坐标归一化:xml 里的xmin, ymin, xmax, ymax是像素绝对值,需要除以图片宽度和高度,再转成中心点加宽高的格式。公式如下:
x_center = (xmin + xmax) / 2 / img_widthy_center = (ymin + ymax) / 2 / img_heightwidth = (xmax - xmin) / img_widthheight = (ymax - ymin) / img_height
注意img_width和img_height必须从对应图片读取,不能硬编码。这 10 张图的分辨率可能不一致,硬编码会导致某些图的坐标全部偏移。
3.2 批量转换脚本与类别名到 id 的映射
下面这个脚本遍历 xml 文件夹,读取每张图的尺寸,解析所有object节点,按摘要里的类别顺序映射成 0~9,输出同名 txt。
import os import glob import xml.etree.ElementTree as ET from PIL import Image xml_dir = "annotations_xml" img_dir = "images" out_dir = "labels" os.makedirs(out_dir, exist_ok=True) # 类别名到 id 的映射,顺序必须和 data.yaml 里的 names 一致 class_map = { "Right": 0, "NO-Right": 1, "Parking": 2, "STOP": 3, "Left": 4, "NO-Straight": 5, "Honking": 6, "NO-Left": 7, "NO-Parking": 8, "Straight": 9 } for xml_path in glob.glob(os.path.join(xml_dir, "*.xml")): base = os.path.splitext(os.path.basename(xml_path))[0] img_path = os.path.join(img_dir, base + ".jpg") if not os.path.exists(img_path): print(f"[跳过] 找不到图片 {base}.jpg") continue with Image.open(img_path) as im: w, h = im.size tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in class_map: print(f"[未知类别] {base}.xml 中出现 {name}") continue cls_id = class_map[name] bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) xc = (xmin + xmax) / 2 / w yc = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(out_dir, base + ".txt"), "w") as f: f.write("\n".join(lines)) print("转换完成")关键参数说明:class_map的键必须和 xml 里<name>标签的内容完全一致,大小写敏感;xc/yc/bw/bh保留 6 位小数,YOLO 默认解析 float,精度足够;如果某张图没有目标,txt 会是空文件,YOLO 训练时会把空标签当作负样本,这是允许的,但 10 张图里如果空标签太多,模型会偏向预测背景。
3.3 转换后做一次可视化抽检
转换完不要直接开训,先抽 2~3 张图把 txt 画回原图,确认框的位置和类别都对。我一般用 OpenCV 快速画一下:
import cv2 import os img_path = "images/1.jpg" label_path = "labels/1.txt" img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path) as f: for line in f: cls_id, xc, yc, bw, bh = map(float, line.split()) x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls_id)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite("check_1.jpg", img)如果画出来的框明显偏移或类别数字对不上,回头检查class_map和图片尺寸读取。这一步花两分钟,能省掉后面几小时的排查。
4. 用这 10 张图跑通 YOLO 训练:data.yaml 与参数配置
4.1 data.yaml 的写法与路径陷阱
YOLO 训练需要一个data.yaml描述数据路径和类别。常见写法如下:
path: ./traffic_sign_dataset train: images val: images nc: 10 names: 0: Right 1: NO-Right 2: Parking 3: STOP 4: Left 5: NO-Straight 6: Honking 7: NO-Left 8: NO-Parking 9: Straight这里train和val都指向images,因为只有 10 张图,强行切分验证集会导致某一边样本过少。更合理的做法是训练时用全部图,验证时也用全部图,只看 loss 是否下降和预测框是否收敛,不追求验证指标。如果你一定要切分,建议按 7:3 分,但要注意每个类别在两边都至少出现一次。
路径陷阱:path是相对路径时,YOLO 会相对于data.yaml所在目录解析。如果你把data.yaml放在项目根目录,path写./traffic_sign_dataset,那images文件夹必须在traffic_sign_dataset/下面。很多人在这里翻车,报错No images found,其实就是路径层级不对。
4.2 训练命令与关键超参数
以 YOLOv8 为例,训练命令:
yolo detect train \ data=./traffic_sign_dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=4 \ lr0=0.001 \ patience=20 \ project=runs/traffic_sign \ name=exp1参数逐个说:model=yolov8n.pt用 nano 版本,10 张图不需要大模型;imgsz=640是默认输入尺寸,如果原图小于 640 会被放大,大于 640 会被缩小;batch=4是因为数据量小,batch 太大会导致一个 epoch 只有 2~3 次迭代,loss 震荡;lr0=0.001比默认的 0.01 小,小数据集上大学习率容易发散;patience=20表示 20 个 epoch 验证 loss 不降就早停。
如果你用的是 YOLOv5,命令类似,把yolo detect train换成python train.py,参数名基本一致。YOLOv11 也兼容这套写法。
4.3 训练过程中看什么指标
10 张图的训练,mAP 不会很高,但你要关注三个信号:box_loss是否持续下降、cls_loss是否在 20 个 epoch 内降到 0.5 以下、precision和recall是否至少有一边在上升。如果box_loss下降但cls_loss不降,说明坐标在学但类别分不开,常见原因是NO-类别的视觉差异太小,或者data.yaml里names顺序和 txt 里的 class_id 不一致。
如果box_loss和cls_loss都不降,先检查数据加载是否正常——把batch=1跑一个 epoch,看输出里有没有WARNING: No labels found。这个警告出现时,说明 txt 路径或格式有问题,模型在学背景。
5. 避坑与排查:10 张图训练时最容易翻车的 5 个点
5.1 现象:训练启动后 loss 一直是 nan
原因:txt 里有坐标超出 0~1 范围,或者某行只有 4 列导致解析出 nan。10 张图里只要有一行格式错误,整个 batch 的 loss 就可能被污染。
解决:回到 2.3 的校验脚本,把坐标越界和列数不对的行全部打印出来,手动修正或删除。如果不想删,可以把越界坐标裁剪到 0~1 之间。
5.2 现象:模型把所有框都预测成同一类
原因:data.yaml里的names顺序和 txt 里的 class_id 不对应。比如 txt 里0是 Right,但names里0写成了 NO-Right,模型学到的就是错位映射。
解决:把data.yaml的names和摘要里的类别表逐行对齐,确认 0~9 每个 id 对应的英文名完全一致。改完后重新训练,不要接着之前的 checkpoint 继续。
5.3 现象:验证时 mAP 为 0,但 loss 在降
原因:val路径指向的图片和train不是同一批,或者验证集里没有标签文件。10 张图如果按 7:3 切,验证集只有 3 张,其中某张可能没有目标,导致 mAP 计算时除零。
解决:小数据集不建议切验证集,直接把val指向train同一目录。或者用val: images但接受 mAP 波动,重点看预测框的可视化结果。
5.4 现象:训练到一半报 CUDA out of memory
原因:imgsz=640加上batch=4在某些显存小的卡上会爆。10 张图虽然少,但 YOLO 的中间特征图占显存和图片数量无关,和输入尺寸、模型宽度有关。
解决:把imgsz降到 416 或 320,或者把batch降到 2。如果还爆,换yolov8n以下的模型,或者用 CPU 训练(速度慢但能跑通)。
5.5 现象:预测框位置对但类别置信度极低
原因:某些类别在 10 张图里只出现 1 次,模型没有足够样本学习该类别的特征分布。NO-系列类别尤其容易这样。
解决:要么补充数据,要么在训练时用cls=0.5降低分类 loss 权重,让模型更关注框回归。另一种做法是合并相似类别,比如把NO-Right和Right合并成Right,但这样会丢失细粒度信息,只适合做流程验证。
6. 小数据集的进阶用法:用这 10 张图做增强与迁移验证
10 张图直接训练,模型泛化能力有限,但你可以把它当成一个「探针」,用来验证增强策略或迁移学习是否有效。我一般会做两件事:一是用离线增强把 10 张扩到 100 张,二是用预训练模型做冻结训练,看 loss 下降速度。
离线增强用 Albumentations 比较顺手:
import albumentations as A import cv2 import os transform = A.Compose([ A.HorizontalFlip(p=0.5), A.RandomBrightnessContrast(p=0.3), A.Rotate(limit=15, p=0.3), A.Mosaic(p=0.2) # 如果版本支持 ], bbox_params=A.BboxParams(format="yolo", label_fields=["class_ids"])) img = cv2.imread("images/1.jpg") with open("labels/1.txt") as f: bboxes = [] class_ids = [] for line in f: cid, xc, yc, bw, bh = map(float, line.split()) bboxes.append([xc, yc, bw, bh]) class_ids.append(int(cid)) for i in range(10): augmented = transform(image=img, bboxes=bboxes, class_ids=class_ids) cv2.imwrite(f"aug/1_aug_{i}.jpg", augmented["image"]) with open(f"aug/1_aug_{i}.txt", "w") as f: for bbox, cid in zip(augmented["bboxes"], augmented["class_ids"]): f.write(f"{cid} {' '.join(f'{v:.6f}' for v in bbox)}\n")这段脚本对每张图做 10 次随机增强,生成 100 张新图和新标签。注意BboxParams的format="yolo"表示输入输出都是归一化中心点格式,和你的 txt 一致。增强后的图要重新检查一遍框是否还在合理位置,尤其是旋转增强后,框可能超出边界。
迁移验证则是用yolov8n.pt的预训练权重,先冻结 backbone 训练 20 个 epoch,再解冻全部训练 30 个 epoch。对比两次的box_loss下降曲线,如果冻结阶段 loss 就降得很快,说明预训练特征对交通标志有效;如果解冻后 loss 反而上升,说明小数据集上全量微调容易过拟合,需要加 weight decay 或 dropout。
从那以后我每次拿到小数据集,都会先跑一遍增强前后的 loss 对比,再决定要不要扩数据。这个习惯帮我省掉了很多「训了半天发现数据不够」的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取