简介本资源为基于深度学习的交通标志识别系统毕业设计源码包面向计算机相关专业需要完成毕业设计或课程设计的学生。系统采用Python开发使用YOLOv5训练检测模型结合Django搭建前后端MySQL存储数据可实现实景图片与笔记本摄像头实时检测交通标志并分类同时支持低像素模糊、远距离、雾霾、雨天、黑夜等特殊天气与场景下的识别检测结果可保存另存界面美观简洁。压缩包共570个文件约578.97MB包含47个py源码、62个js脚本、31个css样式、151个gif与107个png图片资源、2个pth模型权重、1个sql数据库文件及说明文档等覆盖完整前后端与数据库结构。已有66人学习下载适合作为毕业设计参考模板帮助读者快速理解YOLOv5训练流程、Django项目组织方式与交通标志分类实现思路并在此基础上完成二次开发与功能扩展。1. 交通标志识别系统为什么值得作为毕业设计落地城市道路上的限速牌、禁令标、指示牌人眼扫过去零点几秒就能分辨但要让机器在车载摄像头抖动、逆光、雨雾、部分遮挡的条件下稳定认出来难度完全不是一个量级。基于深度学习的交通标志识别系统本质上是把「检测 分类」这条链路做扎实先用卷积网络把画面里的标志框出来再判断它属于哪一类最后把结果落到一个能查询、能管理、能演示的 Web 系统里。用 Python 做算法、Django 做后端、MySQL 存数据是毕业设计里最常见也最稳的一套组合因为它把深度学习模型和完整前后端串成了一条可展示、可答辩、可继续扩展的闭环。这套方案适合两类人一类是刚入门深度学习、需要一个真实数据集练手的同学另一类是想把模型真正接进业务系统、而不是只跑个 notebook 就交差的开发者。下面我按自己踩过的路把选型、训练、后端对接和排错讲清楚。2. 数据集、模型与 Django 后端的选型逻辑2.1 交通标志数据集怎么选、怎么划分做交通标志识别数据集决定了天花板。公开数据集里GTSRBGerman Traffic Sign Recognition Benchmark是最常被拿来练手的它包含 40 多个类别、几万张实拍图图片尺寸不一、光照差异大正好能暴露真实场景的问题。如果你的题目要求做检测而不只是分类那就要用带标注框的数据集比如把 GTSRB 的类别信息配合检测标注使用或者直接找带 bbox 的交通标志数据。我一般会按 7:2:1 划分训练集、验证集、测试集并且按类别分层抽样避免某个类别在验证集里一张都没有。划分脚本可以这样写import os import random import shutil from collections import defaultdict # 原始数据按类别放在 data/raw/class_id/ 下 RAW_DIR data/raw OUT_DIR data/split SPLIT {train: 0.7, val: 0.2, test: 0.1} random.seed(42) # 先按类别收集所有文件 class_files defaultdict(list) for cls in os.listdir(RAW_DIR): cls_dir os.path.join(RAW_DIR, cls) if not os.path.isdir(cls_dir): continue for f in os.listdir(cls_dir): if f.lower().endswith((.png, .jpg, .jpeg, .ppm)): class_files[cls].append(os.path.join(cls_dir, f)) # 分层抽样保证每个类别在三个集合里都有样本 for cls, files in class_files.items(): random.shuffle(files) n len(files) n_train int(n * SPLIT[train]) n_val int(n * SPLIT[val]) buckets { train: files[:n_train], val: files[n_train:n_train n_val], test: files[n_train n_val:], } for split, items in buckets.items(): dst_dir os.path.join(OUT_DIR, split, cls) os.makedirs(dst_dir, exist_okTrue) for src in items: shutil.copy(src, os.path.join(dst_dir, os.path.basename(src))) print(划分完成)这段代码的关键点是random.seed(42)保证可复现defaultdict(list)按类别聚合分层抽样避免类别倾斜。参数上SPLIT可以按数据量调整数据少的时候验证集比例可以提到 0.25。注意 GTSRB 原始格式有的是.ppm读取时要用 PIL 或 OpenCV 兼容处理否则会在训练时报「cannot identify image file」这种让人抓狂的错。2.2 用 CNN 做分类还是用检测模型做定位这是选型里最容易纠结的一步。如果你的系统只需要「输入一张已经裁好的标志图输出类别」那用 CNN 分类就够了ResNet、MobileNet、甚至自己搭一个五六层的卷积网络都能跑出不错的效果。但如果输入是整张道路图标志只占画面一小块就必须上检测模型常见做法是 YOLO 系列或 Faster R-CNN。我一般会这样判断答辩演示如果只要求上传单张标志图分类模型足够如果要求上传整张街景图并框出标志就必须做检测。检测模型训练成本高、标注要求严毕业设计周期紧的话可以先用分类模型把流程跑通再考虑加检测。分类模型的核心结构可以这样搭import torch import torch.nn as nn class TrafficSignCNN(nn.Module): def __init__(self, num_classes43): super().__init__() # 输入统一 resize 到 64x64 self.features nn.Sequential( nn.Conv2d(3, 32, 3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), # 64 - 32 nn.Conv2d(32, 64, 3, padding1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), # 32 - 16 nn.Conv2d(64, 128, 3, padding1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2), # 16 - 8 ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 8 * 8, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, num_classes), ) def forward(self, x): return self.classifier(self.features(x))num_classes按你的数据集类别数改GTSRB 是 43 类。BatchNorm2d放在卷积后、激活前能明显稳住训练Dropout(0.5)放在全连接层前防止小数据集过拟合。输入尺寸 64x64 是速度和精度的折中想更准可以上 96 或 128但显存和训练时间会涨。2.3 Django 与 MySQL 在系统里各承担什么角色Django 在这套系统里不是配角。它负责用户登录、图片上传、调用模型推理、把识别结果写进 MySQL、再在前端页面展示历史记录。MySQL 存的是结构化数据用户表、识别记录表、标志类别字典表。模型本身不放进数据库而是以文件形式存在项目目录里Django 启动时加载一次避免每次请求都重新读模型。常见做法是把模型加载放在 Django 的apps.py的ready()里或者用一个单例工具类管理。数据库表设计上识别记录表至少要有图片路径、识别类别、置信度、创建时间、所属用户。这样前端才能做「按时间排序」「按类别筛选」这类功能也方便答辩时展示数据管理能力。3. 从训练到 Django 接口的完整落地步骤3.1 训练脚本与关键超参设置训练部分我习惯单独写一个train.py和 Django 项目解耦训练完把权重文件拷进 Django 的models/目录。核心训练循环如下import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms # 数据增强训练集加随机翻转和颜色抖动验证集只做 resize 和归一化 train_tf transforms.Compose([ transforms.Resize((64, 64)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.3, contrast0.3), transforms.ToTensor(), transforms.Normalize([0.5] * 3, [0.5] * 3), ]) val_tf transforms.Compose([ transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize([0.5] * 3, [0.5] * 3), ]) train_ds datasets.ImageFolder(data/split/train, transformtrain_tf) val_ds datasets.ImageFolder(data/split/val, transformval_tf) train_loader DataLoader(train_ds, batch_size64, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size64, shuffleFalse, num_workers4) device torch.device(cuda if torch.cuda.is_available() else cpu) model TrafficSignCNN(num_classeslen(train_ds.classes)).to(device) criterion torch.nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5) for epoch in range(30): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() scheduler.step() # 每个 epoch 后在验证集上评估保存最优权重 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 {epoch}, val_acc{correct / total:.4f}) torch.save(model.state_dict(), best.pth)超参上lr1e-3配 Adam 是稳妥起点weight_decay1e-4抑制过拟合StepLR每 10 个 epoch 降一半学习率。batch_size64在 8G 显存上基本够用显存小就降到 32。注意num_workers在 Windows 上有时会报多进程错误设成 0 最保险代价是加载慢一点。3.2 把模型接进 Django 的推理接口Django 里我一般建一个inference.py工具模块负责加载模型和单张图片推理视图函数只做参数校验和数据库写入。这样职责清晰也方便单元测试。# recognition/inference.py import torch from PIL import Image from torchvision import transforms from .models import TrafficSignCNN # 模型定义放在 Django app 内 class Predictor: _instance None def __new__(cls, weight_pathmodels/best.pth, num_classes43): if cls._instance is None: cls._instance super().__new__(cls) cls._instance.model TrafficSignCNN(num_classes) cls._instance.model.load_state_dict( torch.load(weight_path, map_locationcpu) ) cls._instance.model.eval() cls._instance.tf transforms.Compose([ transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize([0.5] * 3, [0.5] * 3), ]) return cls._instance def predict(self, image_file): img Image.open(image_file).convert(RGB) tensor self.tf(img).unsqueeze(0) # 增加 batch 维度 with torch.no_grad(): logits self.model(tensor) prob torch.softmax(logits, dim1) conf, idx prob.max(1) return idx.item(), conf.item()__new__里做单例保证模型只加载一次这是 Django 多请求场景下的关键优化。map_locationcpu让权重在没有 GPU 的机器上也能加载答辩电脑不一定有显卡。unsqueeze(0)补上 batch 维度否则会报维度不匹配。视图层这样写# recognition/views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from .inference import Predictor from .models import RecognitionRecord csrf_exempt def recognize(request): if request.method ! POST or image not in request.FILES: return JsonResponse({code: 400, msg: 请上传图片}) image request.FILES[image] idx, conf Predictor().predict(image) # 落库方便前端展示历史记录 RecognitionRecord.objects.create( image_nameimage.name, class_ididx, confidenceround(conf, 4), ) return JsonResponse({code: 0, class_id: idx, confidence: round(conf, 4)})csrf_exempt是为了方便前端用 fetch 直接调正式项目里应该用 token 校验。RecognitionRecord.objects.create是 Django ORM 的写法对应 MySQL 里的一张表字段在models.py里定义。3.3 MySQL 表结构与 Django 模型映射数据库这块我一般先设计表再写模型。识别记录表的核心字段如下字段名类型说明idBIGINT 自增主键image_nameVARCHAR(255)上传文件名class_idINT识别出的类别编号confidenceFLOAT置信度0~1created_atDATETIME创建时间默认当前对应的 Django 模型# recognition/models.py from django.db import models class RecognitionRecord(models.Model): image_name models.CharField(max_length255) class_id models.IntegerField() confidence models.FloatField() created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table recognition_record ordering [-created_at] # 默认按时间倒序auto_now_addTrue让创建时间自动写入不用手动赋值。ordering让查询默认按时间倒序前端展示历史记录时不用再写排序。迁移命令是python manage.py makemigrations加python manage.py migrateMySQL 连接配置写在settings.py的DATABASES里注意字符集设成utf8mb4否则中文文件名会乱码。4. 部署、性能与常见问题排查4.1 本地跑通到部署的注意点本地开发用python manage.py runserver就够了但答辩或演示时如果要用生产级方式Windows 上常见做法是 waitress 加 nginxLinux 上用 gunicorn 加 nginx。模型推理是 CPU 密集型如果并发请求多建议把推理放到独立进程或加缓存避免阻塞 Django 主线程。静态文件在 Django 里容易翻车尤其是static目录下图片显示不出来。检查三点STATIC_URL和STATICFILES_DIRS配置是否正确、模板里有没有用{% static %}标签、DEBUGFalse时有没有配STATIC_ROOT并执行collectstatic。这三点漏一个页面就是白板。4.2 识别准确率上不去的排查思路准确率卡在 80% 上不去先别急着换模型。按这个顺序查第一看验证集和训练集的准确率差距差距大是过拟合加数据增强或 Dropout第二看混淆矩阵是不是某几个相似类别总混比如限速 50 和限速 60这种要针对性补样本第三看输入预处理训练和推理的归一化参数必须完全一致不一致就是玄学掉点第四学习率是不是太大导致震荡画 loss 曲线一看便知。4.3 避坑与常见问题记录现象训练 loss 一直不降准确率停在随机水平。原因通常是标签和图片没对齐ImageFolder依赖目录名做类别如果目录结构错了标签全乱。解决打印train_ds.classes和train_ds.class_to_idx确认类别映射正确。现象Django 启动时报模型文件找不到。原因是权重路径用了相对路径而 Django 的工作目录和训练脚本不一样。解决用os.path.join(BASE_DIR, models/best.pth)拼绝对路径BASE_DIR在settings.py里已有定义。现象上传图片后接口返回 500日志显示 PIL 无法识别。原因是上传的文件不是真实图片或者格式是 WebP 而 PIL 没装对应插件。解决在视图里先校验文件后缀和Image.open是否抛异常捕获后返回友好提示。现象MySQL 插入中文文件名报错或乱码。原因是数据库或表字符集不是utf8mb4。解决建库时指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ciDjango 的DATABASES配置里加OPTIONS: {charset: utf8mb4}。现象推理速度慢单张图要好几秒。原因是每次请求都重新加载模型或者用了 CPU 且模型太大。解决用单例保证模型只加载一次模型换成 MobileNet 这类轻量结构输入尺寸从 128 降到 64。5. 让系统更像成品的几个进阶技巧把基本流程跑通之后想让这套交通标志识别系统在答辩里更站得住可以加两个东西。第一个是置信度阈值过滤当模型输出的最大概率低于某个阈值比如 0.6就不返回具体类别而是返回「无法确定」这样能避免把模糊图片硬判成某个类别显得系统更可靠。实现上就是在predict里加一个判断def predict(self, image_file, threshold0.6): img Image.open(image_file).convert(RGB) tensor self.tf(img).unsqueeze(0) with torch.no_grad(): prob torch.softmax(self.model(tensor), dim1) conf, idx prob.max(1) if conf.item() threshold: return -1, conf.item() # -1 表示不确定 return idx.item(), conf.item()阈值不是拍脑袋定的我一般会在验证集上跑一遍画出置信度和准确率的关系曲线找一个准确率还高、但能过滤掉大部分错误样本的点。0.6 是个常见起点数据干净可以降到 0.5数据噪声大就提到 0.7。第二个是批量测试与结果导出。答辩时老师常问「你这模型在测试集上到底多少准确率」与其现场跑不如提前写个脚本把测试集全跑一遍输出混淆矩阵和每类准确率存成 CSV。这样既能证明模型泛化能力也能在论文里直接放表格。我习惯用sklearn.metrics.classification_report生成报告配合confusion_matrix画热力图一眼就能看出哪些类别是短板。最后一个习惯训练和推理的预处理代码一定要抽成同一个函数或同一个配置。我踩过最冤的坑就是训练时用了Normalize([0.5]*3, [0.5]*3)推理时忘了写结果线上准确率比验证集低了十几个点查了半天才定位到。现在我都把 transform 定义在一个config.py里训练和 Django 都 import 同一份从根上杜绝这种不一致。这套系统不难难的是每个环节都对齐、都可复现把细节抠住毕业设计就能从「能跑」变成「能打」。希望帮到你。本文还有配套的精品资源点击获取
