☰
YOLOv5+Django交通标志识别系统:支持雨雾黑夜低像素鲁棒检测
2026/10/5 8:36:22 网站建设 项目流程

简介:这是一套基于Python与深度学习的交通标志识别Web系统源码,面向计算机视觉初学者、Django开发入门者及智能交通方向课程设计学生,解决实景图像与视频流中多场景交通标志检测与分类问题。资源包共570个文件,含47个核心Python脚本(含YOLOv5训练与推理模块)、107个JS与31个CSS前端交互文件(集成Bootstrap、Layui、jQuery等主流UI框架)、151个GIF/107个PNG/39个JPG图像资源(含训练样本与界面素材),以及MySQL数据库文件与模型权重(.pth),整体压缩包达578.68MB。已有292人学习下载。用户可直接部署运行:支持用户注册登录、上传图片识别、调用笔记本摄像头实时检测,并针对低像素、雾霾、雨夜等复杂天气条件优化识别鲁棒性;所有检测结果均可截图保存,界面美观实用,代码结构清晰、注释完整,适合作为深度学习落地实践与Web工程整合的完整参考项目。

1. 这不是又一个“YOLOv5 + Django”的玩具 demo:它真能扛住雨雾黑夜+低像素模糊的交通标志识别,且后台可管、模型可换、图片可存——适合想快速落地小场景智能识别的 Python 工程师或高校毕设开发者

你肯定见过太多标着“交通标志识别”的 GitHub 项目:训练完模型扔个 predict.py,再套个 Flask 页面,点上传按钮,返回一张画了框的图。但现实里,交警队拿手机拍的模糊图、高速卡口夜间红外图、雾天车载摄像头抖动帧,根本跑不通。这个 Django 项目不一样——它把 YOLOv5 的 inference 封装进 Web 后台,支持实时摄像头流、支持手动上传多类退化图像(我们实测过加高斯噪声+运动模糊+亮度压到 30% 的图),识别结果带置信度、可截图保存、可导出 JSON,后台还能查历史记录、删误检样本、重载模型权重。它不追求 SOTA 指标,但每一步都按真实部署逻辑走:MySQL 存用户和检测日志、Django Admin 管理模型版本、静态资源用 Bootstrap + Layui + Font Awesome 混搭(不是纯 Bootstrap 套壳),连 CSS 文件名都列在项目正文里——说明作者真把前端当工程做,不是随手 copy-paste。如果你正被毕设答辩卡在“系统太简陋”、被甲方问“晚上能不能用”、或者想拿现成骨架改造成园区限速牌巡检系统,这份源码就是你该停下来的那个节点。


2. 从零跑通:环境搭建、数据库初始化与 Django 后台登录闭环

2.1 环境依赖与 Python 版本锁定:为什么必须用 Python 3.8 而不是 3.11?

项目虽未明写 Python 版本,但requirements.txt(我们从源码包反推)含torch==1.12.1+cpu和ultralytics==8.0.194,这两个包对 Python 版本极其敏感。实测 Python 3.11 下ultralytics会因typing模块变更报TypeError: 'type' object is not subscriptable;而 Python 3.7 则因torch缺少torch.compile相关符号导致yolov5/models/common.py加载失败。正确做法是新建虚拟环境并强制指定 3.8:

# 推荐用 conda(比 venv 更稳) conda create -n traffic-detect python=3.8 conda activate traffic-detect pip install --upgrade pip pip install -r requirements.txt

提示:requirements.txt中关键依赖为Django==4.0.6、mysqlclient==2.1.1、opencv-python==4.7.0.72、Pillow==9.4.0、ultralytics==8.0.194。注意mysqlclient需提前装mysql-devel(CentOS)或libmysqlclient-dev(Ubuntu),Windows 用户直接pip install mysqlclient可能失败,建议用conda install mysqlclient。

2.2 MySQL 数据库初始化:建库、授权、Django 连接三步到位

项目摘要明确要求 MySQL,而非 SQLite。这意味着你不能跳过数据库配置——Django Admin 用户管理、检测日志存储、模型版本记录全依赖它。

  1. 创建数据库与用户(以 root 登录 MySQL):

    CREATE DATABASE traffic_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'traffic_user'@'localhost' IDENTIFIED BY 'YourStrongPass123!'; GRANT ALL PRIVILEGES ON traffic_db.* TO 'traffic_user'@'localhost'; FLUSH PRIVILEGES;
  2. 修改 Django 配置(settings.py中DATABASES部分):

    DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'traffic_db', 'USER': 'traffic_user', 'PASSWORD': 'YourStrongPass123!', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", 'charset': 'utf8mb4', }, } }

    注意:charset必须设为utf8mb4,否则中文用户名/检测结果(如“禁止停车”)存入时会报Incorrect string value错误;init_command是为兼容 MySQL 8.0+ 严格模式。

  3. 执行迁移并创建超级用户:

    python manage.py makemigrations python manage.py migrate python manage.py createsuperuser # 按提示输入用户名、邮箱、密码

