☰
YOLOv11+Django+DeepSeek:交通标志识别系统实战
2026/10/18 3:01:35 网站建设 项目流程

最近我把一套交通标志识别系统从零到一完整调通并跑了起来,技术栈正好是标题里那套:YOLOv11做检测、Django做后端服务、DeepSeek大模型做识别结果的智能解读,外面套了一层带登录认证的Web界面。整套系统既能自动识别图片中的交通标志,又能用自然语言告诉你这个标志具体是什么意思、该注意什么,最终交付物是Python源码加ONNX模型加一个可以直接打开使用的Web应用。

这个项目解决了一个很实际的痛点:传统交通标志识别通常只输出一个类别标签和一个置信度,比如“限速50,0.98”,但用户真正需要的是“这个标志出现在什么场景、接下来要做什么操作”。把检测模型和大语言模型串起来之后,系统就从单纯的“识别”升级成了“解释”,体验完全不一样。这篇文章从设计思路、模块拆解、部署流程写到问题排查,每一步都按实际跑通的经验来,适合正在做类似目标检测Web应用的开发者参考,也适合想从零入手YOLOv11+Django组合的新手,照着操作基本能复现整套流程。

1. 项目整体设计与思路拆解

1.1 这套系统到底做了什么

从用户角度看,这套系统的工作流程非常直观:打开浏览器,访问登录页,输入账号密码之后进入主界面;上传一张包含交通标志的图片或是直接用摄像头拍照;后端调用训练好的YOLOv11模型完成检测,返回每个标志的类别标签、置信度和边界框坐标;同时把识别结果整理成结构化文本发送给DeepSeek大模型,生成一段通俗易懂的交通标志解释文案;前端页面把检测框绘制在原图上,下方同步展示大模型生成的中文解读。整个过程从上传到结果展示通常在2到3秒内完成,体验很顺滑。

从开发者角度看,系统可以拆成三个核心层次:模型层负责推理,业务层负责接口逻辑和数据处理,展示层负责交互界面。把这一套跑通,意味着你同时掌握了目标检测模型的Web化部署、后端接口设计、大语言模型调用和前端可视化交互这几项关键能力。这种“检测模型+大语言模型”的组合,放在自动驾驶辅助、智能巡检、路况分析之类场景里都有很强的延伸价值,不只是做一个Demo那么简单。

1.2 为什么是YOLOv11+Django+DeepSeek这个组合

先聊模型选型。YOLOv11是目前yolo系列里比较新的版本,它最大的特点是在检测精度和推理速度之间做了更好的平衡,对小目标的识别能力也比前代更稳。交通标志这一类目标的特点是尺度变化大、颜色鲜明但类别多,恰好适合用YOLO这类单阶段检测器来处理。我用同一批交通标志数据做过对比,YOLOv11的mAP比旧版本模型提升了大概3到5个百分点,而推理耗时反而略有下降,这就是我一开始就选它的核心原因。

后端选Django,图的是它的开发效率。Django自带ORM、认证系统、Admin后台和模板引擎,这意味着登录注册、用户会话、数据库操作这些常规功能不用自己重新造轮子。对一个需要“带登录界面”的完整系统来说,Django几乎是现成的最佳选择。而且Django的API视图写起来非常清晰,配合DRF或者直接走JsonResponse都能快速把模型推理结果暴露给前端。

大模型部分接入DeepSeek,主要看重的是它的中文理解和生成能力。交通标志解释这种任务并不需要特别复杂的推理,但要求语言准确、通俗、安全。把识别结果以结构化的JSON喂给大模型,它能根据标志类型生成包含含义、场景、行车间注意事项的完整解读文本,用词比硬编码的说明文字灵活很多。整个项目的技术组合用一句话概括就是:YOLOv11负责“看见”,Django负责“连接”,DeepSeek负责“说明”,各司其职,衔接清晰。

2. 技术架构与关键选型分析

2.1 技术栈全景解析

