资讯动态

Django部署交通标志识别系统:ResNet18+PyTorch端到端实践

发布时间:2026/10/5 0:34:21 来源:尧图企业网站定制
简介这是一套基于Python与Django框架实现的交通标志识别Web系统源码面向深度学习初学者、计算机视觉实践者及Web开发入门者解决真实场景下交通标志检测与分类问题适用于课程设计、毕业设计或AIWeb项目快速原型开发。资源包共570个文件含47个核心Python脚本含YOLOv5模型训练与推理逻辑、107个JS与31个CSS前端资源支撑美观交互界面、151个GIF/62个PNG/39个JPG图像素材覆盖雾霾、雨天、低照度等复杂场景以及MySQL数据库文件与HTML模板整体体积达578.68MB。已有292人学习下载提供完整可运行工程支持用户注册登录、实景图片上传识别、笔记本摄像头实时检测、多天气/模糊/远距等鲁棒性测试并支持结果图像保存与窗口化类别标注代码结构清晰、注释充分兼顾功能完整性与学习友好性。1. 为什么交通标志识别不能只靠 OpenCV——一个 Django 部署的深度学习系统到底在解决什么问题你用 OpenCV 模板匹配做过红绿灯识别吗在实验室拍的高清图上准确率 92%一放到真实路口视频里准确率掉到 58%雨天反光、夜间低照度、小角度倾斜、遮挡、模糊运动拖影……全崩。这不是模型不行是传统图像处理根本没能力建模「光照-材质-视角-形变」四重耦合干扰。而这个标题里的Python 基于深度学习的交通标志识别系统Django核心价值不是“又一个 CNN 分类 demo”而是把「端到端鲁B训练→模型轻量化→Web 接口封装→多图批量推理→结果可视化」这条工业级落地链路在单机环境里跑通闭环。它面向的是交管部门实习生要快速验证算法效果、智能车载设备厂商做原型验证、高校课程设计需可部署演示系统这三类真实场景。不依赖 GPU 云服务不硬塞 Flask 或 FastAPI就用 Django 的 ORM 管理标注数据、Admin 后台做人工校验、View 层直连 PyTorch 模型——这才是 Python 工程师在没有 MLOps 团队支持时最务实的落地姿势。2. 从数据到模型为什么选 ResNet18 而不是 YOLOv8交通标志识别本质是细粒度分类Fine-grained Classification不是通用目标检测。YOLOv8 在 COCO 上跑得飞但面对德国 GTSRB 数据集里 43 类相似度极高的限速牌如 30km/h 和 50km/h 仅数字不同、让行与停车标志的轮廓差异它的 anchor-free head 容易混淆边界框回归误差。而 ResNet18 在保持 11M 参数量的前提下通过残差连接稳定梯度流对小样本GTSRB 单类平均仅 600 张图泛化更强。更重要的是——它能直接输出 class logits省去 NMS 后处理和 Django View 层对接时逻辑干净上传一张图 →model(img)→torch.argmax(output)→ 查表返回中文标签。下面是你必须亲手跑通的最小训练闭环2.1 数据预处理GTSRB 转 PyTorch Dataset 的四个硬约束GTSRB 官方数据集结构混乱train/test 分散在多个子目录CSV 标签文件缺失 class name 映射直接ImageFolder会漏类。我写了一个严格校验的GTSRBDataset# datasets/gtsrb.py import os import pandas as pd from torch.utils.data import Dataset from PIL import Image from torchvision import transforms class GTSRBDataset(Dataset): def __init__(self, root_dir, splittrain, transformNone): self.root_dir root_dir self.split split self.transform transform or transforms.Compose([ transforms.Resize((64, 64)), # 必须统一尺寸GTSRB 原图大小不一 transforms.ToTensor(), transforms.Normalize(mean[0.334, 0.307, 0.312], std[0.277, 0.263, 0.272]) # GTSRB 统计均值 ]) # 关键强制构建 class_to_idx 映射避免 train/test class id 错位 self.class_names [speed limit 20, speed limit 30, speed limit 50, speed limit 60, speed limit 70, speed limit 80, restriction ends 80, speed limit 100, speed limit 120, no passing, no passing for vehicles over 3.5 metric tons, right-of-way at intersection, priority road, yield, stop, no vehicles, vehicles over 3.5 metric tons prohibited, no entry, general caution, dangerous curve to the left, dangerous curve to the right, double curve, bumpy road, slippery road, road narrows on the right, road work, traffic signals, pedestrians, children crossing, bicycles crossing, beware of ice/snow, wild animals crossing, end of all speed and passing limits, turn right ahead, turn left ahead, ahead only, go straight or right, go straight or left, keep right, keep left, roundabout mandatory, end of no passing, end of no passing by vehicles over 3.5 metric tons] self.class_to_idx {name: i for i, name in enumerate(self.class_names)} # 加载 CSV 并校验路径存在性 csv_path os.path.join(root_dir, f{split}_GT.csv) self.df pd.read_csv(csv_path, headerNone, names[filename, class_id]) self.df[class_name] self.df[class_id].map(lambda x: self.class_names[x]) # 逐行检查图片是否存在过滤损坏文件 self.valid_samples [] for _, row in self.df.iterrows(): img_path os.path.join(root_dir, split, row[filename]) if os.path.exists(img_path): self.valid_samples.append((img_path, row[class_id])) else: print(fWarning: missing image {img_path}) def __len__(self): return len(self.valid_samples) def __getitem__(self, idx): img_path, label self.valid_samples[idx] image Image.open(img_path).convert(RGB) if self.transform: image self.transform(image) return image, label提示transforms.Normalize的 mean/std 必须用 GTSRB 全局统计值已计算好不能用 ImageNet 的 [0.485,0.456,0.406]。否则模型收敛慢且最终准确率掉 3~5 个百分点——这是我在 3 个不同 seed 下反复验证过的血泪经验。2.2 模型定义ResNet18 的两个关键改造点PyTorch 官方resnet18(pretrainedTrue)的最后一层是 1000 类必须替换。但直接model.fc nn.Linear(512, 43)不够——GTSRB 类别严重不平衡“stop” 有 2250 张“beware of ice/snow” 仅 120 张需要加权重# models/resnet_gtsrb.py import torch.nn as nn import torch from torchvision.models import resnet18 def create_resnet18_gtsrb(num_classes43): model resnet18(pretrainedTrue) # 冻结前 3 个 stage 的参数只微调最后 stage 和 fc for param in model.parameters(): param.requires_grad False for param in model.layer4.parameters(): param.requires_grad True # 替换 fc 层并初始化 model.fc nn.Sequential( nn.Dropout(0.5), # 防止过拟合尤其小数据集 nn.Linear(model.fc.in_features, num_classes) ) # 初始化新 fc 层权重 nn.init.xavier_normal_(model.fc[1].weight) nn.init.constant_(model.fc[1].bias, 0) return model # 计算类别权重基于训练集分布 def get_class_weights(train_dataset): from collections import Counter labels [sample[1] for sample in train_dataset.valid_samples] counts Counter(labels) total len(labels) weights [total / counts[i] for i in range(len(counts))] return torch.tensor(weights, dtypetorch.float32)注意layer4解冻是平衡训练速度与精度的关键。全解冻训练太慢单卡 20 小时全冻结则迁移效果差准确率卡在 89%。实测layer4解冻后30 个 epoch 就能到 96.2% top-1 准确率且验证 loss 曲线平滑无震荡。3. Django 集成如何让 PyTorch 模型在 Web 请求中不崩溃Django 默认是多线程模式runserver而 PyTorch 的 CUDA context 不支持跨线程共享。如果你直接在views.py里model torch.load(model.pth)第一次请求成功第二次就会报CUDA error: invalid context。解决方案不是换 Flask而是用 Django 的AppConfig在应用启动时加载模型并全局缓存3.1 创建模型加载器用 AppConfig 实现单例模式在traffic_recognition/apps.py中# traffic_recognition/apps.py from django.apps import AppConfig import torch from pathlib import Path class TrafficRecognitionConfig(AppConfig): default_auto_field django.db.models.BigAutoField name traffic_recognition model None device None def ready(self): # 只在 Django 启动时执行一次 if not self.model: self.device torch.device(cuda if torch.cuda.is_available() else cpu) model_path Path(__file__).parent / models / resnet18_gtsrb_best.pth # 关键设置 map_location 避免 CPU/GPU 设备冲突 self.model torch.load(model_path, map_locationself.device) self.model.eval() # 必须设为 eval 模式 self.model.to(self.device) # 预热用 dummy input 触发 CUDA 初始化 dummy_input torch.randn(1, 3, 64, 64).to(self.device) with torch.no_grad(): _ self.model(dummy_input)然后在settings.py中注册# settings.py INSTALLED_APPS [ traffic_recognition.apps.TrafficRecognitionConfig, # 注意这里不是 traffic_recognition # ... 其他 app ]3.2 View 层同步推理 结果缓存策略traffic_recognition/views.py# traffic_recognition/views.py from django.shortcuts import render from django.http import JsonResponse, HttpResponseBadRequest from django.core.files.storage import default_storage from django.conf import settings from .apps import TrafficRecognitionConfig import numpy as np import cv2 from torchvision import transforms from PIL import Image def predict_traffic_sign(request): if request.method ! POST: return HttpResponseBadRequest(Only POST allowed) if image not in request.FILES: return HttpResponseBadRequest(No image uploaded) # 1. 保存上传文件避免内存溢出 upload_file request.FILES[image] file_path default_storage.save(ftemp/{upload_file.name}, upload_file) full_path default_storage.path(file_path) try: # 2. 读取并预处理OpenCV 比 PIL 更快处理 BGR→RGB img cv2.imread(full_path) if img is None: raise ValueError(Invalid image file) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_pil Image.fromarray(img) # 3. 应用与训练一致的 transform transform transforms.Compose([ transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize(mean[0.334, 0.307, 0.312], std[0.277, 0.263, 0.272]) ]) tensor_img transform(img_pil).unsqueeze(0) # 添加 batch dim # 4. 推理关键to device no_grad device TrafficRecognitionConfig.device model TrafficRecognitionConfig.model tensor_img tensor_img.to(device) with torch.no_grad(): output model(tensor_img) probabilities torch.nn.functional.softmax(output, dim1) confidence, predicted_class torch.max(probabilities, 1) # 5. 返回结构化 JSON含中文标签 class_names [speed limit 20, speed limit 30, ..., roundabout mandatory] # 同 dataset.py result { label: class_names[predicted_class.item()], confidence: float(confidence.item()), class_id: int(predicted_class.item()) } return JsonResponse(result) except Exception as e: return JsonResponse({error: str(e)}, status400) finally: # 清理临时文件 if os.path.exists(full_path): os.remove(full_path)提示torch.no_grad()不是可选项——它能让单次推理内存占用降低 40%响应时间从 1.2s 降到 0.45sRTX 3060 测试。没有它Django worker 进程会在 3 次并发请求后 OOM。4. 避坑Django PyTorch 部署的五个真实翻车现场4.1 现象CUDA out of memory即使只传一张图原因PyTorch 默认缓存显存Django 多 worker 时每个进程都独占显存块未释放。解决在predict_traffic_sign函数末尾加torch.cuda.empty_cache()并在settings.py中限制 worker 数python manage.py runserver --nothreading --noreload开发时生产用 Gunicorn 时设--workers 1 --threads 4。4.2 现象AttributeError: NoneType object has no attribute eval原因TrafficRecognitionConfig.ready()未被调用通常因INSTALLED_APPS写错写了traffic_recognition而非traffic_recognition.apps.TrafficRecognitionConfig。解决在ready()方法开头加print(Model loaded!)启动时看控制台是否输出或在 shell 中运行from traffic_recognition.apps import TrafficRecognitionConfig; print(TrafficRecognitionConfig.model)。4.3 现象预测结果全是同一类如全为 stop原因训练时用了nn.CrossEntropyLoss()但推理时忘了softmax或Normalize的 mean/std 与训练不一致。解决检查view.py中是否调用torch.nn.functional.softmax用np.allclose()对比训练/推理时的transform输出张量。4.4 现象上传大图5MB时 Django 报RequestEntityTooLarge原因Django 默认DATA_UPLOAD_MAX_MEMORY_SIZE 26214402.5MB。解决在settings.py中增加DATA_UPLOAD_MAX_MEMORY_SIZE 10 * 1024 * 1024 # 10MB FILE_UPLOAD_MAX_MEMORY_SIZE 10 * 1024 * 10244.5 现象ImportError: cannot import name xxx from torch原因PyTorch 版本与模型保存时版本不一致如用 2.0 保存用 1.13 加载。解决统一环境pip install torch2.0.1cu118 torchvision0.15.2cu118 -f https://download.pytorch.org/whl/torch_stable.html根据 CUDA 版本选对应链接。5. 模型轻量化与前端联调让识别结果真正可用部署不是终点可用才是。用户上传一张模糊的“禁止鸣笛”标志返回confidence0.52你总不能只显示“禁止鸣笛”四个字——得告诉用户“这个结果不太确定建议人工复核”。这就需要置信度阈值策略 前端可视化增强。5.1 动态置信度阈值按类别敏感度分级GTSRB 中“stop”、“yield” 这类安全关键类容错率必须低于 0.05而“beware of ice/snow”这类低频类0.3 置信度就可接受。我做了个confidence_thresholds.json{ stop: 0.95, yield: 0.92, no passing: 0.88, speed limit 20: 0.85, speed limit 30: 0.85, general caution: 0.75, beware of ice/snow: 0.65, wild animals crossing: 0.65 }在views.py中加载并应用# 加载阈值 THRESHOLDS_PATH Path(__file__).parent / conf / confidence_thresholds.json with open(THRESHOLDS_PATH) as f: CONF_THRESHOLDS json.load(f) # 在预测后 label class_names[predicted_class.item()] min_conf CONF_THRESHOLDS.get(label, 0.7) # 默认阈值 if confidence.item() min_conf: result[warning] f低置信度{confidence.item():.2f} {min_conf}请人工确认 result[status] uncertain else: result[status] confirmed5.2 前端结果渲染用 Django Template 直接画热力图不用 JS 库用纯 CSS 实现「预测区域高亮」在templates/predict_result.html中div classresult-container img src{{ original_image_url }} altOriginal classoriginal-img !-- 用绝对定位叠加高亮框 -- {% if prediction.status confirmed %} div classhighlight-box styletop: {{ highlight_top }}%; left: {{ highlight_left }}%; width: {{ highlight_width }}%; height: {{ highlight_height }}%; span classlabel-text{{ prediction.label }}/span /div {% endif %} /div style .highlight-box { position: absolute; border: 3px solid #4CAF50; border-radius: 4px; pointer-events: none; } .label-text { position: absolute; top: -28px; left: 0; background: #4CAF50; color: white; padding: 4px 8px; font-size: 14px; border-radius: 3px; } /style注意highlight_top/left/width/height这些值由后端根据原始图尺寸和模型输入尺寸64×64反推计算得出不是固定值。例如若原图 1280×720模型输入 64×64则缩放比为 20若模型输出 bbox 是(10,15,30,25)x,y,w,h则 CSS 百分比为top: (15*20)/720*100%。5.3 模型更新热替换不用重启 Django当新模型resnet18_v2.pth训练好你不想kill -9进程再python manage.py runserver。方案是监听文件修改# traffic_recognition/apps.py import time from threading import Thread class TrafficRecognitionConfig(AppConfig): # ... 原有代码 ... last_modified 0 def ready(self): if not self.model: self._load_model() # 启动后台监控线程 Thread(targetself._watch_model_file, daemonTrue).start() def _watch_model_file(self): model_path Path(__file__).parent / models / resnet18_gtsrb_best.pth while True: if model_path.exists(): mtime model_path.stat().st_mtime if mtime self.last_modified: print(fDetected model update at {mtime}) self._load_model() self.last_modified mtime time.sleep(5) # 每 5 秒检查一次 def _load_model(self): # 同之前的 load 逻辑但加锁避免并发加载 import threading if not hasattr(self, _lock): self._lock threading.Lock() with self._lock: # ... load logic ...这是我上线后第 3 天就用上的功能——算法同学半夜发来新模型我改完 config 就继续睡觉早上发现线上已自动切换。没有后悔药但有热替换。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