恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于CNN的GTSRB交通标志识别源码解析与实战
首页
资讯中心
/
基于CNN的GTSRB交通标志识别源码解析与实战
基于CNN的GTSRB交通标志识别源码解析与实战
发布时间:2026/10/12 13:39:44
简介这是一份面向计算机、人工智能及相关专业学生与开发者的交通标志识别项目源码基于Python与卷积神经网络实现采用GTSRB数据集完成模型训练与评估适合作为毕业设计、课程设计或入门深度学习实战的参考方案。压缩包共9个文件约311KB包含5个Python脚本分别负责数据预处理、模型构建、训练与评估2个CSV文件存放训练与测试数据索引另有XML配置与Markdown说明文档结构清晰便于快速上手。目前已有225人学习下载。项目代码经过完整测试可直接运行读者能借此掌握CNN图像分类的完整流程包括数据读取、网络搭建、训练调参与性能评估也可在现有基础上修改以扩展其他识别任务是学习计算机视觉与深度学习落地的实用素材。1. 从一份能跑通的 GTSRB 源码包说起它到底解决了什么问题如果你正在做交通标志识别相关的课程设计或毕设大概率绕不开 GTSRB 这个数据集。德国交通标志识别基准包含 43 类、超过 5 万张实拍图片分辨率从 15×15 到 222×162 不等光照、遮挡、倾斜全都有比 MNIST 那种干净数据难得多。这份 TSR-master 源码包做的事情很明确用 Python CNN 把 GTSRB 从原始图片一路跑到可评估的识别模型中间的数据预处理、训练、评估脚本全部拆开成独立文件不是那种一个 notebook 从头写到尾的糊状代码。它适合三类人刚接触深度学习想找一个完整可复现流程的入门者、需要快速搭出交通标志识别 demo 的在校学生、以及想拿一份结构清晰的 CNN 项目做二次修改的开发者。下面我按实际拆包和跑通的顺序把每个文件的作用、参数怎么调、哪里容易翻车讲清楚。2. 拆开 TSR-master文件结构与数据流走向2.1 每个 .py 文件到底管什么拿到压缩包解压后根目录下能看到这些核心文件Preprocessing.py、TSRInput.py、TSRCnn.py、TSRTrain.py、TSREval.py外加train_data.csv、test_data.csv两个索引文件和data3目录。很多人第一次打开会懵——为什么训练脚本和模型定义要分开这正是这份代码值得看的地方它把「数据管道」和「模型结构」做了解耦。Preprocessing.py负责把 GTSRB 原始图片统一尺寸、做归一化并生成 CSV 索引。TSRInput.py是数据读取层按 CSV 里的路径批量加载图片、做 batch 划分。TSRCnn.py只定义网络结构不掺任何训练逻辑。TSRTrain.py调用前面三个模块完成训练并保存权重。TSREval.py加载权重在测试集上算准确率、混淆矩阵。这个分层思路在小型项目里不常见但对后续改模型、换数据集非常友好——你换网络只需要动TSRCnn.py数据格式不变就不用碰其他文件。data3目录是实际存放图片的地方train_data.csv和test_data.csv里记录的是每张图的相对路径和对应类别标签。常见做法是 CSV 至少两列filename和label有的版本还会带width、height。如果你拿到的 CSV 列名不一样后面TSRInput.py里的读取逻辑就要跟着改这是第一个容易卡住的地方。2.2 数据预处理的三个关键动作GTSRB 原始图片尺寸不统一直接送进 CNN 会报维度错误。Preprocessing.py里通常做三件事resize 到固定尺寸常见是 32×32 或 48×48、像素值归一化到 [0,1]、把类别标签转成 one-hot 或整数索引。下面是我按这份代码逻辑还原的核心预处理片段你可以对照自己手里的版本确认import os import cv2 import numpy as np import pandas as pd # 统一尺寸GTSRB 原始图尺寸差异大必须固定 IMG_SIZE 32 data_dir ./data3 labels [] images [] for class_id in range(43): # GTSRB 共 43 类 class_path os.path.join(data_dir, str(class_id)) if not os.path.isdir(class_path): continue for img_name in os.listdir(class_path): img_path os.path.join(class_path, img_name) img cv2.imread(img_path) if img is None: continue # resize 时用 INTER_AREA缩小图像时比默认插值更少失真 img cv2.resize(img, (IMG_SIZE, IMG_SIZE), interpolationcv2.INTER_AREA) img img.astype(float32) / 255.0 # 归一化加速收敛 images.append(img) labels.append(class_id) # 保存为 npy 或 csv 索引供后续读取 images np.array(images) labels np.array(labels) np.save(train_images.npy, images) np.save(train_labels.npy, labels)这段代码的逻辑说明cv2.resize的interpolation参数很关键放大图片用INTER_LINEAR或INTER_CUBIC缩小图片一定用INTER_AREA否则会出现摩尔纹导致训练精度上不去。归一化除以 255.0 是标准操作但要注意如果你的网络第一层带了Rescaling层这里就不要重复除。np.save把处理好的数组存成二进制比每次训练都重新读图快很多——GTSRB 五万多张图每次 epoch 都重新 resize 会让训练时间翻倍。提示如果你的data3目录结构和上面不一致比如没有按类别分文件夹需要先按标签把图片归到对应子目录否则这段循环读不到任何数据。2.3 训练脚本的参数入口TSRTrain.py是实际执行训练的入口。跑之前先确认几个参数batch size、epoch 数、学习率、模型保存路径。这份代码里常见的默认值是 batch size 64 或 128、epoch 20 到 50、学习率 0.001 配合 Adam 优化器。下面是我一般会先跑一遍的启动命令和对应参数含义# 在项目根目录执行确保 train_data.csv 和 data3 在同一级 python TSRTrain.py \ --batch_size 64 \ --epochs 30 \ --lr 0.001 \ --model_path ./tsr_model.h5参数说明batch_size受显存限制4GB 显存跑 32×32 输入、64 batch 基本没问题如果报 OOM 就降到 32。epochs不是越大越好GTSRB 在 30 轮左右通常已经收敛再往后容易过拟合验证集准确率反而下降。lr用 0.001 是 Adam 的常用起点如果 loss 在前几轮就震荡降到 0.0005 试试。model_path指定权重保存位置训练完TSREval.py要从同一个路径加载两边必须一致。训练过程中重点看两个指标训练 loss 和验证准确率。如果训练 loss 持续下降但验证准确率卡住不动说明过拟合了常见做法是加 Dropout 层或做数据增强。如果两个都不降先检查数据标签有没有对错位——CSV 里路径和标签错行是新手最常犯的错误模型会学得莫名其妙。3. CNN 结构怎么读从 TSRCnn.py 看卷积层参数选择3.1 卷积核数量与感受野的取舍TSRCnn.py里定义的是一个典型的轻量级 CNN结构大致是「卷积 → 池化 → 卷积 → 池化 → 全连接 → 输出」。交通标志识别的特点是目标在图像中占比大、类别间差异集中在形状和颜色所以不需要像 ImageNet 那样堆很深的网络。常见配置是两层卷积加两层池化卷积核数量从 32 起步到 64 或 128。from tensorflow.keras import layers, models def build_tsr_cnn(input_shape(32, 32, 3), num_classes43): model models.Sequential([ # 第一层卷积32 个 3x3 核提取边缘和颜色块 layers.Conv2D(32, (3, 3), activationrelu, paddingsame, input_shapeinput_shape), layers.MaxPooling2D((2, 2)), # 第二层卷积64 个 3x3 核组合出形状特征 layers.Conv2D(64, (3, 3), activationrelu, paddingsame), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), # 抑制过拟合交通标志数据类别不均衡时尤其重要 layers.Dense(num_classes, activationsoftmax) ]) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) return model逻辑说明paddingsame保证卷积后特征图尺寸不变方便你算后续维度。第一层 32 个核是经验起点核太少欠拟合核太多在小数据集上容易过拟合。Dropout(0.5)放在全连接层之前是因为全连接层参数量最大、最容易记住训练样本。最后一层用softmax输出 43 类概率损失函数必须配categorical_crossentropy如果你把标签改成了整数而非 one-hot损失要换成sparse_categorical_crossentropy这两个不匹配会直接报错或训练不收敛。3.2 输入维度与通道顺序的坑input_shape(32, 32, 3)是 TensorFlow/Keras 的默认通道在后格式。如果你用 PyTorch 复现输入维度要写成(3, 32, 32)通道在前。这份代码是基于 Keras 的所以TSRInput.py里读出来的数组形状必须是(N, 32, 32, 3)。我见过有人用 OpenCV 读图后直接送进去OpenCV 默认是 BGR 通道而训练时如果按 RGB 处理颜色通道反了模型在红色禁令标志上准确率会明显偏低。解决办法是在预处理阶段加一句img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。另一个常见问题是图片尺寸和网络输入不匹配。Preprocessing.py里 resize 到 32×32但TSRCnn.py里如果写的是 48×48训练会直接抛维度错误。改的时候两个文件要同步改或者把IMG_SIZE抽成一个公共配置变量两边都引用它。这是小项目里最容易忽略的一致性维护点。3.3 类别不均衡的处理思路GTSRB 的 43 个类别样本数差异很大限速类标志可能有几千张而某些稀有标志只有一两百张。直接训练会导致模型偏向多数类稀有类召回率很低。常见做法有两种一是在TSRInput.py里做重采样让每个 batch 里各类别比例接近二是在model.compile时传class_weight参数给少数类更高权重。第二种改动更小适合不想动数据管道的情况。# 计算类别权重缓解不均衡 from sklearn.utils.class_weight import compute_class_weight import numpy as np class_weights compute_class_weight( class_weightbalanced, classesnp.unique(train_labels), ytrain_labels ) weight_dict dict(enumerate(class_weights)) # 训练时传入 model.fit(train_images, train_labels, class_weightweight_dict, batch_size64, epochs30)参数说明compute_class_weight的balanced模式会自动按样本数反比给权重样本越少的类权重越高。weight_dict的 key 必须是整数类别索引和标签编码方式对应。如果你的标签是 one-hot需要先np.argmax转回整数再算权重。这个改动通常能把稀有类的召回率拉高十几个百分点代价是整体准确率可能略微下降属于精度和公平性的权衡。4. 训练与评估实操从启动到看懂混淆矩阵4.1 训练过程的监控与中断处理跑TSRTrain.py之后终端会逐 epoch 打印 loss 和 accuracy。我一般会同时开一个 TensorBoard 或者至少把日志重定向到文件方便回头对比不同参数的效果。如果训练中途断了断电、手动 CtrlC权重文件可能只保存了部分重新加载会报格式错误。稳妥做法是在ModelCheckpoint回调里设置save_best_onlyTrue只保留验证集上最好的那一版。from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping callbacks [ # 只保存验证准确率最高的权重避免保存到过拟合的版本 ModelCheckpoint(best_tsr_model.h5, monitorval_accuracy, save_best_onlyTrue, verbose1), # 验证集 loss 连续 5 轮不降就停省时间也防过拟合 EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue) ] model.fit(train_images, train_labels, validation_split0.2, epochs30, batch_size64, callbackscallbacks)参数说明validation_split0.2从训练集里切 20% 做验证如果TSRInput.py已经单独切好了验证集这里就不要重复切。patience5表示验证 loss 连续 5 轮不下降就提前停这个值设太小会停得太早设太大等于没设5 到 10 之间比较合理。restore_best_weightsTrue保证停止时模型回到最佳状态而不是停在最后那个过拟合的 epoch。4.2 评估脚本输出的指标怎么读TSREval.py跑完后通常会输出测试集准确率和混淆矩阵。准确率只是一个总数真正要看的是混淆矩阵里哪些类被互相认错。交通标志里最容易混的是「限速 30」和「限速 50」这种数字相近的以及「禁止通行」和「停车」这种形状相似的。如果混淆矩阵显示某两类互相错得特别多说明网络提取的特征还不足以区分它们可以考虑加深网络或对这两类做针对性增强。from sklearn.metrics import classification_report, confusion_matrix import numpy as np # 加载权重后预测 preds model.predict(test_images) pred_labels np.argmax(preds, axis1) true_labels np.argmax(test_labels, axis1) # 输出每类的精确率、召回率、F1 print(classification_report(true_labels, pred_labels, digits4)) cm confusion_matrix(true_labels, pred_labels) print(混淆矩阵形状:, cm.shape)逻辑说明classification_report会按类别列出 precision、recall、f1-score比一个总准确率信息量大得多。如果某个类的 recall 明显低于其他类说明这个类被漏检严重优先检查它的样本量是不是太少。confusion_matrix返回 43×43 的矩阵对角线是正确分类数非对角线是错分。你可以用np.argmax找出错分最多的类别对然后单独把那两类的图片调出来看通常能发现标注错误或图片质量太差的问题。注意评估时用的测试集必须和训练集完全隔离如果test_data.csv里的图片在训练阶段被读到过准确率会虚高答辩时被问到泛化能力就露馅了。5. 避坑与排查跑这份代码最容易翻车的五个地方5.1 报错「No module named tensorflow」或版本不兼容现象执行TSRTrain.py直接报模块找不到或者导入成功但运行时报AttributeError。原因通常是环境里没装 TensorFlow或者装的是 2.x 但代码用的是 1.x 的 API比如tf.placeholder。解决先确认 Python 版本这份代码一般跑在 Python 3.7 到 3.9 上然后pip install tensorflow2.x对应版本。如果代码里混用了 Keras 独立包和 tf.keras统一改成from tensorflow.keras import ...避免两套 API 打架。5.2 训练 loss 一直是 nan现象第一个 epoch 打印出来 loss 就是 nan准确率不动。原因通常是学习率太大、输入数据里有 NaN、或者标签编码和损失函数不匹配。解决先把学习率降到 0.0001 试一轮如果还是 nan检查预处理后的数组里有没有np.nan用np.isnan(images).any()排查。再确认标签是 one-hot 还是整数one-hot 配categorical_crossentropy整数配sparse_categorical_crossentropy配错了不会报错但会算出 nan。5.3 准确率卡在 5% 左右不动现象训练几十轮准确率一直在随机猜测水平43 类随机猜约 2.3%5% 说明模型没学到东西。原因最常见的是标签和图片错位CSV 里路径顺序和标签顺序不一致模型在学随机噪声。解决手动抽几张图用cv2.imread读出来显示对照 CSV 里的标签看是否一致。另一个可能是图片根本没读进去TSRInput.py里路径拼接错了打印一下实际读取的文件数量如果远小于预期就是路径问题。5.4 显存不足OOM导致训练中断现象训练到一半报ResourceExhaustedError或CUDA out of memory。原因batch size 太大、图片尺寸太大、或者 GPU 上残留了之前的进程。解决先把 batch size 减半32×32 输入下 64 一般够用48×48 输入就要降到 32 或 16。然后在命令行用nvidia-smi看有没有僵尸进程占着显存有的话 kill 掉再重跑。如果用的是 CPU 训练把batch_size设小一点也能避免内存溢出。5.5 评估准确率远低于训练准确率现象训练集准确率 99%测试集只有 70% 多。原因过拟合模型记住了训练样本但没学到泛化特征。解决加 Dropout、加 L2 正则、做数据增强随机旋转、平移、亮度调整或者直接减少网络参数量。交通标志识别里数据增强很有效因为实拍图片本来就有各种角度和光照变化增强后的数据更接近真实分布。如果增强后还是差距大检查训练集和测试集是不是来自不同分布比如训练集全是清晰图、测试集全是模糊图。6. 进阶技巧把这份代码改成你自己的交通标志识别项目跑通默认配置只是第一步真正有价值的是知道怎么改。我一般会从三个方向动手换输入尺寸、加数据增强、换 backbone。换输入尺寸最直接。把Preprocessing.py和TSRCnn.py里的IMG_SIZE从 32 改成 48准确率通常能涨 2 到 4 个百分点代价是训练时间增加约一倍。48×48 下 batch size 要相应降到 32否则显存吃紧。改完记得重新生成 npy 文件不然读到的还是旧尺寸数据。数据增强用 Keras 的ImageDataGenerator最省事不用改数据管道直接在fit的时候传进去from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rotation_range15, # 随机旋转 ±15 度交通标志拍摄角度有偏差 width_shift_range0.1, # 水平平移 10% height_shift_range0.1, brightness_range[0.8, 1.2], # 亮度扰动应对不同光照 zoom_range0.1 ) datagen.fit(train_images) model.fit(datagen.flow(train_images, train_labels, batch_size64), validation_data(val_images, val_labels), epochs30, callbackscallbacks)参数说明rotation_range不要设太大交通标志旋转超过 20 度就不符合实际场景了。brightness_range对交通标志特别有用因为实拍图片有逆光、阴影、过曝各种情况。zoom_range模拟远近变化。增强只对训练集做验证集和测试集保持原样否则评估结果不可信。换 backbone 是提升上限最大的做法。把TSRCnn.py里的自定义卷积层换成预训练的 MobileNetV2 或 ResNet50用include_topFalse去掉原分类头接上自己的 43 类输出层。注意预训练权重是在 ImageNet 上学的输入尺寸通常要求 96×96 以上所以IMG_SIZE要同步调大。冻结前面的卷积层只训练新加的分类头跑几轮后再解冻部分层做微调这样在小数据集上不容易过拟合。验证改动是否有效我习惯固定一个随机种子保证每次跑的数据划分一致然后对比改动前后的测试集准确率和混淆矩阵。只看一个总数容易被波动误导分类报告里每类的 F1 变化才是真正说明问题的指标。从那以后我每次改网络结构或预处理参数都强制走一遍「固定种子 → 跑三次取平均 → 对比分类报告」的流程单次跑出来的高分大概率是运气。希望帮到你。本文还有配套的精品资源点击获取