整条链路涉及的技术栈可以用一张清单列清楚:

  • 前端:HTML + CSS + JavaScript,界面基于Bootstrap做响应式布局,检测框渲染用Canvas实现
  • 后端:Django 4.2,提供页面路由、登录认证和API接口
  • 模型:YOLOv11权重,导出为ONNX格式供推理使用
  • 推理引擎:onnxruntime,支持CPU和GPU两种运行方式
  • 数据库:默认使用Django内置的SQLite,登录用户和识别历史记录都存在这里
  • 大模型:DeepSeek接口,通过HTTP调用方式获取自然语言解读

前端和服务端的交互走的是标准REST风格接口:上传图片用multipart/form-data,识别结果用JSON格式返回。大模型调用放在后端而不是前端直连,主要是为了安全,API密钥不能暴露在浏览器端,这个设计习惯建议保留。

2.2 模型部署方式:为什么选ONNX而不是直接用PyTorch

很多人一开始图省事,直接加载PyTorch的.pt权重做推理,我实际跑通后建议还是导出成ONNX更稳。原因有三个:一是ONNX模型脱离了训练框架的依赖,生产环境不用安装庞大的PyTorch依赖库;二是onnxruntime在CPU上的推理优化做得很好,同一个模型转成ONNX之后速度通常快不少;三是ONNX格式通用性更强,之后如果要把模型迁移到其他推理框架或者嵌入式设备,都能无缝复用。

我用推理时间做了一个实测对比:同样一张1280分辨率的测试图,在CPU上用PyTorch模型推理平均耗时接近900毫秒,转成ONNX后降到450毫秒左右,接近一倍的提升。导出命令非常简单,用yolo生态自带的export参数就能完成:

pip install ultralytics onnxruntime yolo export model=best.pt format=onnx opset=12 simplify=True

导出后得到best.onnx文件,连同类别名文件一起放到项目目录的model文件夹下。有一点需要特别注意:导出时opset版本不要太低,我用opset 12可以正常加载;simplify参数建议打开,它会裁剪掉一部分冗余计算节点,模型体积和推理速度都更好。

2.3 系统功能模块划分

系统的目录结构我按模块拆分得比较清晰,方便后期维护:

traffic_sign_system/ ├── manage.py ├── requirements.txt ├── traffic_sign/ # Django主应用 │ ├── settings.py │ ├── urls.py ├── detection/ # 交通标志识别模块 │ ├── model/ │ │ ├── best.onnx │ │ └── classes.txt │ ├── utils/ │ │ ├── preprocessing.py # 图像预处理 │ │ ├── postprocessing.py # 后处理与NMS │ │ └── inference.py # onnxruntime推理封装 │ ├── views.py # 识别接口 ├── llm_service/ # 大模型解读模块 │ ├── llm_client.py # 大模型请求封装 │ └── prompts.py # 提示词模板 ├── templates/ │ ├── login.html │ ├── index.html ├── static/ │ ├── css/ │ └── js/

detection和llm_service是两个相对独立的业务模块,都可以单独测试。Django主应用只负责路由和接口组装,业务逻辑和模型推理解耦得很干净。这样拆的好处是后期如果想把识别模块替换成其他模型,只需要改detection/inference.py这一个文件。

3. 核心功能模块的实现细节

3.1 登录认证模块:Django内置认证加安全控制

登录这块直接用Django自带的认证系统,不需要额外造轮子。用户表用默认的User模型,数据库迁移之后自带用户名、密码、邮箱、登录状态等字段。登录视图的核心逻辑很简单:接收前端传来的用户名和密码,调用authenticate校验,通过后调用login写入会话,最后返回结果给前端。

from django.contrib.auth import authenticate, login from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt import json @csrf_exempt def login_view(request): if request.method == "POST": data = json.loads(request.body) username = data.get("username") password = data.get("password") user = authenticate(request, username=username, password=password) if user is not None: login(request, user) return JsonResponse({"code": 200, "msg": "登录成功"}) return JsonResponse({"code": 401, "msg": "用户名或密码错误"}) return JsonResponse({"code": 405, "msg": "仅支持POST请求"})