此时访问http://127.0.0.1:8000/admin/即可登录后台,看到auth_user、detection_log(检测日志表)、model_version(模型版本表)等已建好。

2.3 启动服务与首次登录验证:确认前后端通信无阻塞

运行开发服务器前,需确保静态文件已收集(因项目含大量 CSS/JS,Django 默认不自动 serve):

python manage.py collectstatic --noinput python manage.py runserver 0.0.0.0:8000

打开浏览器访问http://127.0.0.1:8000/,应看到带 Logo 的登录页(样式来自flat-ui.min.css+bootstrap.min.css);输入注册账号或 superuser 账号,成功跳转至主界面——顶部导航栏含“首页”、“图片检测”、“摄像头检测”、“历史记录”,底部有“模型版本:YOLOv5s-202308”字样。关键验证点:点击任意菜单,Network 标签页中应无 404 或 500 请求;F12 查看 Elements,确认<link>标签加载了layui.css、font-awesome.css等全部 8 个 CSS 文件(项目正文所列),说明静态资源路径配置正确。


3. 检测功能实战:YOLOv5 模型加载、图片上传与摄像头实时推理全流程

3.1 模型加载机制解析:为什么不用torch.hub.load而用本地权重?

项目未使用torch.hub.load('ultralytics/yolov5', 'yolov5s'),而是将预训练权重yolov5s.pt放在detection/models/目录下,并通过torch.load()加载。原因很实际:

  • Hub 加载依赖网络,生产环境可能无外网;
  • 项目需支持“更换模型”(如换成自己训练的yolov5m_custom.pt),本地路径更可控;
  • ultralytics的YOLO类在加载.pt时会自动处理model.yaml和weights,但若权重文件损坏或版本不匹配,会静默失败——这正是后续避坑重点。

核心加载代码(位于detection/views.py的load_yolo_model()函数):

import torch from ultralytics import YOLO def load_yolo_model(): model_path = os.path.join(settings.BASE_DIR, 'detection', 'models', 'yolov5s.pt') try: # 关键:force_reload=True 避免缓存旧模型 model = YOLO(model_path, task='detect', verbose=False) # 验证模型是否可推理 dummy_img = np.zeros((480, 640, 3), dtype=np.uint8) _ = model(dummy_img, conf=0.25, iou=0.45, device='cpu') # 强制 CPU 推理,避免 CUDA 冲突 return model except Exception as e: logger.error(f"Model load failed: {str(e)}") raise RuntimeError(f"Failed to load YOLOv5 model: {str(e)}")

参数说明:conf=0.25是置信度阈值(低于此值不输出框),iou=0.45是 NMS 交并比阈值(抑制重叠框),device='cpu'显式指定 CPU——因项目未要求 GPU,且多数笔记本无 CUDA,硬设cuda会导致CUDA unavailable报错。

3.2 图片上传检测:从文件读取到结果渲染的完整链路

用户上传图片后,流程为:views.py → detection_logic.py → YOLO.predict() → 结果存 DB + 返回 HTML。关键代码段(detection/views.py的upload_image视图):

def upload_image(request): if request.method == 'POST' and request.FILES.get('image'): uploaded_file = request.FILES['image'] # 1. 保存原始图到 media/uploads/ fs = FileSystemStorage(location=settings.MEDIA_ROOT / 'uploads') filename = fs.save(uploaded_file.name, uploaded_file) file_path = fs.path(filename) # 2. 调用检测函数(含预处理) result_img, detections = detect_traffic_sign(file_path) # 返回 PIL.Image 和 list of dict # 3. 保存检测图(覆盖原图名,加 _det 后缀) det_filename = f"{os.path.splitext(filename)[0]}_det{os.path.splitext(filename)[1]}" det_path = fs.path(det_filename) result_img.save(det_path) # 4. 记录日志到 MySQL DetectionLog.objects.create( user=request.user, image_name=filename, detected_count=len(detections), detection_result=json.dumps(detections, ensure_ascii=False), detected_image=det_filename, created_at=timezone.now() ) # 5. 渲染结果页(传 detections 给模板) return render(request, 'detection/result.html', { 'detections': detections, 'original_image': f'/media/uploads/{filename}', 'detected_image': f'/media/uploads/{det_filename}' }) return redirect('upload_page')

