☰
基于深度学习的火焰烟雾检测系统:YOLOv8训练到Django Web部署全流程
2026/10/17 8:14:19 网站建设 项目流程

最近在帮朋友调试一个火灾监控方向的深度学习项目,核心是一套基于深度学习的火焰烟雾检测系统,模型侧把YOLOv5/v8/v11/v12都跑通了,后端用Django做了Web服务,配上网页展示界面,整套系统包含训练代码、训练数据集和模型权重。因为涉及从数据标注、模型训练到Web落地的完整链路,过程中踩了不少坑,也有不少可以给后来人参考的经验,所以整理成这篇分享。

这套系统能做什么、解决什么问题?就是对摄像头画面或者上传的图片进行实时推理,把画面中的火焰和烟雾区域用检测框标出来,并给出置信度。适合三类人参考:一是做火灾检测方向毕业设计的学生,需要一个能演示、能答辩的全栈系统;二是想从纯算法转向完整工程项目、只做过单模型推理的开发者;三是消防安防行业的工程师,想快速评估深度学习目标检测在自己业务场景下的效果。

1. 系统整体架构与方案选型思路

1.1 一个完整的检测系统需要哪些组件

很多人一看到"火焰烟雾检测"这个标题,第一反应就是找个模型训练一下,然后拿几张图测测效果就完了。但一个能叫"系统"的东西,至少得包含下面这几块:

第一是数据层。深度学习模型的质量上限基本由数据决定,火焰烟雾这类目标尤其讲究。你需要一份包含火焰、烟雾不同场景、不同光照、不同距离的标注好的图片集,并且格式要跟训练框架对齐。

第二是模型层。这部分要解决"用什么模型检测"的问题。目标检测领域现在主流就是YOLO系列,这套系统里我分别用YOLOv5、v8、v11、v12做了训练和对比,核心原因后面细说。

第三是后端服务层。模型训练完只是得到了一个权重文件.pt,它本身不能对外提供服务。需要把它封装成接口,接收上传图片或视频帧,调用模型推理,然后返回结果。这里选择Django来做,理由在后面单独展开。

第四是前端展示层。一个演示系统必须有可视化的界面,比如上传图片、选择本地文件、开启摄像头检测实时画面,让使用者能直观看到检测框和置信度。

整个系统的数据流是:前端页面拿到图片 -> 通过HTTP请求发送给Django后端 -> 后端加载YOLO模型推理 -> 标出检测框 -> 把标注后的图片转成Base64编码返回给前端 -> 前端把图片渲染出来。

1.2 YOLOv5/v8/v11/v12到底怎么选

YOLO系列版本非常多,很多人选型时很纠结。我把自己实际测下来的感受做个对比,方便大家参考。

从架构差异来看,YOLOv5属于上一代经典模型,基于anchor机制,结构是CSPDarknet,训练和部署文档非常完善,很多老项目还在用。YOLOv8用anchor-free设计替代了anchor机制,骨干网络引入C2f结构,逻辑更简洁,训练时少了很多调anchor的超参数,对新手更友好,可以说是现在做目标检测的默认选项。YOLOv11在v8基础上更新了C3k2模块,并优化了颈部网络,在小目标检测上有一定改善,火焰烟雾这类目标有时候早期火焰区域很小,用v11会有一定帮助。YOLOv12是最新加入的版本,引入了区域注意力机制(Area Attention),骨干网络改成R-ELAN结构,精度上确实比前几代更高,但模型复杂度和推理耗时也上去了,环境依赖上偶尔需要额外的编译步骤。

模型版本核心结构训练/部署难度推理速度精度表现适合场景
YOLOv5anchor-based,CSPDarknet低,资料成熟快中等老项目维护、低配环境
YOLOv8anchor-free,C2f低,API友好较快较高首选推荐,通用场景
YOLOv11C3k2模块升级低,兼容性好较快更高中小目标、精度优先
YOLOv12R-ELAN + 注意力机制中,依赖稍复杂中等最高精度演示、前沿对比

我的实际建议是:如果你是第一次做这类系统,直接选YOLOv8,因为它训练代码、文档、部署样例是最丰富的,遇到问题最容易搜到解决方案。如果答辩时需要展示"对多版本模型做过对比实验",可以训练一个v11做精度对比。让系统支持多模型切换其实很简单,后端接口加一个 model_name 参数即可,这个在后端部署部分会说明。