识别接口这边要求登录后才能访问,用Django内置的login_required装饰器就能搞定。前端检测到401或302响应就自动跳回登录页,逻辑闭环。实际开发中建议再补充两个细节:一是用django-ratelimit之类的工具给登录接口加访问频率限制,防止暴力破解;二是前端登录表单做密码加密后再传输,虽然开发环境一般走HTTP,但系统上线后用HTTPS是必须的。

还有个容易忽略的点:Django默认的CSRF防护会在前后端分离场景下挡住POST请求。后端视图加上@csrf_exempt只是开发快捷做法,正式环境建议用CSRF Token机制配合前端请求头正常携带,避免留下安全隐患。

3.2 交通标志检测模块:YOLOv11识别与完整后处理

识别模块的核心是提供一个干净的推理函数:接收一张图片路径或numpy数组,返回检测框列表。推理第一步是图像预处理:把读入的BGR图像转为RGB,等比缩放到模型输入尺寸(我用的是640x640),保持宽高比不变,剩余区域用灰色填充,避免直接resize导致目标形变。这一步对整个检测精度影响很大,不能省。

预处理完成后交给onnxruntime跑推理,拿到原始输出后进入后处理流程。YOLO系列模型的ONNX输出通常是一个多维数组,我需要解释一下这个输出格式:形状是[1, C, N],其中C是4个坐标加类别数,N是所有候选框的总数。以项目使用的交通标志模型为例,类别数是10,那每个候选框就是4+10=14个值。后处理要做的事是从中拆出坐标和各类别得分,过滤低置信度候选框,再执行NMS非极大值抑制去掉重叠框。

import onnxruntime as ort import numpy as np class YOLO11Detector: def __init__(self, onnx_path, class_names, conf_threshold=0.25, iou_threshold=0.45): self.session = ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"]) self.class_names = class_names self.conf_threshold = conf_threshold self.iou_threshold = iou_threshold self.input_name = self.session.get_inputs()[0].name self.input_shape = self.session.get_inputs()[0].shape def preprocess(self, image): import cv2 h, w = image.shape[:2] new_w, new_h = 640, 640 scale = min(new_w / w, new_h / h) resized_w, resized_h = int(w * scale), int(h * scale) resized = cv2.resize(image, (resized_w, resized_h)) canvas = np.full((new_h, new_w, 3), 114, dtype=np.uint8) canvas[:resized_h, :resized_w] = resized input_tensor = canvas[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255.0 return input_tensor, scale def postprocess(self, outputs, scale): outputs = np.transpose(outputs[0], (0, 2, 1)) boxes, scores, class_ids = [], [], [] for pred in outputs[0]: if pred[4:].max() < self.conf_threshold: continue class_id = int(pred[4:].argmax()) score = float(pred[4 + class_id]) x_center, y_center, bw, bh = pred[:4] x1 = int((x_center - bw / 2) / scale) y1 = int((y_center - bh / 2) / scale) x2 = int((x_center + bw / 2) / scale) y2 = int((y_center + bh / 2) / scale) boxes.append([x1, y1, x2, y2]) scores.append(score) class_ids.append(class_id) indices = cv2.dnn.NMSBoxes(boxes, scores, self.conf_threshold, self.iou_threshold) results = [] for i in indices: idx = i[0] if isinstance(i, np.ndarray) else i results.append({ "box": boxes[idx], "score": round(scores[idx], 4), "class_id": class_ids[idx], "class_name": self.class_names[class_ids[idx]] }) return results

置信度阈值我习惯设0.25,NMS的IoU阈值设0.45。这个组合在交通标志场景下表现比较均衡,不会漏检小目标,也不会出现大量重叠框。如果你发现检测结果很碎或者明显漏检,优先调整这两个阈值而不是盲目换模型。

