资讯动态

交通标志识别数据集详解:从ImageFolder加载到YOLO迁移训练

发布时间:2026/9/28 3:21:08 来源:尧图企业网站定制
简介交通标志物图像分类数据集包含训练集、验证集、测试集面向图像分类、YOLOv5分类任务及交通物流场景识别的学习者与开发者。数据按文件夹整理43个类别覆盖红绿灯、限速、左右转等常见交通标志可直接用ImageFolder加载无需额外预处理。资源包共2000个文件以1999张jpg图像为主另含1个json中文字典文件压缩后约58.4MB整体结构与主流图像分类任务规范一致train/val/test划分清晰。已有333人浏览学习。数据集规模完整训练集31374张、验证集7835张、测试集7835张总量约60MB适合用于模型训练与效果验证。json文件提供43类交通标志物的中文名称映射便于标注可视化和结果分析下载后按原始目录放置即可使用。1. 交通标志图像分类43 类 4.7 万张目录排好直接开训交通标志识别是图像分类里最适合直接拿现成数据集来跑的场景类别固定、拍摄环境相对单一、标注清晰。这套交通标志物图像分类数据集共 43 个类别覆盖红绿灯、限速牌、左右转、禁止通行等路面常见标牌训练集 31,374 张验证集和测试集各 7,835 张合计约 4.7 万张打包后约 60MB。最省事的地方在于目录已按 ImageFolder 约定排好data 下分 train、val、test每个子文件夹一类不用写转换脚本PyTorch 三行代码就能进训练循环。想做自动驾驶视觉的毕业设计或验证 yolov5、yolov8 分类流程都可直接拿它当输入。2. 目录结构剖析train/val/test 按类分文件夹ImageFolder 的天然输入2.1 三层目录布局为什么“按文件夹保存”就是省掉了预处理拿到解压后的数据第一眼看到的是 data 目录下三个子目录train、val、test。每个子目录里面不是散落的图片而是继续按类别分成子文件夹子文件夹名就是类别编号。比如 train 下有 00001 到 00043 共 43 个文件夹每个文件夹里是该类别的全部训练图片。val 和 test 的布局完全一致只是图片数量不同。data/ ├── train/ │ ├── 00001/ │ │ ├── 00001_00027.jpg │ │ ├── 00001_00028.jpg │ │ └── ... │ ├── 00002/ │ └── 00043/ ├── val/ │ ├── 00001/ │ └── ... └── test/ ├── 00001/ └── ...这种组织方式恰好就是 torchvision.datasets.ImageFolder 的标准输入格式。ImageFolder 的规则很直接root 目录下每个子目录视为一个类别子目录里的每张图片作为该类别的样本类别标签就是子目录名。设计者在保存数据时已经替我们把“打标签、归类、拆训练验证测试集”这一步做完了。对比一下用 CSV 标注或者 XML 标注的数据集那种还需要额外写一个 Dataset 子类去解析标注文件而这套数据连 transform 都不用改就能直接喂给模型。我一般会先跑一个快速检查确认目录层数和类别数对得上再往下走find data/train -maxdepth 1 -type d | wc -l find data/train/00001 -type f | wc -l第一条命令统计 train 下一级子目录数量预期输出 44因为 data/train 自身也算一个第二条统计第一个类别的图片数量用来估算单类图片密度。如果类别数不是 43或者某个类目是空目录说明数据在拷贝或解压时已经损坏这时候再怎么调模型都救不回来。2.2 目录名到类别索引sorted 排序带来的 0 到 42ImageFolder 实例化时会扫描 root 下的子目录然后对目录名做排序生成 classes 列表和 class_to_idx 字典。classes 是按排序后的目录名得到的类别名称列表class_to_idx 是“目录名 → 索引”的映射。train_dataset.classes[0] 就是排在最前面的目录名train_dataset.class_to_idx[00001] 返回 0。这套数据的目录名是 00001 到 00043 的定长数字字符串。按字符串排序时00001 在最前、00043 在最后正好和数字顺序一致。也就是说class_to_idx[00001] 0class_to_idx[00043] 42不需要额外对齐。这个顺序就是模型输出层 43 个节点的顺序后面所有预测结果的 argmax 索引都要靠这个固定顺序还原。from torchvision import datasets train_dataset datasets.ImageFolder(root./data/train) assert len(train_dataset.classes) 43 assert train_dataset.class_to_idx[00001] 0 assert train_dataset.class_to_idx[00043] 42这三条断言建议在每个项目的开头都跑一遍。一旦数据的目录顺序发生变化比如有人把某个文件夹改名成 class_01排序结果就会改变class_to_idx 的映射也会跟着变最终导致训练好的模型在推理时标签错位。这种错误在训练时完全看不出来因为训练用的标签也是从同一个映射生成的错位只在部署时暴露。json 文件里的 43 个中文字典键恰好也是 00001 到 00043 这套编号。我拿到数据后会先把 json 的 keys 排序和 train_dataset.classes 做一次比对确认两边完全一致。这样就能保证“预测索引 → 目录名 → 中文名”整条链路在同一次排序语义下闭环。2.3 数据量分配31,374 / 7,835 / 7,835 的拆分与体积估算从数量上看train 31,374 张、val 7,835 张、test 7,835 张合计约 47,044 张。按 43 类平均计算每个类别训练图约 730 张验证图和测试图各约 182 张。这个规模对小型分类任务足够尤其是限速牌、红绿灯这类高频标志单类图片数量会明显高于平均值。子集图片数占比每类平均train31,37466.7%约 730val7,83516.6%约 182test7,83516.6%约 182总大小 60MB除以约 4.7 万张图平均单张只有 1KB 出头。这个体量说明图片大概率已经做过统一缩放不可能是手机原图。实际加载时你会发现每张图分辨率不高这一点直接决定了后面的模型选型方向不需要超大输入尺寸也不需要大分辨率预训练模型Resize 到 224×224 就足够。val 和 test 数量完全一致说明设计者在拆分时用了同一次随机划分保障两组数据的分布一致。val 用于训练过程中选模型、调超参数test 用于训练结束后的最终评估两组数据不能互换使用。验证集在训练过程中已经被模型间接“看过”多次用它报告最终成绩会有虚高成分。3. 用 PyTorch ImageFolder 加载从文件路径到训练张量的完整链路3.1 实例化 ImageFolderroot 指向 train 而不是 data把 root 指向错误是最常见的翻车操作。ImageFolder 的 root 要指向“包含类别子目录”的那一层。这套数据集里 train 下面才是 00001 到 00043所以加载训练集时 root 应该是 ./data/train。如果指向 ./dataImageFolder 会认为 train、val、test 是三个类别类别数变成 3训练直接崩。from torchvision import datasets, transforms train_dataset datasets.ImageFolder( root./data/train, transformtransforms.ToTensor() ) print(类别数:, len(train_dataset.classes)) # 43 print(前5个类别:, train_dataset.classes[:5]) # [00001, 00002, ...] print(样本数:, len(train_dataset)) # 31374 print(单样本shape:, train_dataset[0][0].shape) # [3, H, W]这段代码输出四样关键信息类别数 43、前几个类名、样本总数 31,374、单张图的张量形状。前三个数字对上数据完整性就基本确认。第四行确认图片是 3 通道 RGB如果某些图是灰度图ImageFolder 读出来会是单通道卷积网络会报维度错误需要单独转成 RGB。val 和 test 的加载方式完全一样root 分别指向 ./data/val 和 ./data/test。三个数据集要分别实例化不要图省事把 train 和 val 合在一起再 split因为设计者已经按比例划分好重新划分反而会破坏分布一致性。实例化之后train_dataset.samples 是一个 list每个元素是 (图片路径, 类别索引)train_dataset.targets 是对应的索引列表这两个字段在后面的采样器和混淆矩阵里都会用到。注意val/test 的 root 分别指向 ./data/val 和 ./data/test不要为了省事统一指向 ./data。3.2 transforms 配置Resize、Normalize 与适度增强图片已经批量处理过分辨率不高transforms 不需要复杂。我常用的配置是这样的transform_train transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) transform_eval transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])Resize((224, 224)) 把所有图片统一成方形输入这是分类网络的硬性要求。为什么选 224 而不是 128大多数预训练模型默认输入是 224直接用可以少改网络结构而且 224 对这套小图数据也不会造成明显的计算压力。RandomHorizontalFlip 对交通标志是一个需要想清楚的增强。红色禁令标志翻转后语义不变但“向左转”和“向右转”这类箭头标志翻转后会变成另一个类别。从数据增强的角度看这相当于给镜像类互相补充训练样本让模型对拍摄角度更鲁棒实际效果是正向的。ColorJitter 调整亮度、对比度、饱和度模拟一天内不同光照条件对户外拍摄的标志识别非常值得加。Normalize 使用 ImageNet 的均值和标准差我们没有专门针对交通标志的预训练统计值直接用 ImageNet 的即可。val 和 test 的 transform 必须去掉随机增强只保留 Resize、ToTensor、Normalize。如果评估阶段还带着 RandomHorizontalFlip同一张图每次推理都会得到不同结果val 准确率忽高忽低看起来像玄学其实就是增强没关。另外不建议在 transform 里做 RandomCrop因为标志物可能被裁掉关键部分对分类来说风险大于收益。3.3 DataLoader 参数batch、shuffle、num_workers 的设置逻辑数据集本身没问题之后DataLoader 的参数决定了 GPU 能不能吃饱、CPU 会不会成为瓶颈。from torch.utils.data import DataLoader train_loader DataLoader( train_dataset, batch_size64, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue ) val_loader DataLoader( val_dataset, batch_size64, shuffleFalse, num_workers4, pin_memoryTrue )batch_size64 对 224×224 输入和单张消费级 GPU 是合理起点显存 8GB 以上可以试 128吃紧就降到 32。这套图片很小CPU 解码压力不大num_workers 设 4 到 8 都可以Windows 上如果频繁报 DataLoader worker 相关错误先降到 0 排查。pin_memoryTrue 减少 Host 到 Device 的拷贝时间纯 CPU 训练可以关掉。shuffleTrue 只用于训练集val/test 必须为 False。drop_lastTrue 用在训练集上是为了丢掉最后一个不足 64 张的 batch避免 BN 层统计量被小 batch 干扰。31,374 除以 64 余数很小丢掉对数据利用率的影响可忽略。val/test 不要开 drop_last否则评估总数会从 7,835 变少混淆矩阵和报告里的样本数对不上。加载完可以先迭代一个 batch 做 sanity check确认取出来的张量形状和标签范围正确images, labels next(iter(train_loader)) print(images.shape) # [64, 3, 224, 224] print(labels.min().item(), labels.max().item()) # 0 424. 迁移到 YOLOv5/v8 分类任务同一套目录直接交给 --data4.1 YOLO 分类数据集约定与 ImageFolder 的兼容性摘要里明确说明这套数据可以用作 YOLOv5 的分类数据集。YOLOv5 的 classify/train.py 接收的数据目录形式本质上就是 ImageFolder 风格数据集根目录下放 train 和 val每个子目录按类别划分。YOLOv8 的 ultralytics 框架在分类任务上也延续了同样的约定。这意味着不需要为 YOLO 重新组织文件。data 目录下有 train/val/testYOLOv5 训练时自动去找 data/train 和 data/val。有两个容易忽略的点一是 YOLO 分类不会因为你有 test 目录就自动做最终测试它只在训练集上训练、验证集上评估test 需要自己在训练后单独验证二是 YOLO 分类和 YOLO 检测的数据集格式完全不同检测需要每张图配一个标签文件分类只需目录结构这套数据是分类格式别拿去做检测训练。为什么这套数据适合分类而不是检测因为图片里基本是单个标志物居中标注框没有提供如果硬要做检测还需要重新标注成本远高于直接分类。用分类模型对单张图片输出 43 类概率已经能满足绝大多数交通标志识别场景。4.2 训练命令与参数从轻量模型开始跑先给 YOLOv5 的完整命令python classify/train.py \ --model resnet18 \ --data ./data \ --epochs 50 \ --imgsz 224 \ --batch 64 \ --device 0 \ --workers 4YOLOv8 环境对应命令是yolo classify train \ data./data \ modelyolov8n-cls.pt \ epochs50 \ imgsz224 \ batch64 \ device0指令中比较关键的参数是 --model 和 --data。data 直接指到 data 这一层让 YOLO 自己找 train 和 val。模型先选 resnet18 或 yolov8n-cls.pt 这种轻量网络因为前面算过平均单张图 1KB 出头、分辨率有限重模型收益小且容易过拟合。epochs 先给 50跑完看 val 准确率曲线再决定是否加。imgsz 224 和 torchvision 的 Resize 保持一致后面用 PyTorch 做推理时预处理不会被割裂。参数作用建议值--model骨干网络resnet18 / mobilenetv3--data数据集根目录./data--epochs训练轮数50--imgsz输入尺寸224--batch批大小64--device计算设备0 或 cpu--workers加载线程数4训练日志里除了 loss重点看 val accuracy 和 val loss。如果 val acc 在 20 轮前就收敛平了可以提前结束如果始终在低位徘徊先检查是不是标注和预测索引错位而不是立刻换大模型。YOLO 训练完会在 runs/classify 下生成 best.pt这个权重直接用即可但它的类别顺序同样是目录名的排序结果和前面 ImageFolder 的 classes 一致。注意test 目录不会被 YOLO 自动评估训练完成后需要自己跑推理脚本。4.3 json 中文字典训练时用不到推理时必须有数据集附带的 json 文件里是 43 种交通标志物的中文名称键是子目录名值是中文描述。训练时它派不上用场因为网络学习的是“图片 → 类别索引”的映射索引和目录名的对应关系由目录结构决定。但推理结果要给人看的时候没人愿意面对 00021 这种编号必须翻译成中文。加载方式有这样的坑import json # 文件名以实际压缩包里的为准 with open(class_dict.json, r, encodingutf-8) as f: label_cn json.load(f) for k, v in list(label_cn.items())[:5]: print(k, -, v)label_cn 的键是 00001 这种字符串和 train_dataset.classes 的顺序一一对应。推理时先对模型输出做 argmax 得到索引用 train_classes[idx] 还原目录名再查 json 得到中文名。完整链路是预测索引 → 目录名 → 中文标签。这三层转换建议在训练结束前写成公用函数测试和部署都复用它不要各写各的映射。5. 避坑指南处理这套交通标志数据集的五个实际问题5.1 文件名不等于标签00001_00027 的两个字段都容易误解现象看到 00001_00027.jpg以为 00001 是样本编号、00027 是类别标签或者反过来干脆在代码里 split(_) 把文件名拆出来当 target。原因这套数据集的标签由“图片所在父目录的名称”决定。文件名只是设计者自己的编号规则00001 大概率是类别编号00027 是该类别里的第 27 张图但 ImageFolder 完全不解析文件名它只读父目录。解决任何时候都不要用文件名获取标签。需要反查时用 os.path 取父目录名import os image_path ./data/train/00001/00001_00027.jpg true_label os.path.basename(os.path.dirname(image_path)) print(true_label) # 00001如果按_拆分字段当标签训练过程不会立即报错因为每个样本的 target 值都在 0-42 范围内但映射是乱的最终准确率会卡在一个上不去的值属于最难排查的一类问题。5.2 数量不平衡平均 730 张不代表每个类都有 730 张现象整体准确率 96%但某些类别召回率只有 80%。单独打印每一类的分类报告发现样本多的类指标很高样本少的类被压得很低。原因43 类平均每类 730 张训练图但平均数是掩盖分布的。高频标志会远超均值低频标志可能只有几百张。模型天然偏向样本多的类。解决用 WeightedRandomSampler 按类别数量反比采样提高稀有类别在训练中的出现次数from torch.utils.data import WeightedRandomSampler import numpy as np targets np.array(train_dataset.targets) class_counts np.bincount(targets) sample_weights 1.0 / class_counts[targets] sampler WeightedRandomSampler( weightssample_weights, num_sampleslen(sample_weights), replacementTrue ) train_loader DataLoader(train_dataset, batch_size64, samplersampler)sample_weights 长度等于训练集样本数每个样本的权重是“其所属类别样本数的倒数”。类别图片越少权重越高。replacementTrue 让稀有样本在一个 epoch 内可被重复抽到。代价是训练时间略有增加但类别间召回率会被明显拉平。注意用了 sampler 之后DataLoader 的 shuffle 参数必须设为 False两者冲突会报错。5.3 json 编码坑Windows 下不指定 utf-8 直接翻车现象在 Windows 上运行json.load(open(class_dict.json, r))报 UnicodeDecodeError或者中文全部变成乱码。原因json 文件是 UTF-8 编码Windows 默认文本编码是 GBK。open() 不传 encoding 时用的是系统默认编码读 UTF-8 文件字节对不上就报错。解决所有读取 json 的地方都显式传 encodingutf-8。Linux 上不传也能跑Windows 上必须传with open(class_dict.json, r, encodingutf-8) as f: class_dict json.load(f)这个问题不影响训练但会在写推理服务、交付报告时突然出现。如果你用的是 pandas.read_json同样要传 encoding 参数。5.4 val 和 test 数量相同不能拿 val 当 test 用现象val 和 test 都是 7,835 张觉得两个集差不多训练时一直在 val 上选最优 epoch最后报告成绩用的也是 val 数字全程没碰过 test。原因把 val 当 test 用图省事省一次推理。解决val 在训练过程中被反复用来挑模型最优 epoch 就是按 val 指标选的用它报结果等于用训练信息评估自己成绩虚高。test 的 7,835 张图从头到尾不参与任何决策只跑一次最终评估。训练完用这段代码在 test 上过一遍def evaluate(model, loader, devicecuda): model.eval() correct, total 0, 0 with torch.no_grad(): for images, labels in loader: images, labels images.to(device), labels.to(device) preds model(images).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) return correct / total test_score evaluate(model, test_loader, devicecuda) print(ftest acc: {test_score:.4f})test_loader 的构造方式和 val_loader 完全一样只是 root 换成 ./data/test。5.5 模型选型小图不要硬上大模型现象一上来选 ViT-B/16 或 EfficientNet-B7显存不够、batch 降到 8、训练时间翻倍准确率还不如 ResNet18。原因这套数据平均单张图只有 1KB 出头分辨率有限标志结构简单43 类分类复杂度不高。大模型擅长学习细粒度纹理这里没有那么多纹理可学参数量大反而更容易过拟合。解决从 ResNet18、MobileNetV3-Small、YOLOv8n-cls 起步先在 val 上看 top-1 和 top-5。如果 top-1 达到目标就收手。后续要部署到树莓派或 JetsonMobileNetV3 或 YOLOv8n-cls 的权重体积和推理延迟都比 ResNet 更合适。换模型时记得同步改预训练权重的下载地址和输入尺寸这几个参数一旦不一致训练 loss 会在初期出现诡异的震荡。6. 训完怎么验证与落地混淆矩阵、单张推理与中文标签回显6.1 用混淆矩阵定位容易混淆的类训练结束只看准确率不够。交通标志里有不少长得很像的类比如“直行”和“直行右转”这类组合标志。把 val 或 test 的预测结果和真实标签对齐画一张 43×43 混淆矩阵一眼能看到哪些类互相混from sklearn.metrics import confusion_matrix, classification_report import matplotlib.pyplot as plt cm confusion_matrix(all_targets, all_preds) report classification_report(all_targets, all_preds, target_namestrain_dataset.classes) print(report) plt.figure(figsize(12, 10)) plt.imshow(cm, interpolationnearest, cmapBlues) plt.colorbar() plt.tight_layout() plt.show()混淆矩阵出现明显的非对角线亮块说明那两个类在视觉上确实接近。这时候去补对应训练数据或者对难分类做二次判断比盲目换模型更有效。6.2 单张图片推理脚本写推理脚本时把训练时的 transform 原样复制过来不要重新发明一套from PIL import Image import torch import torchvision.transforms as transforms def predict_one(model, image_path, class_names, devicecuda): model.eval() tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) img Image.open(image_path).convert(RGB) x tf(img).unsqueeze(0).to(device) with torch.no_grad(): logits model(x) prob torch.softmax(logits, dim1)[0] pred_idx logits.argmax(dim1).item() return class_names[pred_idx], prob[pred_idx].item() folder_name, confidence predict_one( model, ./test/00021/00021_00100.jpg, train_dataset.classes) print(folder_name, confidence)class_names 用 train_dataset.classes 传入返回的就是目录名。注意 class_names 的顺序必须和训练时完全一致建议直接复用训练脚本里的 train_dataset不要重新构造一份。6.3 中文字典回显给人的最终输出目录名 00021 不是给人看的。对接业务时用 json 做一次回显with open(class_dict.json, r, encodingutf-8) as f: label_cn json.load(f) cn_name label_cn.get(folder_name, folder_name) print(f预测: {cn_name} ({folder_name}), 置信度: {confidence:.3f})这一层映射做成函数后训练、验证、部署三处共用。我从第一次做这类数据集起就吃过“文件名当标签”的亏后来养成习惯拿到任何新的分类数据集先花五分钟看目录数、数每个类图片数、核对 json 键与目录名是否一一对应再写训练代码。这套流程走完剩下的问题基本都在模型而不是数据上。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