2. 火焰烟雾数据集准备与模型训练实操

2.1 数据集结构与标注格式

训练数据集是整个系统的地基。公开的火焰烟雾数据集其实不少,但质量参差不齐。我这套系统里用的是整理过的火焰、烟雾合并数据集,包含室内火灾、室外森林火灾、夜间火焰、工厂烟雾、隧道烟雾等常见场景,总计约2000张图片。

这里重点说一下标注格式。YOLO系列模型统一使用txt格式的标注文件,每张图片对应一个同名的txt文件,每一行表示一个目标,格式是:

类别编号 归一化中心点x 归一化中心点y 归一化宽度 归一化高度

比如一张图片的宽是1920像素、高是1080像素,一个火焰目标框左下角在(500, 300)、右上角在(900, 700),换算后就是中心点(700, 500)除以宽高得到(0.3646, 0.4630),宽400高400归一化后是(0.2083, 0.3704),对应的标注行就是:

0 0.3646 0.4630 0.2083 0.3704

我用的是两类标注:类别0表示fire火焰,类别1表示smoke烟雾。如果你的场景是室内火灾为主、烟雾和火焰往往同时出现,建议两类都保留,让模型同时学习两种目标的特征,这样比只检测火焰要好,因为很多火灾初期烟雾往往先于明火出现。

有个特别重要的实操经验必须强调:火焰和烟雾的标注边界和普通目标不一样。普通目标比如人、车,边界清晰,框子贴着物体画就行。但火焰边缘是动态变化的,标注时不要试图把每一丝火光都框进去,框住明亮火焰的主体部分就好。烟雾是半透明的、形状不定,标注时框住密度较高、肉眼清晰可见的主烟团,不要把大面积稀薄烟气都画进去,否则模型训练时学到的特征会很杂,反而降低检测精度。

数据划分我建议按8:1:1的比例划分为训练集、验证集、测试集,并且划分的时候要注意同场景的图片不要都跑到同一个集合里,比如同一个房间不同角度的照片要均匀分配。目录结构按YOLO官方约定就行:

dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml

data.yaml文件内容如下:

train: dataset/images/train val: dataset/images/val test: dataset/images/test nc: 2 names: ['fire', 'smoke']

2.2 训练参数配置与训练命令

数据集准备好之后,训练这一步就不复杂了。以YOLOv8为例,Ultralytics提供了一体化的命令行工具,直接执行:

yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 device=0

参数说明一下:model=yolov8n.pt表示加载预训练权重,n是nano版本,模型最轻量,显存占用小,适合快速验证。如果你的机器显存够用,可以换成yolov8s.pt或yolov8m.pt,精度会更高一些。训练epochs设为100,输入图片尺寸imgsz设为640,这是YOLO系列最常用的默认尺寸,兼顾检测精度和速度。

如果是YOLOv5,用的是它的官方仓库,训练命令有点不一样:

python train.py --img 640 --batch 16 --epochs 100 --data data.yaml --weights yolov5s.pt

YOLOv11和v12也走Ultralytics命令,基本和v8一致,只要把权重文件换成对应的yolov11n.pt、yolov12n.pt即可。

有几个训练参数值得单独说。第一是batch size。如果训练途中报 CUDA out of memory,把batch降到8甚至4,同时可以考虑把imgsz从640降到512。火焰烟雾检测对分辨率其实很敏感,因为早期火焰可能占画面比例很小,imgsz过低会漏检严重。第二是训练轮数。100轮对于一个两千数据量的场景通常够用,但每次训练完一定要看results.png里的loss曲线,如果val/loss还在明显下降,说明没收敛,要继续加大epochs到150甚至200。第三是数据增强。YOLO默认开启了Mosaic、随机翻转、HSV扰动等增强策略,火焰烟雾场景下有个注意点:HSV颜色扰动虽然能增加模型对不同光照环境的适应能力,但辐值和饱和度扰动太大会让火焰变成奇怪的紫色、绿色,反而学不到真实的火焰颜色特征。如果发现训练出来的模型对红色物体误报高,优先检查这一项。