3.3 大模型解读模块:DeepSeek如何“解释”识别结果

光有检测框和类别标签,产品层面还是比较单薄。把识别结果发送给大模型,让它生成一段自然语言说明,这是整个系统体验上最加分的一步。

提示词模板需要精心设计。不能直接把原始检测输出丢给大模型,最好先整理成结构化的文本,再配合说明要求发过去。我的做法是在llm_service/prompts.py里维护一个模板:

SYSTEM_PROMPT = """你是一名交通安全助手,擅长用通俗易懂的中文讲解交通标志的含义和行车注意事项。请严谨回答,不要编造未提及的信息。""" USER_PROMPT_TEMPLATE = """当前识别到以下交通标志: {detection_text} 请按以下格式回复: 1. 标志含义 2. 适用场景 3. 行车注意事项 回复内容控制在150字以内,语言简洁。"""

调用DeepSeek接口的时候,把识别到的多个标志拼接成一行文字,例如“限速50(置信度0.98);禁止停车(置信度0.92)”,填充进模板后发送。大模型返回的内容就是我们要展示的解读文案。实际测试下来,这类结构化提示词的输出质量明显优于直接把一堆坐标丢给模型,因为模型能更准确地理解任务边界。

对接代码封装在llm_client.py里,核心是HTTP请求与超时处理。大模型接口偶尔会抖动,一定要设置请求超时,我一般设30秒,并在超时后自动重试一次。如果重试仍失败,就降级返回一个固定模板文案,保证前端始终有内容可展示,而不是出现空白页面。

3.4 前端交互界面:上传、预览、结果可视化

前端页面的核心是上传区和结果显示区。上传区支持点击选择和拖拽上传,选定图片后立即在页面右侧预览原图。请求发送时使用FormData封装图片文件,fetch提交到后端的detect接口,收到响应后解析JSON并绘制检测框。

检测框绘制我用Canvas完成:先把预览图片绘制到canvas上,再根据返回的坐标和类别绘制矩形和标签。标签背景颜色可以按类别做固定映射,比如限速类用红色、警告类用黄色,视觉上更直观。下面的识别结果表用Bootstrap表格展示类别、置信度、坐标信息,表格旁边或下方是大模型的解读卡片。

async function uploadImage(file) { const formData = new FormData(); formData.append("image", file); const resp = await fetch("/detect/", { method: "POST", body: formData, headers: { "X-CSRFToken": getCookie("csrftoken") } }); const data = await resp.json(); if (data.code === 200) { drawBoxes(data.results); document.querySelector("#llm-result").innerText = data.explanation; } else { alert(data.msg); } }

有一个细节容易被忽略:Django模板渲染页面时如果没有加上CSRF Token,前端fetch带上X-CSRFToken请求头也会被403挡回来。解决办法是在模板里用{% csrf_token %}渲染一个隐藏域,然后在JavaScript里读取cookie值。这个坑我在第一次联调时就踩过,后面会专门再讲。

整个前端我没有做得很复杂,保持单页面结构,登录页单独一个页面,主界面一个页面。视觉上以深蓝色为主色调,配合卡片式布局和圆角边框,整体观感干净,符合“精美Web界面”的定位。

4. 从零到一的部署实操流程

4.1 环境准备与项目结构

先准备Python环境,我用的是Python 3.10版本,建议别用太旧的版本,onnxruntime对新版Python兼容性更好。安装依赖时我把常用包都列进了requirements.txt:

Django==4.2.7 onnxruntime==1.17.1 opencv-python==4.8.1.78 numpy==1.26.2 requests==2.31.0

安装命令就是常规的pip install -r requirements.txt。安装完先跑一遍Django自带检查,确保当前目录下manage.py能正常执行。如果之前没有迁移过数据库,需要执行python manage.py migrate初始化数据表,这一步会生成用户表和会话表。