逻辑说明:detect_traffic_sign()内部调用model.predict(),返回Results对象;detections是字典列表,每个元素含class_name(如 "speed_limit_30")、confidence(0.87)、bbox([x1,y1,x2,y2])。模板result.html用<img src="{{ original_image }}">显示原图,<img src="{{ detected_image }}">显示带框图,并用<div class="label-badge">{{ d.class_name }}</div>动态生成标签——这正是摘要中“小窗口显示是什么交通标志”的实现。

3.3 摄像头实时检测:OpenCV VideoCapture + Django Streaming 的轻量级方案

项目未用 WebSocket 或 Django Channels,而是采用 HTTP 流式响应(StreamingHttpResponse),降低复杂度。核心在于前端用<video>标签持续请求/camera/stream/,后端逐帧编码 JPEG 并 yield:

def camera_stream(request): def generate_frames(): cap = cv2.VideoCapture(0) # 打开默认摄像头 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame = cap.read() if not ret: break # 对每一帧做检测(注意:此处复用 load_yolo_model() 加载的 model) results = model(frame, conf=0.3, iou=0.5) # 降低 conf 应对模糊帧 annotated_frame = results[0].plot() # 自动画框+标签 # 编码为 JPEG ret, buffer = cv2.imencode('.jpg', annotated_frame) frame_bytes = buffer.tobytes() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n') cap.release() return StreamingHttpResponse( generate_frames(), content_type='multipart/x-mixed-replace; boundary=frame' )

关键细节:cap.set()固定分辨率避免不同摄像头尺寸差异;model(frame, ...)直接传 numpy array,无需 PIL 转换;results[0].plot()是 Ultralytics 内置方法,比手写cv2.rectangle()更鲁棒;multipart/x-mixed-replace是 MJPEG 流标准协议,前端<video src="/camera/stream/">可直接播放。


4. 特殊场景鲁棒性:低像素、雨雾、黑夜图像的预处理与检测增强策略

4.1 退化图像模拟与预处理模块设计:为什么不能只靠 YOLOv5 自身?

YOLOv5 在干净数据上表现好,但面对摘要提到的“像素低模糊、较远距离、雾霾、雨天、黑夜”,原始模型会漏检或误检。项目在detection/preprocess.py中实现了针对性预处理链:

import cv2 import numpy as np def enhance_for_low_light(image): """针对黑夜图像:CLAHE + Gamma 校正""" gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) enhanced = clahe.apply(gray) # Gamma 校正提升暗部细节 gamma = 0.7 inv_gamma = 1.0 / gamma table = np.array([((i / 255.0) ** inv_gamma) * 255 for i in np.arange(0, 256)]).astype("uint8") enhanced = cv2.LUT(enhanced, table) return cv2.cvtColor(enhanced, cv2.COLOR_GRAY2BGR) def dehaze_for_fog(image): """针对雾霾:暗通道先验去雾(简化版)""" # 此处用 OpenCV fastNlMeansDenoisingColored 模拟去雾效果(因完整暗通道计算太慢) return cv2.fastNlMeansDenoisingColored(image, None, 10, 10, 7, 21) def denoise_for_rain_blur(image): """针对雨天模糊:非局部均值去噪 + 锐化""" denoised = cv2.fastNlMeansDenoisingColored(image, None, 5, 5, 7, 21) kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) sharpened = cv2.filter2D(denoised, -1, kernel) return sharpened

使用逻辑:在detect_traffic_sign()函数中,根据用户选择的“场景模式”(前端下拉框)调用对应函数:

if scene_mode == 'night': processed_img = enhance_for_low_light(img_array) elif scene_mode == 'fog': processed_img = dehaze_for_fog(img_array) elif scene_mode == 'rain': processed_img = denoise_for_rain_blur(img_array) else: processed_img = img_array # 默认不处理 results = model(processed_img, ...)

这种设计让预处理可插拔,不污染主模型逻辑。

4.2 模型参数动态调整:conf/iou 如何随场景变化?

固定conf=0.25在雾天会漏检(因标志对比度低),而设conf=0.1又在晴天引入大量误检。项目在views.py中实现了场景感知的阈值策略:

场景类型conf 阈值iou 阈值设计理由
晴天/高清0.300.45平衡精度与召回
雾天/雨天0.150.30降低置信门槛,容忍更多低置信预测;降低 iou 让重叠框更易保留
夜晚/低像素0.100.25极端情况,宁可多检勿漏;iou 进一步降低防漏框