训练完成后,在训练输出目录runs/detect/train/下会生成best.pt和last.pt两个权重文件。last.pt是最后一轮的权重,best.pt是验证集上表现最好的权重,部署时一定要用best.pt。一般还会看验证集上的mAP指标,mAP@0.5表示IoU阈值取0.5时的平均精度均值,火焰烟雾二分类场景正常情况应该能达到0.85以上,低于0.7说明数据或训练参数有问题,不建议直接拿去部署。

3. Django Web部署与系统集成

3.1 为什么用Django而不是Flask或FastAPI

模型训练完,接下来就是工程化部署。选Django,我是做过对比的。

Flask确实轻量,代码少,上手快,但它只有最基本的请求路由和视图函数,没有数据库ORM、没有Admin后台、没有用户认证体系,想做一个"成套系统"需要自己拼装很多东西。FastAPI性能好,自动生成接口文档,但团队协作和传统开发模式上Django的老牌生态更扎实。Django自带ORM、Admin后台、表单处理、模板引擎、静态文件管理,对于一个需要展示"前端页面 + 后端接口 + 数据库 + 算法模型"的完整系统来说,Django一套就能串起来,做毕设或者课题演示时系统架构图也更好画。

用Django还有个隐形好处:部署到云服务器时,Django应用可以用uwsgi或gunicorn配合Nginx部署,教程非常多,踩坑概率低。对一个检测系统来说,算法部分反而是核心,硬件环境搭建别在Web框架上浪费太多时间才是正道。

3.2 模型推理服务封装与API接口实现

部署的第一步是把模型推理能力封装成一个独立的模块,避免在视图函数里直接堆模型代码。我会单独建一个model_loader.py文件,用单例模式加载模型,确保Django服务启动后模型只加载一次,而不是每个请求都加载一遍权重。

# detect_app/model_loader.py import os from threading import Lock from ultralytics import YOLO _lock = Lock() _detectors = {} def get_detector(model_name="best.pt"): with _lock: if model_name not in _detectors: weights_path = os.path.join(os.path.dirname(__file__), "..", "weights", model_name) _detectors[model_name] = YOLO(weights_path) return _detectors[model_name]

将模型加载放在锁机制里是为了防止多线程并发请求时重复加载。Django默认的多线程模型下,这个单例锁非常有必要。

接着写视图处理函数。这里做图片上传检测接口,接收前端传过来的图片文件,用OpenCV解码,送入模型推理,把结果画到图上,再编码成Base64字符串返回JSON给前端。

# detect_app/views.py import base64 import cv2 import numpy as np from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from .model_loader import get_detector @csrf_exempt @require_POST def detect_image_api(request): try: img_file = request.FILES.get("image") if img_file is None: return JsonResponse({"error": "no image uploaded"}, status=400) img_bytes = img_file.read() nparr = np.frombuffer(img_bytes, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) if img is None: return JsonResponse({"error": "invalid image"}, status=400) detector = get_detector() results = detector.predict(img, conf=0.25, imgsz=640) boxes_result = [] for box in results[0].boxes: xyxy = box.xyxy.tolist()[0] conf = float(box.conf[0]) cls = int(box.cls[0]) label = f"{results[0].names[cls]} {conf:.2f}" x1, y1, x2, y2 = map(int, xyxy) color = (0, 0, 255) if cls == 0 else (0, 165, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2) boxes_result.append({ "label": results[0].names[cls], "conf": round(conf, 3), "xyxy": xyxy }) _, buffer = cv2.imencode(".jpg", img) img_base64 = base64.b64encode(buffer.tobytes()).decode("utf-8") return JsonResponse({"image_base64": img_base64, "boxes": boxes_result}) except Exception as e: return JsonResponse({"error": str(e)}, status=500)

注意代码里火焰类别用红色框(0,0,255),烟雾类别用黄色框(0,165,255),前端展示时视觉区分更明显。conf=0.25是置信度阈值,低于这个分数的检测结果会被过滤,这个值可以根据实际误报情况调整,误报高就调高到0.3或0.35,漏检明显就调低到0.15。

URL路由配置很简单:

# detect_app/urls.py from django.urls import path from . import views urlpatterns = [ path("api/detect/", views.detect_image_api, name="detect_image"), ]

然后在项目主路由urls.py里include进去。这样一个最基础的后端检测接口就完成了。

3.3 前端交互页面与浏览器摄像头接入

