基于深度学习的交通标志识别系统:Python+Django+MySQL毕业设计实战
2026/9/24 21:01:13 网站建设 项目流程

简介:本资源为基于深度学习的交通标志识别系统毕业设计源码包,面向计算机相关专业需要完成毕业设计或课程设计的学生。系统采用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 交通标志数据集怎么选、怎么划分

做交通标志识别,数据集决定了天花板。公开数据集里,GTSRB(German 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_ok=True) 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_classes=43): super().__init__() # 输入统一 resize 到 64x64 self.features = nn.Sequential( nn.Conv2d(3, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), # 64 -> 32 nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), # 32 -> 16 nn.Conv2d(64, 128, 3, padding=1), 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.pyready()里,或者用一个单例工具类管理。数据库表设计上,识别记录表至少要有:图片路径、识别类别、置信度、创建时间、所属用户。这样前端才能做「按时间排序」「按类别筛选」这类功能,也方便答辩时展示数据管理能力。

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(brightness=0.3, contrast=0.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", transform=train_tf) val_ds = datasets.ImageFolder("data/split/val", transform=val_tf) train_loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=64, shuffle=False, num_workers=4) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = TrafficSignCNN(num_classes=len(train_ds.classes)).to(device) criterion = torch.nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.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(f"epoch {epoch}, val_acc={correct / total:.4f}") torch.save(model.state_dict(), "best.pth")

超参上,lr=1e-3配 Adam 是稳妥起点,weight_decay=1e-4抑制过拟合,StepLR每 10 个 epoch 降一半学习率。batch_size=64在 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_path="models/best.pth", num_classes=43): 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_location="cpu") ) 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, dim=1) conf, idx = prob.max(1) return idx.item(), conf.item()

__new__里做单例,保证模型只加载一次,这是 Django 多请求场景下的关键优化。map_location="cpu"让权重在没有 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_name=image.name, class_id=idx, confidence=round(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~1
created_atDATETIME创建时间,默认当前

对应的 Django 模型:

# recognition/models.py from django.db import models class RecognitionRecord(models.Model): image_name = models.CharField(max_length=255) class_id = models.IntegerField() confidence = models.FloatField() created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "recognition_record" ordering = ["-created_at"] # 默认按时间倒序

auto_now_add=True让创建时间自动写入,不用手动赋值。ordering让查询默认按时间倒序,前端展示历史记录时不用再写排序。迁移命令是python manage.py makemigrationspython manage.py migrate,MySQL 连接配置写在settings.pyDATABASES里,注意字符集设成utf8mb4,否则中文文件名会乱码。

4. 部署、性能与常见问题排查

4.1 本地跑通到部署的注意点

本地开发用python manage.py runserver就够了,但答辩或演示时如果要用生产级方式,Windows 上常见做法是 waitress 加 nginx,Linux 上用 gunicorn 加 nginx。模型推理是 CPU 密集型,如果并发请求多,建议把推理放到独立进程或加缓存,避免阻塞 Django 主线程。

静态文件在 Django 里容易翻车,尤其是static目录下图片显示不出来。检查三点:STATIC_URLSTATICFILES_DIRS配置是否正确、模板里有没有用{% static %}标签、DEBUG=False时有没有配STATIC_ROOT并执行collectstatic。这三点漏一个,页面就是白板。

4.2 识别准确率上不去的排查思路

准确率卡在 80% 上不去,先别急着换模型。按这个顺序查:第一,看验证集和训练集的准确率差距,差距大是过拟合,加数据增强或 Dropout;第二,看混淆矩阵,是不是某几个相似类别总混,比如限速 50 和限速 60,这种要针对性补样本;第三,看输入预处理,训练和推理的归一化参数必须完全一致,不一致就是玄学掉点;第四,学习率是不是太大导致震荡,画 loss 曲线一看便知。

4.3 避坑与常见问题记录

现象:训练 loss 一直不降,准确率停在随机水平。原因通常是标签和图片没对齐,ImageFolder依赖目录名做类别,如果目录结构错了,标签全乱。解决:打印train_ds.classestrain_ds.class_to_idx,确认类别映射正确。

现象:Django 启动时报模型文件找不到。原因是权重路径用了相对路径,而 Django 的工作目录和训练脚本不一样。解决:用os.path.join(BASE_DIR, "models/best.pth")拼绝对路径,BASE_DIRsettings.py里已有定义。

现象:上传图片后接口返回 500,日志显示 PIL 无法识别。原因是上传的文件不是真实图片,或者格式是 WebP 而 PIL 没装对应插件。解决:在视图里先校验文件后缀和Image.open是否抛异常,捕获后返回友好提示。

现象:MySQL 插入中文文件名报错或乱码。原因是数据库或表字符集不是utf8mb4。解决:建库时指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,Django 的DATABASES配置里加"OPTIONS": {"charset": "utf8mb4"}

现象:推理速度慢,单张图要好几秒。原因是每次请求都重新加载模型,或者用了 CPU 且模型太大。解决:用单例保证模型只加载一次,模型换成 MobileNet 这类轻量结构,输入尺寸从 128 降到 64。

5. 让系统更像成品的几个进阶技巧

把基本流程跑通之后,想让这套交通标志识别系统在答辩里更站得住,可以加两个东西。第一个是置信度阈值过滤:当模型输出的最大概率低于某个阈值(比如 0.6),就不返回具体类别,而是返回「无法确定」,这样能避免把模糊图片硬判成某个类别,显得系统更可靠。实现上就是在predict里加一个判断:

def predict(self, image_file, threshold=0.6): img = Image.open(image_file).convert("RGB") tensor = self.tf(img).unsqueeze(0) with torch.no_grad(): prob = torch.softmax(self.model(tensor), dim=1) 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 同一份,从根上杜绝这种不一致。这套系统不难,难的是每个环节都对齐、都可复现,把细节抠住,毕业设计就能从「能跑」变成「能打」。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询