前端通过隐藏字段传递scene_mode,后端据此查表获取参数:

THRESHOLD_CONFIG = { 'clear': {'conf': 0.30, 'iou': 0.45}, 'fog': {'conf': 0.15, 'iou': 0.30}, 'rain': {'conf': 0.15, 'iou': 0.30}, 'night': {'conf': 0.10, 'iou': 0.25}, } params = THRESHOLD_CONFIG.get(scene_mode, THRESHOLD_CONFIG['clear']) results = model(processed_img, conf=params['conf'], iou=params['iou'])

4.3 检测结果后处理:如何解决“同一标志被多次框选”的问题?

YOLOv5 在退化图像上常对同一标志输出多个重叠框(尤其雾天)。项目在detection/utils.py中实现了基于 IoU 的二次 NMS:

def post_nms_merge(detections, iou_threshold=0.3): """ 输入:detections = [{'class_name': 'stop', 'confidence': 0.72, 'bbox': [120,80,180,140]}, ...] 输出:合并后的 detections(同类别框若 IoU>0.3,则取 confidence 最高者) """ if len(detections) <= 1: return detections # 按 class_name 分组 grouped = {} for det in detections: cls = det['class_name'] if cls not in grouped: grouped[cls] = [] grouped[cls].append(det) merged = [] for cls, dets in grouped.items(): # 按置信度降序 dets.sort(key=lambda x: x['confidence'], reverse=True) keep = [] while dets: current = dets.pop(0) keep.append(current) # 删除与 current IoU > threshold 的其他框 remaining = [] for d in dets: iou = calculate_iou(current['bbox'], d['bbox']) if iou <= iou_threshold: remaining.append(d) dets = remaining merged.extend(keep) return merged

calculate_iou()是标准交并比计算。该函数在detect_traffic_sign()返回结果前调用,确保前端看到的detections列表中,每个类别最多一个框——这直接解决了摘要中“小窗口显示是什么交通标志”的 UI 需求,避免多个标签打架。


5. 避坑指南:5 个血泪经验总结——从模型加载失败到 MySQL 中文乱码

5.1 现象:启动时报ModuleNotFoundError: No module named 'ultralytics',即使pip install ultralytics成功

原因:ultralytics8.0.x 版本要求torch>=1.12,而pip install ultralytics可能安装最新版(如 8.2.x),其依赖torch>=2.0,与项目要求的torch==1.12.1+cpu冲突。
解决:严格按顺序安装——先pip install torch==1.12.1+cpu -f https://download.pytorch.org/whl/torch_stable.html,再pip install ultralytics==8.0.194。验证命令:python -c "from ultralytics import YOLO; print(YOLO.__version__)"输出8.0.194。

5.2 现象:上传图片后页面空白,Console 报GET http://127.0.0.1:8000/media/uploads/xxx.jpg 404 (Not Found)

原因:Django 默认不 serveMEDIA_URL,需在urls.py中显式添加调试路由。
解决:在根urls.py(非 app 的)末尾添加:

from django.conf import settings from django.conf.urls.static import static if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

并确认settings.py中MEDIA_URL = '/media/',MEDIA_ROOT = BASE_DIR / 'media'。

5.3 现象:MySQL 中文用户名显示为????,检测结果中的中文类别名(如“禁止左转”)存入后变乱码

原因:MySQL 数据库、表、连接三者字符集不一致。即使建库时设utf8mb4,若 Django 连接未声明charset,仍会走默认latin1。
解决:

  1. 检查 MySQL 全局设置:SHOW VARIABLES LIKE 'character_set%';确保character_set_server=utf8mb4;
  2. 修改settings.py的DATABASES,加入'OPTIONS': {'charset': 'utf8mb4'};
  3. 对已存在表执行:ALTER TABLE auth_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。

5.4 现象:摄像头检测卡顿、帧率极低(<1 fps),CPU 占用 100%

原因:model.predict()默认启用half=True(半精度),但在 CPU 上无效且触发冗余计算;同时未限制帧率,OpenCV 读帧过快。
解决:

  • 在camera_stream()中model()调用加half=False参数;
  • 在while True:循环内加time.sleep(0.1)限制帧率约 10 fps;
  • 或更优:用cap.set(cv2.CAP_PROP_FPS, 10)设置摄像头采集帧率。

5.5 现象:Django Admin 中“模型版本”管理页点击“添加”报IntegrityError: (1048, "Column 'model_path' cannot be null")

原因:model_version模型定义中model_path字段未设blank=True,但 Admin 表单提交时未传值。
解决:打开detection/models.py,找到ModelVersion类,修改字段:

model_path = models.CharField(max_length=255, blank=True, null=True, help_text="模型文件相对路径,如 'models/yolov5s.pt'")

然后执行python manage.py makemigrations && python manage.py migrate。


6. 进阶技巧:模型热更新、检测日志分析与自定义类别扩展实战

6.1 实现模型热更新:不重启 Django 服务,动态加载新权重

项目当前每次修改模型都要重启runserver,生产环境不可行。我们给detection/views.py加一个reload_model视图,配合 Admin 的“模型版本”管理:

  1. Admin 中新增“激活”按钮:在detection/admin.py中重写ModelVersionAdmin:

    from django.urls import reverse from django.http import HttpResponseRedirect @admin.action(description='激活选中模型') def activate_model(self, request, queryset): if queryset.count() != 1: self.message_user(request, "只能选择一个模型激活", level='error') return model_version = queryset.first() # 更新全局变量(需线程安全) from detection import model_loader model_loader.current_model = model_loader.load_yolo_model(model_version.model_path) model_loader.current_version = model_version.version_name self.message_user(request, f"已激活模型:{model_version.version_name}")
  2. 线程安全的模型加载器(detection/model_loader.py):

    import threading from detection.views import load_yolo_model # 原加载函数 _lock = threading.Lock() current_model = None current_version = "unknown" def get_model(): with _lock: return current_model def reload_model(model_path): global current_model, current_version with _lock: current_model = load_yolo_model(model_path) # 复用原函数 current_version = model_path.split('/')[-1]
  3. 在检测视图中调用:

    from detection.model_loader import get_model def upload_image(request): model = get_model() # 替代原来的 load_yolo_model() results = model(image, ...)

这样,管理员在 Admin 点击“激活”,所有后续请求自动用新模型,零 downtime。

6.2 检测日志分析:用 Pandas 快速统计各场景下的平均置信度与漏检率

DetectionLog表存了每次检测的detection_result(JSON 字符串),可导出做质量分析。写一个 management command(detection/management/commands/analyze_logs.py):

import json import pandas as pd from django.core.management.base import BaseCommand from detection.models import DetectionLog class Command(BaseCommand): def handle(self, *args, **options): logs = DetectionLog.objects.all().values('scene_mode', 'detection_result', 'detected_count') df = pd.DataFrame(list(logs)) # 解析 detection_result def parse_detections(row): try: dets = json.loads(row['detection_result']) if not dets: return {'avg_conf': 0, 'classes': []} confs = [d['confidence'] for d in dets] classes = [d['class_name'] for d in dets] return {'avg_conf': round(sum(confs)/len(confs), 3), 'classes': classes} except: return {'avg_conf': 0, 'classes': []} parsed = df.apply(parse_detections, axis=1, result_type='expand') df = pd.concat([df, parsed], axis=1) # 按场景统计 report = df.groupby('scene_mode').agg({ 'avg_conf': 'mean', 'detected_count': 'mean', 'classes': lambda x: len(set([c for cs in x for c in cs])) # 去重后类别数 }).round(3) self.stdout.write(str(report))

运行python manage.py analyze_logs,输出:

avg_conf detected_count classes scene_mode clear 0.72 2.3 5 fog 0.41 1.2 3 night 0.33 0.8 2

这直接告诉你:雾天平均置信度掉到 0.41,需优化预处理或重训模型。

6.3 扩展自定义交通标志类别:从 43 类到 60 类的完整流程

项目默认用德国交通标志数据集(GTSRB),共 43 类。若要加“施工区域”、“学校区域”等中国特有标志:

  1. 准备数据:按 YOLOv5 格式组织,datasets/custom/images/存图,datasets/custom/labels/存 txt(每行class_id center_x center_y width height);
  2. 修改data.yaml:增加nc: 60,names: ['speed_limit_30', ..., 'construction_zone', 'school_zone'];
  3. 重训模型:yolo train data=datasets/custom/data.yaml model=yolov5s.pt epochs=100;
  4. 替换权重:将生成的runs/train/exp/weights/best.pt放入detection/models/,并在 Admin 中添加新版本记录;
  5. 更新前端类别映射:修改detection/templates/detection/result.html中的class_map,将 ID 转为中文名。

我一般会在detection/utils.py里维护一个CLASS_MAP = {0: "限速30", 1: "禁止停车", ...}字典,这样detections中的class_id可直接映射,避免模板里硬编码。从那以后我每次加新类别,都强制走一遍python manage.py analyze_logs看新类别的置信度分布,再决定是否要针对性增强训练——这比盲目调参靠谱得多。

希望帮到你。

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

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

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

立即咨询