前端页面我用了一个单页HTML,页面顶部是标题栏,中间分成左右区域:左侧放图片上传控件和摄像头按钮,右侧显示检测结果图。核心交互是Ajax请求,避免整页刷新。

图片上传的HTML核心逻辑:

document.getElementById("uploadBtn").addEventListener("click", function() { const fileInput = document.getElementById("imageInput"); if (fileInput.files.length === 0) { alert("请先选择图片"); return; } const formData = new FormData(); formData.append("image", fileInput.files[0]); fetch("/api/detect/", { method: "POST", body: formData }) .then(res => res.json()) .then(data => { if (data.image_base64) { const img = document.getElementById("resultImage"); img.src = "data:image/jpeg;base64," + data.image_base64; img.style.display = "block"; } }); });

摄像头实时检测稍微麻烦一些,我用的是浏览器的navigator.mediaDevices.getUserMedia接口获取摄像头画面,绘制到canvas上,然后定时截取canvas图像发送到后端检测接口,检测完把结果图再渲染到页面上。这里有个性能点必须注意:如果每帧都发送检测,后端推理速度和网络传输会成为瓶颈,实际使用中我会用每秒检测2到3帧的频率,也就是大约每300到500毫秒截取一次帧,这样既能做到准实时,又不会让页面卡死。