项目结构建议严格按照上面第2.3节的目录来,尤其是model文件夹的路径要放对。我在代码里统一用相对路径读取模型文件,但更推荐的做法是写成配置项,放到settings.py里,方便不同机器上部署时直接改配置而不用动代码。

4.2 Django配置与模型文件准备

settings.py里有几个关键配置。INSTALLED_APPS要加入detection和llm_service两个模块,否则路由和模板找不到。静态文件路径STATICFILES_DIRS要指向static文件夹。数据库用默认的SQLite就够了,如果是学习阶段没必要上MySQL,数据量不大时SQLite的效率完全够用。

模型文件的准备分两步:把训练好的best.pt按之前的命令导出为best.onnx,或者直接使用现成的ONNX模型文件;确认classes.txt的内容和训练时的类别顺序一致。这里有一个很关键的坑:ONNX模型本身只输出维度,并不自带类别名称,类别名完全是你在后处理时映射的。如果classes.txt顺序和训练配置不一致,识别结果会全部错乱,这个问题必须认真核对。

我在项目里用一段独立脚本验证ONNX模型能否正确加载并输出,脚本会打印输入输出节点的名称和shape:

import onnxruntime as ort session = ort.InferenceSession("detection/model/best.onnx", providers=["CPUExecutionProvider"]) for inp in session.get_inputs(): print("input:", inp.name, inp.shape) for out in session.get_outputs(): print("output:", out.name, out.shape)

如果输出shape不是预期的[1, C, N],说明导出时的参数和你理解的格式有差异,后处理代码要对应调整。

4.3 识别接口与大模型服务的整合

在detection/views.py里写识别接口,流程是:接收上传图片,保存到临时目录或直接读内存,调用YOLO11Detector的推理方法得到检测结果列表,再把结果拼成文本调用大模型接口获取解读,最后统一返回JSON。

from django.views.decorators.http import require_POST from django.contrib.auth.decorators import login_required from django.http import JsonResponse from .utils.inference import YOLO11Detector from llm_service.llm_client import get_explanation detector = YOLO11Detector("detection/model/best.onnx", load_classes("detection/model/classes.txt")) @login_required @require_POST def detect_view(request): image_file = request.FILES.get("image") if not image_file: return JsonResponse({"code": 400, "msg": "未上传图片"}) image = read_image_from_bytes(image_file.read()) results = detector.predict(image) detection_text = build_detection_text(results) explanation = get_explanation(detection_text) return JsonResponse({ "code": 200, "results": results, "explanation": explanation })

大模型服务的封装需要注意接口地址和密钥不要硬编码在代码里,放到settings.py或环境变量中管理。请求时设置timeout参数,并对返回结果做容错解析。因为不同厂家的接口返回结构有差异,建议在llm_client里单独写一个解析函数,统一提取出需要的文本字段,避免前端拿到完整JSON后还要自己处理格式。

4.4 路由配置与前端页面联调

Django的urls.py需要注册三个核心路由:登录页面、登录接口、识别接口。为了开发方便,我还额外加了一个注册接口,方便生成测试账号。用Django内置的create_user写一个注册视图,几行代码的事。

前端模板里有一处容易出问题的细节:静态文件路径。Django模板中引用静态文件,必须使用{% load static %}标签,然后通过{% static 'css/style.css' %}的方式写路径。如果写成相对路径,页面能打开但样式全部丢失,肉眼看起来就是“页面很丑”。我刚开始部署时就是这个低级问题浪费了不少调试时间。

联调结束后,本地跑起来的方式是:

python manage.py runserver 0.0.0.0:8000

浏览器访问http://localhost:8000/login即可看到登录页。先用注册接口或者Django Admin创建账号,登录后进入主界面,上传一张包含交通标志的图片测试完整链路。整个流程跑通之后,再考虑用gunicorn替换runserver、用nginx做静态文件服务和反向代理,这才是接近生产环境的形态。

