简介面向交通标志物识别任务提供一份开箱即用的图像分类数据集覆盖43类常见标志包括红绿灯、限速、左右转等可服务于计算机视觉课程设计、深度学习分类实验及yolov5分类模型训练。包体为zip压缩格式共2000个文件含1999张jpg图片和1个json标签字典压缩后大小约58.4MB数据总量适中便于下载与本地部署。数据按训练集、验证集、测试集三个目录科学划分训练集31,374张、验证集7,835张、测试集7,835张可直接用ImageFolder加载无需额外预处理显著降低数据准备门槛。json文件提供43种交通标志物的中文注释方便标签映射与调试图片命名规则统一便于后续扩展与索引。目前已有333人学习下载适合需要快速获取标准交通标志数据集的初学者或研究者可大幅节省数据集整理时间专注模型设计与优化。1. 交通标志物数据集三个子集才是真正的使用门槛很多拿到交通标志图像分类数据集的人第一反应都是把训练集塞进模型里开始跑等到验证的时候才发现准确率上不去、类别对不齐、图片里的小标志根本看不清。这类数据集的真正门槛从来不是模型选型而是训练集、验证集、测试集这三个子集的结构和组织方式。一个规范的数据集应该把 train、val、test 按类别目录分开每一张图都有明确的归属标注信息要么体现在文件夹名里要么附带一份 csv 或 json 文件。做自动驾驶感知、辅助驾驶课题的学生和工程师以及想入门图像分类算法的研究者都是这类数据集的主要使用人群。接下来的内容从数据集本身的结构开始拆一路讲到划分逻辑、训练参数和最常见的翻车点。2. 交通标志物图像分类数据集的结构目录即标注别自己发明格式2.1 为什么交通标志物适合做图像分类而不是目标检测交通标志物天然是平面物体基本垂直于地面拍摄角度通常在正前方不像行人、车辆那样有各种姿态和遮挡。分类任务只需要回答「这张图里是什么标志」而检测任务还要额外回答「标志在哪里、有多大」。当你的场景是自动驾驶路口识别、辅助驾驶限速提醒或者路侧设备拍照后对标志进行盘点时图像分类是最便宜的落地路径。不需要标注框不需要 anchor 调参分类网络直接吃整张图。但也有一个反向认识值得说清楚如果你的输入图里同时出现多个标志比如路口同时有限速牌、禁止左转牌和方向指示牌纯分类模型只能给整张图打一个标签这时候正确的做法是先用检测模型把每个标志裁出来再做分类。常见做法是用 YOLO 系列训练自己的数据集做检测然后把每个检测框裁剪成小图喂给分类网络两段式串联。这个标题里的数据集如果是为单目标标志设计的那么训练集里的每张图应该只包含一个主要标志如果混入了多目标图后面训练时就会在 loss 上表现得很奇怪。2.2 标准目录结构与标注信息映射拿到数据集后第一件事不是训练而是把目录结构完整看一遍。最常见的组织方式是 ImageFolder 风格traffic_sign_dataset/ ├── train/ │ ├── speed_limit_30/ │ │ ├── 00001.jpg │ │ ├── 00002.jpg │ │ └── ... │ ├── stop/ │ └── no_entry/ ├── val/ │ ├── speed_limit_30/ │ ├── stop/ │ └── no_entry/ └── test/ ├── speed_limit_30/ ├── stop/ └── no_entry/这种结构下每个子目录名就是类别标签PyTorch 的torchvision.datasets.ImageFolder可以直接读取文件夹排序后自动生成类别到索引的映射。Keras 的image_dataset_from_directory也可以直接用。这看起来简单但有三个细节容易忽略第一目录名的拼写前后必须完全一致Speed_Limit_30和speed_limit_30会被当成两个类别第二数据集里类别数量要提前确认有些数据集把other、unknown这种杂项也作为正式类别直接影响输出维度第三测试集如果也按类别分子目录说明这个数据集给了你 ground truth可以用来做最终评估如果测试集是纯图片不带标签那就是让用户自己提交结果到平台打分本地只能靠验证集估精度。标注信息的另一种常见形式是一份总的 csv 文件比如train.csv里每行是image_path, class_id, class_name。这种情况下需要写脚本把文件路径和标签读进来再按 label 分组构建数据集。个人经验是优先用目录结构因为人眼检查和 Debug 时最直观打开文件夹就能看到每一个类别的原始图片长什么样csv 结构虽然灵活但一旦里面混入了空格、逗号或路径拼接错误会在数据加载阶段反复报错。比较两个格式的差异可以看这两个点维度目录即标签CSV 映射读取方式ImageFolder 直接读需要自定义 Dataset 类类别名调整改文件夹名即可需要改 csv 内容出错点目录嵌套深度、命名一致性路径分隔符、编码、空值适合场景中小规模数据快速出结果大规模多标签或带属性数据2.3 图像尺寸、通道与文件格式的统一化预处理交通标志图像数据来源复杂有行车记录仪截图、手机拍摄、路侧摄像头抓拍甚至还有从街景地图里截出来的图。这导致图片尺寸差距很大小到 32×32 的裁剪图大到 1920×1080 的整帧图。在使用前必须统一尺寸和通道格式。常见的做法有两种一是直接 resize 到固定尺寸比如 224×224ImageNet 标准或者 32×32类 CIFAR10 规模二是先按短边等比缩放到某个尺寸再做中心裁剪。实际处理时建议使用下面的预处理脚本将统一缩放和格式转换写成一个可重复执行的函数import os from PIL import Image def preprocess_images(src_dir, dst_dir, target_size(224, 224)): os.makedirs(dst_dir, exist_okTrue) for root, _, files in os.walk(src_dir): for fname in files: if not fname.lower().endswith((.jpg, .jpeg, .png, .bmp)): continue src_path os.path.join(root, fname) rel_path os.path.relpath(src_path, src_dir) dst_path os.path.join(dst_dir, rel_path) os.makedirs(os.path.dirname(dst_path), exist_okTrue) img Image.open(src_path).convert(RGB) img img.resize(target_size, Image.BILINEAR) img.save(dst_path, quality95) print(done) preprocess_images(raw_images/, traffic_sign_dataset/train/)这段代码做的事情按顺序拆开看遍历源目录下所有图片文件只保留 jpg、png、bmp 三种格式统一转成 RGB 三通道避免出现灰度图混入导致训练时 channel 数不匹配然后 resize 到目标尺寸。有一个参数需要注意Image.BILINEAR是双线性插值缩放质量适中且速度比LANCZOS快很多。如果是放大图片用BILINEAR够用如果是缩小图片且原图包含密集细节推荐改用Image.LANCZOS抗锯齿效果更好小标志的边缘保留更完整。quality95这个参数只在保存 jpg 时生效png 会忽略它。日常用数据集时别忘了先抽样看一遍预处理后的效果尤其是 30×30 左右的限速标志缩小到 224 后里面的数字如果糊成一团就往大了留分辨率。数据集的图像尺寸选择本身也决定了后续微调预训练模型时输入层怎么接如果原数据集给的图已经是 32×32硬 resize 到 224×224 反而引入噪声不如直接用 64×64 起步的小网络速度还快很多。3. 训练集、验证集、测试集的划分逻辑分层抽样和固定种子才是正经事3.1 三个子集各干各的活别混着用训练集用来更新模型权重验证集用来选超参数和做 early stopping测试集只在全部实验做完了之后评估一次。这个原则在论文里人人会写实际操作里翻车的不少。常见错误是把验证集和测试集混为一谈反复用测试集调参最后测试集指标虚高模型一上真实场景就垮。另一类错误是验证集和训练集来自同一个视频序列的连续帧去重没做好导致验证集里大量图片和训练集几乎相同验证精度虚高。交通标志数据集如果原始采集来自连续视频帧每帧之间高度相关直接随机划分会严重泄漏。比如一段 10 秒视频里同一个限速牌出现在 300 帧里随机分到 train 和 val 两边模型等于见过验证集的「邻居」验证集就失去了意义。正确做法是先做去重用感知哈希或者简单的帧间隔抽样把相似帧去掉再正常划分。这个处理在数据集构建阶段就应该完成用户拿到手以后默认数据集已经做过了。但如果拿到的是原始未处理视频就得自己补这一步。3.2 划分脚本按类别分层按文件去重假设你手里有一个所有图片都堆在一个大文件夹里的原始集合附带了每张图的标签 csv需要自己划分成三个子集。直接用train_test_split随机划分是可以用但交通标志类别分布天然不均衡城市建设里限速标志的数量可能是危险警示标志的几十倍。如果不做分层抽样小类别可能全跑进训练集验证集里一个样本都没有代码直接报错。import pandas as pd from sklearn.model_selection import train_test_split df pd.read_csv(labels.csv) # 统计每个类别的样本量 print(df[class_id].value_counts()) df_train, df_temp train_test_split( df, test_size0.3, stratifydf[class_id], random_state42 ) df_val, df_test train_test_split( df_temp, test_size0.5, stratifydf_temp[class_id], random_state42 ) print(df_train[class_id].value_counts()) print(df_val[class_id].value_counts()) print(df_test[class_id].value_counts())这里有几个参数值得多说几句。stratifydf[class_id]是分层抽样的关键它保证每个类别在三个子集里的比例和原始数据集一致类别不均衡的问题至少不会因为划分而恶化。test_size0.3的含义是第一步先从全量数据中拿 30% 出来作为 val 和 test 的候选池第二次再从这个池子里各分一半最终得到 train 70%、val 15%、test 15%。如果数据总量很小比如几千张可以改成test_size0.2val 和 test 各 10%保证训练数据量。random_state42是复现的基石。不同的人用同一个数据集如果随机种子不一致划分出来的 val 和 test 完全不同模型之间无法比较。数据集的标题既然明确写了包含训练集、验证集、测试集三个子集说明划分已经替你做了不需要重划分但如果要自己划分务必固定种子并且在文档里写明。不然你跑出来的 baseline 换个人复现指标差两个点都不知道是模型问题还是数据划分问题。3.3 划分结果检查清单划分完成后不要直接开训用一行代码验证每个子集的类别覆盖情况assert set(df_val[class_id]) set(df_train[class_id]) assert set(df_test[class_id]) set(df_val[class_id])这行代码验证三个子集包含的类别完全一致。交通标志数据里如果某个类别在全集中只有 5 张图分层划分后它在 val 里可能只有 1 张test 里 1 张虽然覆盖了但在评估时方差极大。这时候就要考虑是否对这个类别做类别加权或者过采样属于训练层面的问题放到训练参数那一章再详细处理。必要的时候可以看一下每个类别的样本量变化情况如果少数类的 val 样本不足 5 个值得认真考虑是否合并相似类别比如把 limit_30、limit_40、limit_50 合并为 speed_limit 一个超类或者干脆把rare_warning归入other。还要检查的一点是子集之间的图像不能有重叠。前面提到视频帧去重这一步可以在划分前用文件哈希去重一下find . -type f -name *.jpg -exec md5sum {} | sort | awk {print $1} | uniq -d这个命令找出所有重复文件如果输出为空说明没有完全相同的图片。但感知上相同、哈希不同的图查不出来还得靠人工抽查。最常见的重灾区是同一块标志牌在不同天气不同角度的连拍图这些图在工程上算同一目标在数据上也应该归到一起。4. 用预训练模型做分类训练数据增强、超参与评估指标4.1 模型选型轻量网络足够别一上来就上 Transformer交通标志分类的输入相对简单标志占据画面主体背景复杂程度远低于 ImageNet 的真实场景。个人经验是 ResNet 系或者 MobileNet 系完全够用。ResNet18 或者 ResNet50 加 ImageNet 预训练权重迁移学习后在几千张交通标志图上微调验证集准确率很容易做到 95% 以上。MobileNetV3 或者 EfficientNet-Lite 适合嵌入式部署推理速度快很多准确率会低一点但在标志这种高对比目标上差距不明显。最新的图像分类模型像 ViT、Swin Transformer 也可以跑但小数据集上如果没有做充分的增强效果常常打不过带预训练的 CNN还会在训练速度上吃亏。如果是课题需要对比不同分类算法建议先把 CNN baseline 做扎实再考虑上 Transformer。选 ResNet18 还是 ResNet50 有一个简单的判断标准训练集单类样本少于 200 张时用 ResNet18防过拟合单类样本超过 500 张、类别数超过 30 时用 ResNet50容量更大。4.2 数据增强的正确设置翻转和颜色扰动有讲究交通标志有强方向性这是增强策略和普通物体分类最大的区别。左转箭头、右转箭头、单行路标志这类方向敏感的标志在水平翻转后语义会变箭头朝左变成朝右。如果训练集里翻转后没有同步修改标签模型就会在训练时收到大量自相矛盾的样本。常见做法是关闭水平翻转或者做一个类别白名单只对非方向类别的样本做翻转。from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.2), transforms.RandomRotation(degrees5), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])RandomResizedCrop的scale(0.7, 1.0)控制裁剪面积占原图的比例最低留 70%这个值不要设置太低。交通标志本来就不大裁剪面积如果掉到 30%标志主体可能被切掉一半。RandomRotation(degrees5)只在 5 度范围内旋转因为实际路面上标志一般不会歪太多旋转角度要是到了 15 度以上数字和文字会变得难以辨识模型会学到奇怪的旋转不变性。ColorJitter的三个参数是为了模拟清晨逆光、黄昏色偏、阴天低对比度的情况。交通标志数据集的难点之一就是光照环境变化大同样的红色禁行标志在正午和傍晚拍出来RGB 值差异非常大不加颜色扰动的话模型很容易依赖颜色特征而不是形状特征。验证集和测试集不应用任何随机增强只用 Resize 加 CenterCrop。随机增强带来的像素波动在训练时有助于泛化但在评价时只会引入噪点无法稳定复现结果。增强顺序也需要注意PIL 读进来的图像先做几何变换再做颜色变换顺序倒过来会导致某些像素值超出预期范围。4.3 训练超参学习率、batch size 和类别不均衡处理微调预训练模型时学习率设置是决定成败的头号因素。常见做法是 backbone 部分用较小的学习率比如 1e-5 到 3e-5新加的分类头用稍大的 1e-3 到 3e-3。整体用 3e-4 作为初始学习率跑全量微调也能收敛但前期 loss 波动会比较大。如果数据量少于一万张建议冻结 backbone 的前几层只微调后面几层和分类头能明显减少过拟合。batch size 在 32 到 128 之间比较合适。显存不够时优先减 batch size而不是减图像尺寸图像太小了标志里的文字细节彻底丢失再好的增强也没用。类别不均衡在交通标志里是常态。解决手段有三个层次。最简单的先用weighted sampler做样本重采样让小类别的图片在同一个 epoch 里被多次抽样from torch.utils.data import WeightedRandomSampler labels [sample[1] for sample in dataset] class_counts torch.bincount(torch.tensor(labels)) weights 1.0 / class_counts.float() sample_weights weights[labels] sampler WeightedRandomSampler(sample_weights, num_sampleslen(sample_weights), replacementTrue)这段代码的核心逻辑很直白每个样本的权重等于它的类别的样本数的倒数样本量少的类别权重自然变大抽样时更容易被选中。replacementTrue表示抽样放回这样每次循环都能采够指定的num_samples数。注意如果小类别只有十几张图对它过采样会快速导致过拟合只记住这几张图的全部细节泛化很差。所以第二个层次是配合使用 Focal Loss把模型对大类别的高置信度预测降权逼着优化器多关注困难样本。第三个层次是类别合并把语义相近且样本极少的类别合并成更大的类别这是质量最高的做法。做工程项目时最怕在一两个只有十几张图的类别上和模型死磕性价比太低。评估指标方面交通标志数据集用准确率看起来很好看但类别不均衡时一个小众类全错也可能只掉 0.5% 的准确率。建议同时打印每类的精确率和召回率重点看危险警示类别的召回率。这类标志在真实场景里直接关系到安全漏检比误检严重得多。混淆矩阵值得输出一份看哪些类别之间容易互相认错最常见的混淆情况是红圈类标志之间互相打架比如限速 30 和限速 40都是红圈白底黑字区别只有数字模型经常搞混。如果混淆矩阵里这一类错误占比很高考虑不再将单个限速数字作为类别而是合并成统一的限速标志类别让后续更细粒度的识别交给 OCR 模块处理这在实际工程中是很常见的降本做法。4.4 训练配置参考给出一个经过实际验证的微调配置数据规模在 8000 张左右、类别数 30 上下时可以直接参考参数推荐值说明预训练模型ResNet18 / ResNet50ImageNet 权重输入尺寸224×224不宜低于 128batch size64显存不够就降到 32初始学习率3e-4全量微调用 1e-4 更稳分类头学习率1e-3倍率约 3 到 10 倍优化器AdamWweight_decay 设 5e-4学习率调度CosineAnnealingLR总 epoch 的 1/3 处衰减最明显warmup 轮数5防止前期 loss 大幅震荡训练轮数30early stopping 耐心 8 轮类别均衡WeightedRandomSampler小类别样本少于 100 时启用有两点容易踩坑提前说明。weight_decay如果设成 1e-2 以上对预训练 backbone 的扰动会很大出现过拟合时优先调增强而不是猛拉 weight decay。另外CosineAnnealingLR的T_max要和总 epoch 数一致否则学习率退火曲线在半路就归零又反弹模型训练过程很不稳定。5. 交通标志分类训练避坑从数据泄漏到方向翻转的五个实战记录5.1 验证集准确率 99%线上测试掉到 80%现象训练和验证时模型表现非常好准确率 99%但换一批真实场景图测试准确率直接掉到 80% 左右且错误集中在夜间和强逆光样本上。原因验证集和训练集来自同一批采集路段天气、光线条件接近模型学到了「这张图来自哪个路段」的背景特征而不是标志本身的特征。这种情况也常见于连续视频帧未经去重直接洗牌划分。验证集与训练集分布重叠太大评估结果自然虚高。解决重新划分数据按路段或者视频片段分组。划分的单位是「片段」而不是「单帧」同一个路段的图全部放进同一个子集。这个检查应该在数据准备阶段完成。模型训练完以后可以按采集时间或者路段维度拆开评估指标如果某些路段明显偏低说明模型对场景的泛化还没到位。5.2 水平翻转增强导致方向类标志错乱现象训练 loss 一直在降但验证集上左转箭头和右转箭头互相混淆的比例特别高超过 20%。检查增强后的图片时发现左转标志翻转后变成了右转箭头标签却还是左转。原因全局采用水平翻转增强没有对方向敏感类做豁免。交通标志里有大量方向语义由图形走向表达翻转直接改变语义。这类错误模型很难学明白因为同样的训练图在以两种相反的标签反复出现。解决把方向类别的 id 做成白名单在增强流水线中按样本标签判断是否做翻转。翻转逻辑放到 Dataset 里按 index 判断更灵活避免在 transform 层面对无标签数据做统一处理。5.3 夜间和逆光样本上模型集体失明现象白天样本准确率 97%夜间样本准确率只有 62%尤其红色禁行标志在昏暗背景下容易被漏掉。把夜间图片拉出来看发现标志和背景的对比度非常低标志表面还有反光。原因数据集里夜间样本占比太低训练时模型没有学到足够的暗光特征。普通颜色抖动增强只是让颜色听起来噪声更大模拟不了真实的夜间灯光、反光和暗角效果。解决推荐两个方案。一是对夜间样本做离线扩充混入多种不同夜间风格的样本包括路灯暖光、车灯冷白、雨中反光等风格可以简单用亮度调整加对比度拉伸来模拟规范一点的话用风格迁移生成但成本偏高。二是训练时提高少量夜间验证样本的权重观察模型在夜间数据上的 loss 曲线变化确认模型确实在朝夜间方向优化而不是只拟合白天主分布。5.4 小尺寸标志在 resize 后模糊成色块现象标志在原图里只占 30×30 像素resize 到 224×224 后边缘锯齿感明显数字部分基本糊成一团。模型对限速 30 和限速 50 的区分能力很差。原因数据集的原始标注图里有大量未裁剪的全景图标志只占很小比例直接全局 resize 相当于把小标志放大了快 8 倍像素信息不足插值产生的伪细节只会添乱。解决如果数据集的 train 子集里已经统一裁剪好不存在这个问题。但如果发现图片里标志周围有大片背景就值得先做一步主体检测或者按标注框裁出标志区域再做 resize。两段式管线的思路在这里仍然适用先把目标区域切出来再分类准确率通常能一次性提升 5 到 10 个百分点。挑个实际项目经常用的做法就是先跑一个轻量检测模型或者直接用标注框裁剪分类模型只吃标志区域不关心背景。5.5 标签噪声少数类别混入了错误标注现象训练曲线正常但混淆矩阵里出现了一些难以解释的交叉错误人工抽检训练集图片时发现少数图片的标签明显贴错了比如把限速 50 标成了限速 80注意儿童标志和前方施工标志互相标反。原因标注质量问题。交通标志类别多且部分标志外观相似标注人员的疲劳和粗心免不了。使用开源数据集时也可能存在标注工具自动生成的错误。解决用训练的模型对训练集做一次回灌预测筛出置信度极低的样本。人工只看筛选出来的一两百张图比从头看几千张图效率高得多。标签修正后重训多半能恢复 1% 到 3% 的准确率。这个操作在工程里成本很低但经常被忽略。清洗数据对最终效果的提升常常比换一个更好的分类网络骨架更明显。6. 用类激活图验证模型到底在看哪里模型收敛后除了看准确率指标建议花半小时做一次可解释性验证确认模型真的在依据标志的形状和文字做判断而不是在依据背景里某个固定位置的树影或路牌杆。用类激活图技术可以直观地看到模型对某张图做出判断时重点关注了哪些区域。实现上可以用pytorch-grad-cam这个库也可以手工实现一个简单的 Grad-CAM核心是对最后一个卷积层的输出特征图做梯度加权求和然后上采样到输入尺寸叠加显示。from torchcam.methods import SmoothGradCAMpp from torchcam.utils import overlay_mask model.eval() cam_extractor SmoothGradCAMpp(model, target_layerlayer4) with torch.no_grad(): out model(img.unsqueeze(0)) act_map cam_extractor(0, out) result overlay_mask(img, act_map, alpha0.5)运行后观察热力图分布。正常的模型应该把高激活区域集中在标志主体附近即使背景区域亮度很高也不该有响应。有两个场合值得专门验证。第一是换一个天气风格的样本比如把白天样本调暗加噪看模型关注区域是否发生漂移。第二是标注错误的数据清洗阶段之后重新看一遍之前误分类的样本的热力图确认修正后的模型是否学到了更干净的特征。类激活图除了验证模型行为还能辅助做类别合并的判断。如果模型对「注意儿童」和「前方学校」这两个类别关注的区域几乎相同说明两者视觉特征本来就高度重叠合并它们或者引入额外的上下文信息作为输入特征比硬让模型区分更合理。这就是工程上的务实取舍。个人习惯是每个项目结束前都把热力图抽查结果和混淆矩阵存进实验记录里作为调参时排除「玄学问题」的证据。希望这些内容对你手里的交通标志分类有实际帮助。本文还有配套的精品资源点击获取