let timer = null; document.getElementById("cameraBtn").addEventListener("click", function() { navigator.mediaDevices.getUserMedia({ video: true }) .then(stream => { const video = document.getElementById("cameraVideo"); video.srcObject = stream; video.play(); if (timer) clearInterval(timer); timer = setInterval(captureFrame, 400); }); }); function captureFrame() { const video = document.getElementById("cameraVideo"); const canvas = document.getElementById("cameraCanvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height); canvas.toBlob(blob => { const formData = new FormData(); formData.append("image", blob, "frame.jpg"); fetch("/api/detect/", { method: "POST", body: formData }) .then(res => res.json()) .then(data => { if (data.image_base64) { const img = document.getElementById("resultImage"); img.src = "data:image/jpeg;base64," + data.image_base64; } }); }, "image/jpeg"); }

Django开发调试模式下跑起来很简单,项目根目录执行:

python manage.py runserver 0.0.0.0:8000

0.0.0.0表示监听所有网卡,这样局域网内其他设备可以通过http://你的IP:8000访问演示页面。做演示时直接拿手机或者另一台电脑访问,效果比在一台机器上干巴巴展示截图好很多。

4. 运行演示实测与常见问题排查

4.1 演示流程与实测效果

系统跑通后,演示流程我总结了一个固定套路,按这个顺序走很少翻车:

第一步启动后端服务,先在命令行确认Django启动无报错,模型文件路径正确。第二步浏览器打开系统页面,先上传一张单场景的火焰图片,检查检测框是否准确、置信度是否高,这个作为基础验证。第三步换一张烟雾场景图片,确认烟雾也能检测出来。第四步开启摄像头实时检测,可以用手机打开一张火焰图片对着摄像头晃动,或者现场点一张纸制造一点安全烟雾(注意要在符合安全规范的条件下),体验实时检测帧率。

我用YOLOv8n模型实测,单张640x640图片在GTX级别的显卡上,推理耗时大约在10到20毫秒,加上前后处理,整体响应时间小于100毫秒,完全满足演示需求。YOLOv12精度更高但推理耗时约增长30%到50%,不过作为对比展示还是值得跑一下的。

实测下来,白天自然光场景下,火焰的检测置信度普遍在0.85以上;烟雾目标由于半透明特性,置信度会低一些,一般在0.6到0.8之间。室外远距离的小火焰目标,v8偶尔会漏检,v11和v12的表现明显好一截。这也是为什么我在系统里保持了多模型切换能力——可以让使用者自己对比体验。

4.2 常见问题排查速查表

实际操作中遇到的问题各种各样,我整理了一张排查表,按频率排序:

问题现象可能原因解决办法
训练到一半CUDA out of memorybatch过大或imgsz过大batch降到8/4,imgsz降到512,开启AMP混合精度
训练很多轮loss都不降学习率不当、标注数据有错学习率调到0.001,检查labels文件夹里的标注框是否错位
检测接口返回500错误模型路径不对或图片解码失败先用单独的Python脚本测试模型推理,再接入Django
前端上传中文文件名报错编码处理不规范后端用uuid重命名上传文件,路径中不要出现中文
页面开启摄像头后白屏浏览器权限未开或HTTP环境限制确认浏览器摄像头权限,页面改用localhost或IP访问
检测结果误报高、把红灯橙色灯当火焰场景数据不足,conf阈值太低加入负样本训练,conf提高到0.3以上
烟雾检测框特别小或漏检目标太小,分辨率不够imgsz调到960,或换大模型如yolov8m
Web页面加载模型首帧特别慢模型在第一个请求时才加载项目启动时预热模型,直接在Django启动逻辑里手动调用一次get_detector()

这里有几个排查思路值得单独展开。

训练阶段OOM最让人头疼,其实不用急着换硬件。YOLOv8和v11默认开启了AMP(自动混合精度),会在保证精度的前提下显著降低显存占用,确认自己的训练命令有没有带amp=True。如果再不行,把imgsz从640降到512是一个相对无损的降级手段,配合batch=4基本能跑起来。

Django检测接口报500,我的排查顺序是:先在Django项目目录里开一个Python交互环境,手动调用get_detector().predict()测一张图,如果模型本身有问题,这一步就能发现。如果模型没问题,问题多半出在图片读取上,重点检查cv2.imdecode解码是否返回None。还有一类隐蔽问题是对上传文件大小没做限制,几MB的大图解码和推理会非常慢,前端表现为长时间无响应,可以在API里做图片压缩处理,先等比缩放到最长边小于1280像素再去推理。

摄像头检测卡顿的问题,我用了一个比较实用的小策略:不直接降低帧率,而是维护一个"正在请求中"标记,上一次请求没返回前不发送下一帧,这样相当于按需取帧,既避免画面闪烁又不会堆积请求。

4.3 独家避坑经验分享

最后分享几个常规文档里不会写的经验。

第一个是关于数据集的负样本问题。火焰烟雾检测和普通目标检测有一点很大的不同:现实场景里跟火焰颜色相似的物体太多了。夕阳、路灯、红色车尾灯、电焊火花、甚至橙色的墙面,都有可能在某个角度下被模型误判为火。我第一次训练时纯用火灾正样本,测试时发现对着一个红色广告牌反复误报,置信度甚至到了0.7。后来我去网上收集了一批包含红色物体、橙色灯光但完全没有火灾的负样本,把它们标注成空标签文件,混合进训练数据里,误报率一下就降下来了。负样本不需要很多,大概占总数10%到20%就行。

第二个是关于烟雾检测的"看不见的手"。烟雾本身是半透明的,如果只是简单叠加在背景上,模型非常容易只学到背景纹理而不是烟本身。我给训练集做增强时,特意把一部分烟雾图片调低透明度叠加到不同的室内背景上,生成了一批合成样本。这种做法看起来原始,但对提升烟雾检测鲁棒性效果显著,原因是模型被迫学习"前景和背景之间的关系",而不是死记硬背某个固定背景。

第三个是关于演示部署时模型预热。我第一次给导师演示时,启动Django后第一个请求等了将近两秒才出结果,非常尴尬。原因很简单:模型是懒加载的,第一个请求进来时才加载权重、初始化CUDA。解决方式是在Django的apps.py里重写ready()方法,或者直接在启动脚本里调用一次get_detector()预先加载模型,让服务一旦就绪、模型就已经在显存里待命了。

第四个是版本兼容问题。YOLOv5的权重文件结构跟v8/v11/v12不同,不能混用,如果你在系统里保留了多模型切换,后端加载时一定要绑定对应的Ultralytics版本。我在requirements.txt里固定了ultralytics==8.x.x,避免之后更新库导致旧权重读取失败。

这套系统如果想继续扩展,后续方向其实很多:比如接入视频流做持续检测、加入火焰区域面积占比报警逻辑、对接消息推送系统、做模型量化部署到边缘设备等。就我目前的体验来说,基于深度学习的火焰烟雾检测方案,在准确率和实时性上已经能够满足大部分消防监控场景的需求,核心难点往往不在模型本身,而在于数据质量、工程封装和场景适配这三件事上。做一遍完整的训练、部署、演示流程,远比零散看论文调代码更能理解目标检测项目到底是怎么落地的。

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

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

立即咨询