ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于CNN的GTSRB交通标志识别源码包实战:从训练到评估完整链路

基于CNN的GTSRB交通标志识别源码包实战:从训练到评估完整链路 简介这份资源是一套基于卷积神经网络识别交通标志的Python项目源码配套GTSRB数据集使用面向计算机、人工智能、通信、电子信息等相关专业的学生及企业员工。它可用于课程设计、毕业设计、大作业或初期项目立项演示也适合作为深度学习入门实战练习。压缩包共9个文件约310KB包含5个py源码文件、2个csv数据文件、1个xml配置文件和1个md说明文档分别承担模型构建、训练评估、数据预处理与项目说明等职责。项目围绕交通标志识别任务提供从数据读取、图像预处理到CNN模型搭建、训练与评估的完整流程代码经过测试可正常运行。目前已有170人学习下载读者可借此理解卷积神经网络在图像分类中的实际应用掌握数据组织方式与训练评估思路并在此基础上进行二次开发或迁移到其他分类任务。1. 从一份 GTSRB 交通标志识别源码包说起它到底能跑出什么如果你正在为毕业设计或课程大作业找一个能跑通、有数据集、有完整训练评估链路的深度学习项目这份基于 CNN 识别交通标志的 Python 源码包值得先看一眼。它用的是 GTSRBGerman Traffic Sign Recognition Benchmark数据集这是交通标志识别领域最经典的公开基准之一包含 43 类标志、超过 5 万张实拍图片光照变化、遮挡、运动模糊全都有不是那种清洗得干干净净的玩具数据。整个包的结构很直白TSRCnn.py定义网络TSRTrain.py负责训练TSREval.py做评估TSRInput.py和Preprocessing.py处理数据输入与预处理外加train_data.csv、test_data.csv两个索引文件和一份 README。适合谁计算机、人工智能、通信、电子信息方向的同学拿来做课设或毕设底座也适合刚学完 CNN 卷积神经网络理论、想找一个完整项目把「数据读入→预处理→建模→训练→评估」串起来的人。它不炫技但链路完整改起来有抓手。2. 拆开源码包文件分工与 CNN 结构怎么对应2.1 每个 .py 文件在训练链路里的位置拿到一个源码包我习惯先不跑先把文件按数据流排一遍。这份包的逻辑线是清楚的Preprocessing.py图像预处理通常做 resize、归一化、灰度或通道调整。GTSRB 原始图片尺寸不统一这一步是必须的。TSRInput.py数据输入封装把 CSV 里的路径和标签读进来组织成训练/验证可用的批次。TSRCnn.pyCNN 网络定义卷积层、池化层、全连接层的堆叠都在这里。TSRTrain.py训练主循环包含损失函数、优化器、epoch 控制、模型保存。TSREval.py加载保存的模型在测试集上算准确率、混淆矩阵之类的指标。train_data.csv/test_data.csv不是图片本身是「图片路径 标签」的索引表GTSRB 官方划分好的训练/测试列表。这种拆分方式在课程设计里很常见好处是每个环节独立你想换网络结构只动TSRCnn.py想换预处理策略只动Preprocessing.py不会牵一发动全身。常见做法是先把 CSV 读进 pandas 看一眼列名和标签分布确认路径字段和标签字段叫什么再决定后面怎么改。2.2 CNN 结构卷积、池化、全连接的三段式交通标志识别的 CNN 基本结构跑不出「特征提取 分类头」两段。特征提取靠卷积层和池化层交替分类头靠全连接层加 softmax。以 GTSRB 的 43 类为例一个能用的基础结构大概是这样import torch import torch.nn as nn class TSRCnn(nn.Module): def __init__(self, num_classes43): super(TSRCnn, self).__init__() # 第一段卷积提取边缘、颜色块等低级特征 self.conv1 nn.Conv2d(3, 32, kernel_size3, padding1) self.conv2 nn.Conv2d(32, 64, kernel_size3, padding1) self.pool nn.MaxPool2d(2, 2) # 分类头把特征图拉平后映射到 43 类 self.fc1 nn.Linear(64 * 8 * 8, 256) self.fc2 nn.Linear(256, num_classes) self.relu nn.ReLU() self.dropout nn.Dropout(0.5) def forward(self, x): x self.pool(self.relu(self.conv1(x))) # 32x32 - 16x16 x self.pool(self.relu(self.conv2(x))) # 16x16 - 8x8 x x.view(x.size(0), -1) # 拉平 x self.dropout(self.relu(self.fc1(x))) x self.fc2(x) return x这段代码的关键参数num_classes43对应 GTSRB 的类别数改数据集必须同步改kernel_size3, padding1保证卷积后空间尺寸不变尺寸变化只发生在池化64 * 8 * 8是第二段卷积输出拉平后的维度输入图片尺寸变了这个数就得重算。Dropout(0.5)是防过拟合的常规手段交通标志数据量不算特别大加上它训练曲线会稳一些。实际源码里的层数、通道数可能和这里不同但结构逻辑一致你照着这个骨架去读TSRCnn.py就能对上号。2.3 数据预处理GTSRB 的图片为什么不能直接喂GTSRB 原始图片尺寸从 15×15 到 250×250 不等直接送进网络会因为尺寸不一致报错。Preprocessing.py要解决的就是这件事。常见做法是统一 resize 到 32×32 或 48×48然后归一化到 [0,1] 或做标准化。下面是一个可抄的预处理片段from PIL import Image import numpy as np def preprocess_image(img_path, target_size(32, 32)): img Image.open(img_path).convert(RGB) # 统一转 RGB防止灰度图通道数不一致 img img.resize(target_size) # 统一尺寸 arr np.array(img, dtypenp.float32) / 255.0 # 归一化到 [0,1] return arrconvert(RGB)这步容易被忽略GTSRB 里混有灰度图不转的话通道数对不上后面卷积直接崩。target_size要和网络输入层匹配你改成 48×48TSRCnn.py里全连接层的输入维度也得跟着改。归一化用/255.0是最简单的也有用均值方差标准化的效果差异不大但归一化这步不能省否则训练初期 loss 会跳得很难看。3. 从零跑通训练环境、命令与参数怎么设3.1 环境准备与依赖安装这份包是纯 Python 深度学习框架的路线从文件名和结构看大概率是 PyTorch 或 TensorFlow 其中之一。不管哪个环境准备逻辑一样。我一般会先建虚拟环境避免和系统里的包打架python -m venv tsr_env source tsr_env/bin/activate # Linux/Mac # tsr_env\Scripts\activate # Windows pip install torch torchvision pillow numpy pandas matplotlib如果你用的是 PyTorchtorch和torchvision是必须的pillow负责读图numpy和pandas处理 CSV 和数组matplotlib用来画训练曲线。装完之后先别急着跑训练用python -c import torch; print(torch.__version__)确认一下版本CUDA 是否可用用torch.cuda.is_available()看一眼。没有 GPU 也能跑GTSRB 在 CPU 上训练会慢但 32×32 的小图不至于跑不动只是 epoch 时间从秒级变成分钟级。3.2 数据路径配置CSV 里的路径怎么对上train_data.csv和test_data.csv里存的是图片路径和标签但路径是相对路径还是绝对路径直接决定你能不能跑起来。常见翻车场景是CSV 里写的是Train/00000/00000_00000.png这种相对路径但你的工作目录不对读图全部失败。解决办法是先确认 CSV 的列结构import pandas as pd df pd.read_csv(train_data.csv) print(df.columns.tolist()) print(df.head()) print(df[ClassId].value_counts().sort_index()) # 看类别分布如果列名是Path和ClassId那读图时就要拼上数据集根目录。我一般会在TSRInput.py里加一个root_dir参数把 CSV 里的相对路径和根目录拼成绝对路径。类别分布那行也值得看GTSRB 各类样本数不均衡少的类只有几百张多的有两千多张训练时如果发现某些类识别率特别低大概率是样本少导致的可以考虑加权重或做数据增强。3.3 训练脚本的关键参数epoch、batch size、学习率TSRTrain.py是训练入口跑之前先把几个核心参数过一遍。下面是一个典型的训练循环骨架import torch import torch.nn as nn import torch.optim as optim from TSRCnn import TSRCnn device torch.device(cuda if torch.cuda.is_available() else cpu) model TSRCnn(num_classes43).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) for epoch in range(30): model.train() running_loss 0.0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() print(fEpoch {epoch1}, Loss: {running_loss/len(train_loader):.4f}) torch.save(model.state_dict(), ftsr_epoch{epoch1}.pth)参数说明lr0.001是 Adam 的常用起点loss 不降就调到 0.0001震荡就再降batch_size一般在 32 到 128 之间显存小就调小epoch30是经验值GTSRB 上 20 到 50 个 epoch 通常能收敛看验证集准确率不再涨就可以停。每个 epoch 存一次模型是后悔药万一后面训崩了还能回退。CrossEntropyLoss自带 softmax所以网络最后一层不要再加 softmax加了反而数值不稳定。3.4 评估与指标准确率之外还要看什么TSREval.py负责在测试集上评估。准确率是最直观的但只看准确率不够。GTSRB 类别不均衡一个把所有样本都预测成多数类的模型也能有不错的准确率所以混淆矩阵和每类召回率更值得看from sklearn.metrics import classification_report, confusion_matrix model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in test_loader: imgs imgs.to(device) outputs model(imgs) preds outputs.argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, digits4)) print(confusion_matrix(all_labels, all_preds))classification_report会给出每类的 precision、recall、f1-score哪类识别差一目了然。混淆矩阵能看出模型把哪类错认成哪类比如限速 50 和限速 60 容易混这是交通标志识别的经典难点因为数字区域差异小。如果某两类混淆严重可以考虑针对性做数据增强或者把输入分辨率提上去。4. 避坑与排查跑这份源码最容易翻车的五个地方4.1 图片路径读不到训练一开始就报 FileNotFoundError现象运行TSRTrain.py后立刻报FileNotFoundError提示某张图片不存在。原因CSV 里的路径是相对路径而你的工作目录不是数据集根目录或者 CSV 里的路径分隔符和系统不匹配Windows 用反斜杠Linux 用正斜杠。解决在TSRInput.py里统一用os.path.join(root_dir, rel_path)拼路径root_dir通过命令行参数或配置文件传入跨平台的话用pathlib.Path替代字符串拼接它会自动处理分隔符。4.2 通道数不匹配卷积层直接报错现象RuntimeError: Given groups1, weight of size [32, 3, 3, 3], expected input[16, 1, 32, 32] to have 3 channels, but got 1 channels。原因GTSRB 里混有灰度图Preprocessing.py没做convert(RGB)读进来是单通道而网络第一层定义的是 3 通道输入。解决在预处理里强制Image.open(path).convert(RGB)保证所有图片都是三通道。这个坑很隐蔽因为大部分图片是彩色的只有少数灰度图会触发训练跑到一半才崩。4.3 loss 不降或变成 nan训练直接废掉现象训练几个 epoch 后 loss 变成nan或者一直卡在 2.3 左右不降。原因学习率太大导致梯度爆炸或者输入没有归一化像素值在 0 到 255 之间梯度尺度失控。解决先把输入归一化到 [0,1]确认Preprocessing.py里有/255.0然后把学习率从 0.001 降到 0.0001 试一轮如果还不行加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0)。loss 变 nan 基本就是学习率和归一化这两个原因逐个排查。4.4 显存不够batch size 调小还是 OOM现象CUDA out of memory把 batch size 从 64 降到 32 还是报。原因可能是模型没释放中间变量或者图片分辨率设太大32×32 的图 batch 32 一般不会 OOM除非你改成了 128×128。解决先确认输入尺寸是不是被改大了然后在训练循环里用with torch.no_grad()包住评估部分如果还不行用torch.cuda.empty_cache()手动清缓存。CPU 训练的话不存在这个问题只是慢。4.5 评估准确率虚高测试集混进了训练集现象测试集准确率 99% 以上但实际用新图片测试时错得离谱。原因train_data.csv和test_data.csv有重叠或者预处理时对训练集和测试集做了不同的归一化参数。解决先检查两个 CSV 的图片路径有没有交集用集合运算set(train_paths) set(test_paths)看一眼然后确认训练和测试用的是同一套预处理逻辑归一化参数必须从训练集统计出来再应用到测试集不能各算各的。这个坑在课程设计里很常见准确率好看但模型没用。5. 进阶玩法把这份源码改成你自己的项目底座5.1 换数据集从 GTSRB 迁移到自定义交通标志GTSRB 跑通之后很多人想换成自己采集的交通标志数据。迁移的核心改动就三处类别数、输入尺寸、CSV 格式。类别数改TSRCnn.py里的num_classes输入尺寸改Preprocessing.py的target_size和网络全连接层的输入维度CSV 格式保持「路径 标签」两列标签从 0 开始连续编号。如果自定义数据量少建议先用 GTSRB 预训练再在自己的数据上微调只训练最后几层学习率设小一点比如 0.0001。这样比从零训练效果好得多也是常见做法。5.2 数据增强让模型对光照和角度更鲁棒GTSRB 本身已经包含不少变化但如果你要部署到实际场景数据增强能明显提升鲁棒性。常见做法是在预处理里加随机亮度、随机旋转、随机裁剪from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((32, 32)), transforms.RandomRotation(10), # 随机旋转 ±10 度 transforms.ColorJitter(brightness0.3), # 随机亮度调整 transforms.RandomAffine(degrees0, translate(0.1, 0.1)), # 随机平移 transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ])注意增强只用在训练集测试集和验证集不要加随机变换否则评估结果不可比。Normalize的均值和方差可以用训练集统计出来也可以用 0.5 这个经验值差异不大。加了增强之后训练 epoch 要适当增加因为模型看到的变化更多收敛会慢一些。5.3 模型保存与复现怎么保证下次还能跑出同样结果深度学习项目最头疼的是「这次跑通了下次跑结果不一样」。要保证可复现三件事必须做固定随机种子、保存模型权重和优化器状态、记录超参数。随机种子固定import torch import numpy as np import random def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark Falsecudnn.deterministic True会让训练慢一点但结果可复现。保存模型时除了state_dict最好把 epoch 数、优化器状态、当前 loss 一起存方便断点续训。我一般会存成字典torch.save({epoch: epoch, model: model.state_dict(), optimizer: optimizer.state_dict(), loss: loss}, checkpoint.pth)。这样即使训练中断下次加载还能接着跑不用从头再来。5.4 一个具体技巧用混淆矩阵定位难分类别评估时别只看一个准确率数字。把混淆矩阵画出来找出错得最多的那几类针对性处理。比如限速 50 和限速 60 混淆严重可以把这两类的训练样本单独拎出来做增强或者提高输入分辨率让数字区域更清晰。我自己的习惯是每次训练完都跑一遍classification_report把 f1-score 低于 0.9 的类记下来下一轮重点看。这个习惯帮我省了很多盲目调参的时间。从那以后我每次跑完训练都强制走一遍混淆矩阵不看一眼不放心。希望这份源码包能帮你把交通标志识别这个项目真正跑起来而不是停在「下载了但没打开」的状态。本文还有配套的精品资源点击获取
返回列表