拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python+CNN交通标志识别实战:GTSRB数据集从80%到99%准确率

简介这份资源是一套基于Python与卷积神经网络实现的交通标志识别项目采用GTSRB数据集面向计算机、人工智能、通信工程、自动化等专业的在校学生与教师也适合作为毕业设计、课程设计或项目立项的参考案例。压缩包共9个文件约311KB包含5个Python源码文件、2个CSV数据文件、1个XML配置文件和1个Markdown说明文档分别承担模型构建、训练评估、数据预处理与项目说明等职责。项目代码经过完整测试运行稳定答辩评审平均分达到96分已有224人学习下载。读者可获得从数据预处理、CNN模型搭建到训练与评估的完整流程实现理解交通标志分类任务的关键环节并在此基础上修改扩展功能用于自身课题或作业。下载后建议先阅读README.md了解项目结构资源仅供学习参考请勿用于商业用途。1. 交通标志识别为什么值得用 PythonCNN 啃一遍 GTSRB路上那块红圈白底的限速牌人类司机扫一眼就知道踩不踩刹车但要让车自己认出来背后是一整套图像分类流水线。基于 PythonCNN 实现的交通标志识别数据集是 GTSRB这句话拆开看就是三件事用 Python 把德国交通标志基准数据集GTSRB读进来用卷积神经网络CNN做特征提取和分类最后跑出一个能对 43 类标志说人话的模型。GTSRB 全称 German Traffic Sign Recognition Benchmark四万多张实拍图光照、遮挡、倾斜、模糊全都有比 MNIST 那种干净手写数字难得多也真实得多。为什么不用全连接前馈网络硬怼图像处理为啥用 CNN 不用前馈神经网络核心就一句全连接层把图像拉平后丢掉了空间结构相邻像素的关系被拍扁参数还爆炸。CNN 的卷积核在局部感受野上滑动权值共享既保留空间信息又把参数量压下来。这个方向适合谁适合刚学完 Python 基础语法、装过 numpy 和 cv2、想找一个「数据现成、任务明确、能跑出准确率」的深度学习入门项目的人。它不像目标检测那样要标框也不像分割那样要逐像素分类任务闭环短调参反馈快是练手 CNN 的合适靶子。2. 把 GTSRB 读进内存目录结构、尺寸统一与标签映射2.1 GTSRB 的目录长什么样为什么不能直接 ImageFolderGTSRB 官方给的训练集是 43 个文件夹文件夹名就是类别 ID从 00000 到 00042每个文件夹里是一堆 ppm 或 png 图。听起来像 torchvision 的 ImageFolder 能直接吃但有两个坑一是图片尺寸不统一从十几像素到几百像素都有二是官方还单独给了一个 Test.csv测试集的标签不在文件夹名里得靠 csv 里的 ClassId 列去对。所以常见做法是自己写 Dataset而不是无脑 ImageFolder。我一般会先把目录结构确认一遍用一段脚本统计每个类别的样本数和尺寸分布心里有数再动手。import os from PIL import Image from collections import defaultdict root GTSRB/Training # 训练集根目录 count defaultdict(int) sizes defaultdict(int) for cls in sorted(os.listdir(root)): cls_dir os.path.join(root, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): if not fname.lower().endswith((.png, .ppm, .jpg)): continue count[cls] 1 with Image.open(os.path.join(cls_dir, fname)) as im: sizes[im.size] 1 print(类别数:, len(count)) print(总样本:, sum(count.values())) print(尺寸种类数:, len(sizes)) print(最常见尺寸:, sorted(sizes.items(), keylambda x: -x[1])[:5])这段代码的逻辑很直白遍历每个类别文件夹累计样本数同时用 PIL 打开每张图记录尺寸。参数上root指向训练集根目录如果你的数据解压后叫GTSRB/Final_Training/Images就改成那个路径。跑完你会看到尺寸种类可能上百种这就是为什么必须统一 resize。常见做法是全部缩到 32×32 或 48×4832×32 够快48×48 对小标志的细节保留更好我一般先用 32×32 跑通再往上加。2.2 标签映射与训练/验证划分的写法GTSRB 的类别 ID 是字符串 00000 这种训练时要转成 0 到 42 的整数。另外测试集的标签在 csv 里得单独处理。下面这段把训练集按 8:2 划分并建立类别名到索引的映射。import os import pandas as pd from sklearn.model_selection import train_test_split root GTSRB/Training samples [] class_to_idx {} for idx, cls in enumerate(sorted(os.listdir(root))): cls_dir os.path.join(root, cls) if not os.path.isdir(cls_dir): continue class_to_idx[cls] idx for fname in os.listdir(cls_dir): if fname.lower().endswith((.png, .ppm, .jpg)): samples.append((os.path.join(cls_dir, fname), idx)) train_s, val_s train_test_split( samples, test_size0.2, random_state42, stratify[s[1] for s in samples] ) print(训练:, len(train_s), 验证:, len(val_s)) print(类别映射示例:, list(class_to_idx.items())[:3])逻辑说明先按文件夹名排序生成稳定的类别索引保证每次运行映射一致stratify按标签分层抽样避免某些类别在验证集里一张都没有。参数test_size0.2是验证集比例random_state固定后结果可复现。测试集那边读Test.csv用ClassId列做标签Path列拼出图片路径逻辑类似只是标签来源从文件夹名换成 csv 列。注意GTSRB 的 ppm 格式 PIL 能直接读但如果你用 cv2.imread默认读出来是 BGR送进模型前记得转 RGB否则颜色通道反了红色限速牌可能被当成蓝色指示牌准确率会莫名其妙掉一截。3. 用 PyTorch 搭一个能打的 CNN卷积块、池化与分类头3.1 网络结构怎么定三层卷积够不够交通标志识别的 CNN 不需要 ResNet 那么深GTSRB 图片小、类别少三层卷积加两层全连接就能到 95% 以上。结构上我一般这样排Conv(3→32, 3×3) → ReLU → Conv(32→64, 3×3) → ReLU → MaxPool(2×2) → Conv(64→128, 3×3) → ReLU → MaxPool(2×2) → Flatten → FC(128×8×8→256) → ReLU → Dropout(0.5) → FC(256→43)。输入 32×32两次池化后特征图是 8×8通道 128展平后 8192 维接全连接。import torch import torch.nn as nn class TrafficSignCNN(nn.Module): def __init__(self, num_classes43): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 32 - 16 nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 16 - 8 ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 8 * 8, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes), ) def forward(self, x): x self.features(x) return self.classifier(x) model TrafficSignCNN() print(sum(p.numel() for p in model.parameters()), 个参数)逻辑说明padding1保证 3×3 卷积后尺寸不变池化才降采样inplaceTrue省一点显存Dropout 放在全连接之间防过拟合。参数上num_classes43对应 GTSRB 类别数如果你只做限速和禁令两类改成 2 即可。这个网络参数量大概一百多万CPU 也能跑GPU 上几分钟一轮。3.2 数据增强与 DataLoader 的配置GTSRB 里有些类别样本少比如某些罕见标志只有两三百张直接训练会偏。常见做法是加随机旋转、平移、亮度抖动。注意交通标志的旋转不能太夸张±15 度以内比较安全转 90 度限速牌就倒过来了语义变了。from torchvision import transforms from torch.utils.data import Dataset, DataLoader from PIL import Image train_tf transforms.Compose([ transforms.Resize((32, 32)), transforms.RandomRotation(15), transforms.RandomAffine(degrees0, translate(0.1, 0.1)), transforms.ColorJitter(brightness0.3, contrast0.3), transforms.ToTensor(), transforms.Normalize(mean[0.34, 0.31, 0.32], std[0.27, 0.26, 0.27]), ]) val_tf transforms.Compose([ transforms.Resize((32, 32)), transforms.ToTensor(), transforms.Normalize(mean[0.34, 0.31, 0.32], std[0.27, 0.26, 0.27]), ]) class GTSRBDataset(Dataset): def __init__(self, samples, transform): self.samples samples self.transform transform def __len__(self): return len(self.samples) def __getitem__(self, i): path, label self.samples[i] img Image.open(path).convert(RGB) return self.transform(img), label train_loader DataLoader(GTSRBDataset(train_s, train_tf), batch_size64, shuffleTrue, num_workers2) val_loader DataLoader(GTSRBDataset(val_s, val_tf), batch_size64, shuffleFalse, num_workers2)逻辑说明训练集用增强验证集只做 resize 和归一化保证评估稳定。Normalize的均值和标准差是 GTSRB 训练集的统计值用 0.34/0.27 这组是常见经验值你也可以自己算一遍。batch_size64在 8G 显存上比较稳显存小就降到 32。num_workers在 Windows 上如果报错就设 0这是血泪经验多进程在 Windows 下容易卡死。4. 训练、评估与调参让准确率从 80% 爬到 97%4.1 训练循环与学习率调度训练循环本身不复杂关键是损失函数、优化器和学习率调度。分类任务用交叉熵优化器用 Adam 起步学习率 1e-3跑十几个 epoch 后如果验证准确率不涨了用 StepLR 或 CosineAnnealing 降学习率。import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR device torch.device(cuda if torch.cuda.is_available() else cpu) model TrafficSignCNN().to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max20) for epoch in range(20): model.train() running_loss 0.0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() out model(imgs) loss criterion(out, labels) loss.backward() optimizer.step() running_loss loss.item() * imgs.size(0) scheduler.step() model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) pred model(imgs).argmax(1) correct (pred labels).sum().item() total labels.size(0) print(fepoch {epoch1} loss {running_loss/total:.4f} val_acc {correct/total:.4f})逻辑说明weight_decay1e-4是 L2 正则配合 Dropout 一起压过拟合CosineAnnealingLR让学习率按余弦曲线从 1e-3 降到接近 0比固定学习率收敛更稳。参数T_max20要和总 epoch 数一致否则调度节奏对不上。每轮打印验证准确率如果连续几轮不涨就该考虑加数据增强或换更大输入尺寸了。4.2 混淆矩阵看哪些类别在互相认错准确率是个总数看不出问题在哪。GTSRB 里有些标志长得像比如限速 30 和限速 80 在低分辨率下容易混圆形禁令标志之间也容易串。跑一个混淆矩阵找出错得最多的类别对再针对性处理。from sklearn.metrics import confusion_matrix import numpy as np model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) pred model(imgs).argmax(1).cpu().numpy() all_preds.extend(pred) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) # 找出非对角线最大的几个混淆对 off cm.copy() np.fill_diagonal(off, 0) top np.dstack(np.unravel_index(np.argsort(off.ravel())[::-1][:5], off.shape))[0] for i, j in top: print(f真实 {i} 被预测成 {j}: {off[i, j]} 次)逻辑说明confusion_matrix的行是真实标签列是预测标签对角线是正确数。把对角线清零后排序就能看到最严重的混淆对。参数上val_loader必须shuffleFalse否则预测和标签对不上。如果发现某两类互相错得特别多常见做法是单独给这两类加样本或者把输入尺寸从 32 提到 48让细节更清楚。注意别一上来就堆很深网络或加注意力模块。GTSRB 这个任务三层卷积加好的增强就能到 97% 以上加太复杂反而容易过拟合训练还慢。先跑通基线再谈优化。5. 避坑与排查那些让准确率卡在 80% 的常见问题5.1 现象训练准确率很高验证准确率死活上不去原因典型过拟合。GTSRB 训练集四万多张但分布不均某些类别样本多模型记住了这些类别的纹理而不是通用特征。解决先加数据增强尤其是随机旋转和亮度抖动再把 Dropout 从 0.5 提到 0.6如果还不行检查是不是验证集划分有问题比如同一段连续视频的帧被分到了训练和验证两边导致验证集泄漏。用stratify分层抽样能缓解但如果是按视频分帧的数据得按视频 ID 划分而不是按图片。5.2 现象loss 变成 nan训练直接崩原因学习率太大或者输入没归一化。GTSRB 原图是 0 到 255 的整数直接送进网络梯度爆炸是迟早的事。解决确认ToTensor()之后有没有接NormalizeToTensor会把像素缩到 0 到 1但还不够得再减均值除标准差。学习率从 1e-3 降到 1e-4 试试Adam 对学习率比 SGD 敏感1e-3 有时就偏大。5.3 现象某些类别准确率极低其他类别接近满分原因类别不平衡。GTSRB 里最多的类别有两千多张最少的只有两百多张差十倍。解决用加权交叉熵给样本少的类别更大权重或者用 WeightedRandomSampler 让每个 batch 里各类别比例均衡。加权交叉熵的写法是给CrossEntropyLoss传weight参数权重取类别频率的倒数归一化。5.4 现象GPU 利用率低训练慢得离谱原因数据加载成了瓶颈。num_workers设太小或者图片是 ppm 格式PIL 解码慢。解决把num_workers提到 4 或 8pin_memoryTrue加速 CPU 到 GPU 的拷贝。如果还是慢可以先把所有图片预处理成 32×32 的 npy 数组存下来训练时直接读数组省掉每次解码的开销。这个预处理换来的加速非常明显尤其在小图上。5.5 现象测试集提交上去分数比验证集低一截原因测试集的标签在 csv 里路径拼接时可能漏了子目录或者测试集图片的尺寸分布和训练集不同。解决先检查测试集读进来的图片数量和 csv 行数是否一致再抽查几张图确认标签对得上。另外测试集不能用训练集的增强只能 resize 和归一化这点容易写错。6. 把模型推到 99%TTA、集成与部署前的最后几手到 97% 左右单模型单次推理基本到顶了再想往上走得用测试时增强TTA和模型集成。TTA 的思路是推理时对同一张图做多种变换比如原图、水平翻转、轻微旋转分别预测后把概率平均。交通标志水平翻转要小心左转和右转标志翻转后语义就反了所以只对不涉及方向的类别做翻转或者干脆只用旋转和缩放。def predict_tta(model, img_tensor, n_aug5): model.eval() probs torch.zeros(1, 43).to(img_tensor.device) with torch.no_grad(): probs torch.softmax(model(img_tensor), dim1) for _ in range(n_aug - 1): aug transforms.RandomAffine(degrees10, translate(0.05, 0.05))(img_tensor) probs torch.softmax(model(aug), dim1) return probs.argmax(1)逻辑说明对同一张图做多次随机仿射变换每次取 softmax 概率累加最后取 argmax。参数n_aug是增强次数5 次通常够再多收益递减还费时间。注意这里用的是RandomAffine而不是翻转避开方向敏感的标志。集成则是训练三到五个结构相同但初始化不同的模型推理时投票或平均概率。GTSRB 上三模型集成通常能再涨 0.5 到 1 个百分点。代价是推理时间翻三倍如果部署在边缘设备上要权衡。部署前还有一件事把 PyTorch 模型转成 ONNX 或 TorchScript脱离 Python 环境跑。转 ONNX 用torch.onnx.export注意输入尺寸固定成 1×3×32×32动态轴设 batch 维。转完之后用 onnxruntime 加载推理速度通常比原生 PyTorch 快尤其在 CPU 上。我自己踩过最深的坑是归一化参数。有次换了个数据集均值标准差没改模型在验证集上看着还行实际路测时阴天和逆光下疯狂翻车。后来养成习惯每换一批数据先重新统计均值和标准差再训练。这个习惯比任何调参技巧都值钱。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门