首页 / 资讯中心 / 文章详情

YOLOv11+Django+DeepSeek:交通标志识别系统实战

YOLOv11+Django+DeepSeek:交通标志识别系统实战 ★ FEATURED ARTICLE
最近我把一套交通标志识别系统从零到一完整调通并跑了起来技术栈正好是标题里那套YOLOv11做检测、Django做后端服务、DeepSeek大模型做识别结果的智能解读外面套了一层带登录认证的Web界面。整套系统既能自动识别图片中的交通标志又能用自然语言告诉你这个标志具体是什么意思、该注意什么最终交付物是Python源码加ONNX模型加一个可以直接打开使用的Web应用。这个项目解决了一个很实际的痛点传统交通标志识别通常只输出一个类别标签和一个置信度比如“限速500.98”但用户真正需要的是“这个标志出现在什么场景、接下来要做什么操作”。把检测模型和大语言模型串起来之后系统就从单纯的“识别”升级成了“解释”体验完全不一样。这篇文章从设计思路、模块拆解、部署流程写到问题排查每一步都按实际跑通的经验来适合正在做类似目标检测Web应用的开发者参考也适合想从零入手YOLOv11Django组合的新手照着操作基本能复现整套流程。1. 项目整体设计与思路拆解1.1 这套系统到底做了什么从用户角度看这套系统的工作流程非常直观打开浏览器访问登录页输入账号密码之后进入主界面上传一张包含交通标志的图片或是直接用摄像头拍照后端调用训练好的YOLOv11模型完成检测返回每个标志的类别标签、置信度和边界框坐标同时把识别结果整理成结构化文本发送给DeepSeek大模型生成一段通俗易懂的交通标志解释文案前端页面把检测框绘制在原图上下方同步展示大模型生成的中文解读。整个过程从上传到结果展示通常在2到3秒内完成体验很顺滑。从开发者角度看系统可以拆成三个核心层次模型层负责推理业务层负责接口逻辑和数据处理展示层负责交互界面。把这一套跑通意味着你同时掌握了目标检测模型的Web化部署、后端接口设计、大语言模型调用和前端可视化交互这几项关键能力。这种“检测模型大语言模型”的组合放在自动驾驶辅助、智能巡检、路况分析之类场景里都有很强的延伸价值不只是做一个Demo那么简单。1.2 为什么是YOLOv11DjangoDeepSeek这个组合先聊模型选型。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 modelbest.pt formatonnx opset12 simplifyTrue导出后得到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, usernameusername, passwordpassword) 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那每个候选框就是41014个值。后处理要做的事是从中拆出坐标和各类别得分过滤低置信度候选框再执行NMS非极大值抑制去掉重叠框。import onnxruntime as ort import numpy as np class YOLO11Detector: def __init__(self, onnx_path, class_names, conf_threshold0.25, iou_threshold0.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, dtypenp.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.25NMS的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.txtDjango4.2.7 onnxruntime1.17.1 opencv-python4.8.1.78 numpy1.26.2 requests2.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模型能否正确加载并输出脚本会打印输入输出节点的名称和shapeimport 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和前端这样定位问题会快很多。后续还可以在现有基础上加识别历史记录、批量上传、摄像头实时检测这些功能方向都已经铺好了。
阅读完成 · 觉得有帮助?
咨询建站