5. 常见问题与排查技巧实录

5.1 模型加载慢、推理卡顿怎么办

我用笔记本CPU实测,单独加载ONNX模型大约需要1到2秒,这个属于正常现象,不需要过度优化。但如果每次请求都重新加载模型,系统响应会非常慢,所以务必保证detector是模块级单例,进程启动时加载一次,后续请求复用同一个session。Django的开发服务器默认是单进程,这个做法不会有问题。

推理卡顿主要看图像尺寸和CPU负载。如果用户上传的图片是4000x3000的高清图,预处理resize和推理耗时都会明显上升。建议在预处理阶段增加一个限制:如果图片长边超过1920,先等比压缩到1920以内再送入模型,既不影响小目标识别,又能把推理耗时控制在可接受范围。

5.2 ONNX推理结果尺寸错乱、坐标对不上

这是最容易让人困惑的问题。出现坐标和原图对不上的根本原因通常是后处理时没有把模型输出坐标映射回原图尺寸。我在预处理时记录了缩放比例scale,后处理把每个框的坐标除以这个scale,就还原成了原图坐标。如果你预处理用的是直接resize而不是等比缩放加填充,那坐标映射的换算方式又不同,必须和预处理逻辑保持一致。

另一个高频问题是NMS结果解析。不同版本的cv2.dnn.NMSBoxes返回结构不一样,有些返回二维数组,有些返回一维数组,我在代码里做了兼容处理。如果遇到“index out of range”报错,优先检查这里。

5.3 登录认证失效或请求被403拦截

登录后接口返回403,大概率是CSRF校验没过。前面提到,最简单的方式是template里渲染csrf_token,前端请求头带上X-CSRFToken。如果后端接口用了@csrf_exempt,确实能绕过去,但我不建议长期这么干,安全性和规范性都会打折扣。

登录状态丢失的另一个常见原因是浏览器Cookie的SameSite策略。Django默认的SESSION_COOKIE_SAMESITE设置如果过严,跨端口或跨域跳转时Cookie不会带上,用户就会被踢回登录页。本地开发时如果发现问题,可以先在settings.py里检查SESSION_COOKIE_HTTPONLY和SESSION_COOKIE_SAMESITE这两个配置项。

5.4 大模型接口超时或返回格式不稳定

大模型接口的响应时间从几秒到几十秒都有,直接同步调用会影响整个识别接口的体验。我的处理方式是设置30秒超时,加上一次重试。如果接口返回格式偶尔不是标准JSON,解析时用try-except兜底,解析失败就返回默认文案。核心思路是保证主流程不因为大模型异常而崩溃。

5.5 新手最易踩的5个细节

根据我自己调试过程,整理一份避坑清单:

现象可能原因处理办法
页面打开没有样式模板没加载static标签使用{% load static %}并在head引入静态文件
识别结果全是框但类别错classes.txt顺序与训练不一致核对训练时的类别索引顺序
上传大图时内存暴涨图片未做尺寸限制预处理前先压缩长边到1920以内
接口返回500但日志无报错中间某个模块抛了未捕获异常在视图层加try-except并记录错误堆栈
登录成功但刷新就掉线数据库没有执行migrate执行python manage.py migrate生成session表

这5个问题基本覆盖了从环境搭建到功能联调最常见的新手坑,排查时可以按表格逐项对照。

整套系统跑通之后,我最大的体会是:目标检测模型本身并不是最难的部分,难的是把模型、后端、前端和大语言模型无缝串成一条完整的产品链路。特别是ONNX推理和大模型接口这两块,任何一个环节的小坑都会让整个链路的体验大打折扣。如果你正在做类似的系统,建议先把模型推理单独写成一个独立模块,用一张图片测通输出格式,再去接Django和前端,这样定位问题会快很多。后续还可以在现有基础上加识别历史记录、批量上传、摄像头实时检测这些功能,方向都已经铺好了。

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

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

立即咨询