简介:这是一份面向深度学习与计算机视觉方向的实拍交通标志已标注数据集,适合使用YOLO系列算法开展目标检测训练与验证的开发者、学生及算法工程师。数据集聚焦停止、提示、等待三类常见交通标志,图像以真实道路场景实拍为主,标注精度较高,作者实测在100轮训练后检测精度可达98%,可用于课程设计、毕业项目或算法对比实验。压缩包共1643个文件,包含547张jpg原图、547个xml标注文件与548个txt标注文件,并附带1个cache缓存文件,txt与xml两种格式均已转换完毕,可按需直接接入不同训练框架,整体约705.56MB。目前已有1055人学习下载,资源同时提供两种标注格式,省去自行转换与整理的环节,便于快速搭建训练流程、复现检测效果并验证模型精度。
1. 550 张实拍交通标志数据集:txt 与 xml 双版本到底怎么选
手上有个红绿灯路口的小项目,客户催着要 demo,模型精度还得能看。这时候最缺的不是网络结构,而是一份标注干净、格式对得上、拿来就能跑的数据。这份实拍交通标志数据集一共 550 张,覆盖停止、提示、等待三类标志,最省事的地方在于 txt 和 xml 两套标注都给你转好了,YOLO 系直接吃 txt,想用 VOC 那套流程就走 xml,不用自己再写转换脚本。它适合两类人:一类是刚接触目标检测、想跑通训练闭环的新手,另一类是手上有现成框架、只缺一份小规模实拍数据做验证的从业者。550 张不算大,但胜在是实拍场景,光照、角度、遮挡都带一点,比纯合成图更接近真实路况,这也是它检测精度能上来的原因。
2. 数据集结构与标注格式:txt 和 xml 分别对应什么
2.1 目录组织与文件命名
拿到压缩包解压后,常见的组织方式是图片放一个目录、标注放另一个目录,或者图片和同名标注并排。从项目正文里那串文件名能看出来,图片是03015.jpg、03430.jpg这种纯数字命名,标注文件跟图片同名,只是后缀不同——txt 版就是03015.txt,xml 版就是03015.xml。这种同名对应关系是后面所有脚本能跑通的前提,一旦名字对不上,训练时就会报找不到标签。
我一般拿到数据集先干一件事:确认图片数量和标注数量是否一致。550 张图,理论上应该有 550 个 txt 或 550 个 xml。数量对不上,要么是漏标,要么是转换时丢了文件,这两种情况都会让训练结果莫名其妙地差。
# 统计图片数量 ls images/*.jpg | wc -l # 统计 txt 标注数量 ls labels/*.txt | wc -l # 统计 xml 标注数量 ls annotations/*.xml | wc -l这三条命令跑完,三个数字应该都是 550。如果 txt 和 xml 数量不一致,说明其中一套转换有问题,优先用数量对得上的那套。labels.cache这个文件是 YOLO 训练时自动生成的缓存,里面存的是标签路径和形状信息,第一次训练会自动重建,不用手动管它,但如果换了数据集路径,最好把它删掉重新生成,否则可能读到旧缓存。
2.2 txt 格式:YOLO 系的归一化坐标
txt 版本是给 YOLO 系列用的,每张图对应一个 txt,每行代表一个目标,格式是:
类别索引 中心x 中心y 宽度 高度这四个坐标值都是归一化到 0 到 1 之间的浮点数,不是像素值。这一点是新手最容易翻车的地方——直接把像素坐标填进去,训练时 loss 会大得离谱,模型根本学不动。归一化的意思是:中心 x 除以图片宽度,中心 y 除以图片高度,宽度除以图片宽度,高度除以图片高度。
# 读取一个 txt 标注,验证坐标是否在 0-1 之间 with open('labels/03015.txt', 'r') as f: for line in f: cls, cx, cy, w, h = line.strip().split() cx, cy, w, h = map(float, (cx, cy, w, h)) # 正常情况四个值都应该在 0 到 1 之间 assert 0 <= cx <= 1 and 0 <= cy <= 1, f"坐标越界: {line}" assert 0 < w <= 1 and 0 < h <= 1, f"宽高异常: {line}" print(f"类别 {cls}, 中心 ({cx:.3f}, {cy:.3f}), 尺寸 ({w:.3f}, {h:.3f})")这段脚本的作用是快速体检。如果断言失败,说明这份 txt 不是标准 YOLO 格式,可能是像素坐标没归一化,或者坐标写成了左上角加右下角。类别索引从 0 开始,停止、提示、等待三类分别对应 0、1、2,具体哪个是哪个,得看数据集附带的类别说明文件,或者自己打开几张图对照标注确认,别想当然。
2.3 xml 格式:VOC 风格的绝对坐标
xml 版本走的是 Pascal VOC 那套,每个 xml 文件描述一张图,里面包含图片尺寸和若干目标框。关键字段是size里的width、height,以及每个object里的name和bndbox。bndbox里的xmin、ymin、xmax、ymax是像素绝对坐标,左上角和右下角。
import xml.etree.ElementTree as ET tree = ET.parse('annotations/03015.xml') root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) print(f"图片尺寸: {img_w}x{img_h}") for obj in root.findall('object'): name = obj.find('name').text box = obj.find('bndbox') xmin = int(box.find('xmin').text) ymin = int(box.find('ymin').text) xmax = int(box.find('xmax').text) ymax = int(box.find('ymax').text) print(f"类别 {name}, 框 ({xmin},{ymin})-({xmax},{ymax})")xml 的好处是可读性强,用浏览器或文本编辑器直接打开就能看,改起来也直观。缺点是文件体积比 txt 大不少,550 张图的 xml 加起来可能比图片本身还占地方。如果你用的是 YOLOv5 或 YOLOv8,训练时读的是 txt,xml 只是留作备份或给其他框架用。两套都留着的好处是,哪天想换框架,不用回头重新标。
3. 用这份数据跑 YOLO 训练:从配置到出结果
3.1 目录结构整理与 data.yaml 配置
YOLO 训练对目录结构有约定,常见做法是分成images/train、images/val、labels/train、labels/val四个目录。550 张按 8:2 切,训练集 440 张,验证集 110 张。切分脚本要保证图片和标注同步移动,不能只挪图片不挪标签。
import os, shutil, random random.seed(42) # 固定随机种子,保证每次切分结果一致 img_dir = 'images' lbl_dir = 'labels' pairs = [(f, f.replace('.jpg', '.txt')) for f in os.listdir(img_dir) if f.endswith('.jpg')] random.shuffle(pairs) split = int(len(pairs) * 0.8) for phase, subset in [('train', pairs[:split]), ('val', pairs[split:])]: os.makedirs(f'images/{phase}', exist_ok=True) os.makedirs(f'labels/{phase}', exist_ok=True) for img, lbl in subset: shutil.copy(os.path.join(img_dir, img), f'images/{phase}/{img}') shutil.copy(os.path.join(lbl_dir, lbl), f'labels/{phase}/{lbl}') print(f"{phase}: {len(subset)} 张")random.seed(42)这行别省,不固定种子的话每次切分结果不同,实验没法复现。切完之后检查一下images/train和labels/train里的文件名是否一一对应,数量是否都是 440。
然后是data.yaml,这是 YOLO 训练的数据描述文件:
path: ./traffic_sign_data train: images/train val: images/val nc: 3 names: ['stop', 'warning', 'wait']nc是类别数,这里是 3。names的顺序必须和 txt 里的类别索引严格对应,索引 0 对应names列表第一个元素,索引 1 对应第二个,以此类推。顺序搞反了,模型会把停止标志认成等待,精度再高也没用。path写数据集根目录,train和val写相对路径。
3.2 训练命令与关键参数
YOLOv8 的训练命令很简洁:
yolo detect train \ data=traffic_sign_data/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/traffic \ name=exp1model=yolov8n.pt用的是 nano 版本,550 张图这个量级,nano 或 small 就够了,上大模型容易过拟合。epochs=100和项目里说的 100 轮对得上,patience=20表示 20 轮没提升就早停,省时间。imgsz=640是输入尺寸,如果原图分辨率远大于 640,训练时会自动缩放。batch=16看显存,8G 显存跑 nano 加 640 尺寸,16 一般没问题,爆显存就降到 8。
训练过程中重点看两个指标:mAP50和mAP50-95。前者是 IoU 阈值 0.5 时的平均精度,后者是 0.5 到 0.95 多个阈值下的平均,后者更严格。项目里说的 98% 精度,大概率指的是mAP50。如果mAP50到 98 但mAP50-95只有六七十,说明框的位置还不够准,但对分类和粗略定位来说够用了。
3.3 验证与推理:确认模型真的能用
训练完在runs/traffic/exp1/weights/下会有best.pt和last.pt,best.pt是验证集表现最好的那轮,推理用它。
yolo detect predict \ model=runs/traffic/exp1/weights/best.pt \ source=test_images/ \ conf=0.25 \ save=Trueconf=0.25是置信度阈值,低于这个值的框不显示。交通标志场景建议先设 0.25 看效果,如果漏检多就降到 0.1,误检多就升到 0.4。推理结果会存到runs/detect/predict/下,打开图看框的位置和类别对不对。
验证集上的混淆矩阵也值得看,在runs/traffic/exp1/下有个confusion_matrix.png。如果停止和等待两类互相混淆严重,说明这两类在视觉上太像,或者标注时边界没统一,得回头检查标注。
4. 避坑与排查:标注和训练里最容易翻车的几件事
4.1 现象:训练 loss 一直不降,mAP 接近 0
原因通常是标注格式不对。最常见的是 txt 里坐标没归一化,还是像素值,或者类别索引从 1 开始而不是 0。YOLO 要求类别索引从 0 开始,如果标注工具默认从 1 开始,就会全部错位。
解决:跑一遍 2.2 节那个断言脚本,确认坐标都在 0 到 1 之间。再看类别索引的最大值,如果出现 3 而nc是 3,说明索引越界了,要么改标注,要么把nc改成 4 但这样会多一个空类。正确做法是把索引统一减 1。
4.2 现象:训练报错「找不到标签文件」
原因:图片和标注文件名不一致,或者目录结构不符合 YOLO 约定。YOLO 找标签的逻辑是把图片路径里的images替换成labels,后缀换成.txt。如果图片叫03015.jpg,标签必须叫03015.txt,放在对应的labels/train下。
解决:用脚本批量检查对应关系。
import os for phase in ['train', 'val']: imgs = {f.replace('.jpg', '') for f in os.listdir(f'images/{phase}')} lbls = {f.replace('.txt', '') for f in os.listdir(f'labels/{phase}')} missing = imgs - lbls if missing: print(f"{phase} 缺少标签: {missing}") else: print(f"{phase} 对应正常")4.3 现象:labels.cache导致换了数据集后结果异常
原因:YOLO 第一次训练会在标签目录下生成labels.cache,缓存标签路径和形状。如果换了数据集但没删缓存,训练时读的还是旧缓存里的路径,轻则报错,重则静默用错标签。
解决:每次换数据集或改了标签文件,手动删掉所有labels.cache,让它重新生成。这个文件是黑匣子,出问题时第一反应就是删它。
4.4 现象:验证集精度很高,但实际图片检测效果差
原因:550 张图切分时如果按顺序切而不是随机切,可能出现训练集和验证集场景高度相似,验证集精度虚高。另外,如果实拍图里某些光照条件只在训练集出现,验证集没有,模型泛化就差。
解决:切分时固定随机种子并打乱,确保两个集合场景分布均匀。实际部署前,找几张完全没参与训练的图测一下,这才是真实水平。项目里说的 98% 是在验证集上,换到全新场景可能会掉几个点,心里要有数。
4.5 现象:xml 转 txt 后框位置偏移
原因:xml 里bndbox是绝对坐标,转 YOLO 格式时要先归一化。常见错误是用了错误的图片宽高,比如 xml 里写的尺寸和实际图片尺寸不一致,或者转换时把xmax和ymax当成了宽高而不是右下角坐标。
解决:转换时以实际图片的尺寸为准,用 PIL 或 OpenCV 读图拿宽高,不要信 xml 里的size字段,有些标注工具写进去的尺寸是错的。转换公式是:cx = (xmin + xmax) / 2 / img_w,w = (xmax - xmin) / img_w,y 方向同理。
5. 把 550 张用到极致:小数据集的增强与迁移技巧
550 张对深度学习来说不算多,但交通标志这个任务本身类别少、目标特征明显,用对方法完全能出可用模型。我一般会做两件事:一是开 YOLO 自带的增强,二是用预训练权重做迁移。
YOLOv8 默认就开了 mosaic、HSV 抖动、随机翻转这些增强。mosaic 是把四张图拼成一张,变相增加 batch 内的多样性,对小数据集特别有用。但要注意,交通标志里有些标志是有方向性的,比如某些提示标志翻转后语义就变了,所以flipud(上下翻转)建议关掉,fliplr(左右翻转)看类别,停止标志左右翻转还是停止,问题不大,但如果有文字类标志就得谨慎。
# 在 data.yaml 同级加一个增强配置,或在训练命令里覆盖 hsv_h: 0.015 # 色调抖动,模拟不同光照 hsv_s: 0.7 # 饱和度抖动 hsv_v: 0.4 # 亮度抖动 degrees: 10 # 随机旋转角度,交通标志一般不会大角度倾斜 translate: 0.1 # 平移 scale: 0.5 # 缩放 flipud: 0.0 # 关掉上下翻转 fliplr: 0.5 # 左右翻转保留 mosaic: 1.0 # 开启 mosaic迁移学习这块,yolov8n.pt本身就是在 COCO 上预训练过的,已经学到了边缘、纹理这些底层特征,拿来微调比从零训练收敛快得多。100 轮能到 98%,预训练权重功不可没。如果换成从零初始化,同样 100 轮大概率到不了这个数。
还有一个技巧是冻结 backbone 先训几轮。前 10 轮把 backbone 冻住,只训检测头,让检测头先适应新类别,然后再解冻全量微调。这样能防止预训练权重被小数据集带偏。
# 前 10 轮冻结 backbone yolo detect train data=traffic_sign_data/data.yaml model=yolov8n.pt \ epochs=10 freeze=10 lr0=0.001 name=warmup # 再解冻全量训练 yolo detect train data=traffic_sign_data/data.yaml \ model=runs/detect/warmup/weights/last.pt \ epochs=90 lr0=0.01 name=finetune验证模型是否真的学到东西,除了看 mAP,我习惯抽几张验证集的图,把预测框和真实框画在一起对比。如果预测框和真实框贴合度高,类别也对,那才是真稳。只看数字容易被虚高的验证集精度骗到。
从那以后我每次拿到新数据集,都强制先跑一遍数量核对和坐标断言,再切分、再训练,这三步不走完不开工。希望帮到你。
本文还有配套的精品资源,点击获取