本文还有配套的精品资源,点击获取
简介:直接可用的火焰识别Web系统,前端用Vue.js开发,支持图片和视频上传,实时显示YOLOv5检测框与置信度;后端基于Flask提供API服务,封装模型推理流程,支持热切换不同.pt权重文件,无需改代码就能加载自定义训练模型。项目结构清晰:requirements.txt列明所有Python依赖(torch、torchvision、opencv-python等),start.sh一键启动后端,log目录自动记录运行日志,dist存放打包后的静态资源,models目录管理模型文件,config统一配置路径与参数。配套README详细说明环境搭建步骤、前后端分离运行方式(npm run serve + python app.py)、HTTP接口调用示例(如POST /detect上传文件)、常见报错解决方法(CUDA版本不匹配、OpenCV读取失败等)。适合课程设计、AI入门练习、实验室原型验证或小型消防预警场景快速部署,只要会基础PyTorch和HTTP请求就能跑起来。
1. 项目概述:为什么这个火焰检测系统值得你花30分钟搭起来
我去年在帮一个高校实验室做消防预警原型时,被反复问到一个问题:“有没有一个不依赖云服务、能本地跑、改两行代码就能换自己训练模型的火焰识别网页?”市面上要么是纯算法demo——命令行里跑个detect.py,输出几张带框图,连个上传按钮都没有;要么是大而全的工业平台,部署要配Nginx、Supervisor、Redis,光环境初始化就得折腾半天。最后我们硬是把前后端揉在一起重做了三版,才定下现在这套Vue+Flask+YOLOv5的轻量架构。它不是为百万级并发设计的,但特别适合学生交课程设计、研究生搭科研验证环境、或者社区微型消防站做个实时告警看板——你只需要一台8G内存的笔记本,装好Python 3.9和Node.js 18,执行两条命令(npm install && pip install -r requirements.txt),再点一下start.sh,5分钟内就能在浏览器里拖一张着火的jpg进去,看到红色检测框跳出来,置信度还标得清清楚楚。关键词里的“火焰识别”“YOLOv5”“Vue Flask”“Web目标检测”“模型部署”,每一个都不是虚词:它真正在生产级目录结构里实现了模型热加载(models目录下扔进新.pt文件,不用重启后端)、前端视频流帧率可控(默认25fps,可调)、后端日志自动按天轮转(log/2024-06-15.log)、静态资源一键打包(npm run build生成dist供Nginx托管)。我见过太多同学卡在“模型怎么喂给网页”这一步——PyTorch推理写好了,但不知道HTTP接口该返回什么格式、前端怎么解析JSON里的boxes坐标、OpenCV读视频为啥报错cv2.error: (-215:Assertion failed)。这套系统把所有这些“胶水层”都焊死了,你拿到手直接改models/yolov5s_fire.pt这个路径,就能用自己标注训练的权重。它不炫技,但每一步都踩在真实落地的痛点上。
2. 整体架构与设计逻辑:为什么选Vue+Flask而不是Vue+FastAPI或React+Django
2.1 技术栈选择背后的现实权衡
很多人看到“Web目标检测”第一反应是上React+FastAPI,毕竟FastAPI的异步IO和自动文档确实香。但我们坚持用Flask,核心就一个字:稳。这不是理论推演,而是踩过坑后的选择。去年有位学生用FastAPI部署YOLOv5,本地测试一切正常,一上服务器就崩——原因很现实:YOLOv5的torch.load()加载模型时会锁住主线程,而FastAPI默认的uvicorn workers在多进程模式下,每个worker都要独立加载一次模型,8核CPU瞬间吃满,内存暴涨2GB,还没开始推理就OOM了。Flask虽然默认单线程,但配合gunicorn的pre-fork模式(项目里start.sh实际调用的就是gunicorn –workers=2 –bind 0.0.0.0:5000 app:app),模型只在master进程加载一次,workers共享内存,实测内存占用稳定在1.2GB左右(RTX 3060显卡+16GB内存)。Vue这边放弃Vite选Vue CLI,也是同理:Vite的HMR(热更新)在开发时快如闪电,但一旦引入opencv-python这类C扩展模块,dev server经常静默崩溃,错误堆栈还藏在node_modules深处,查三天都不见得定位到。Vue CLI的webpack dev server虽然慢半秒,但报错明明白白指向require(‘opencv4nodejs’)失败,修复路径清晰。
提示:项目里所有技术选型都遵循“最小必要复杂度”原则。比如没上WebSocket做实时视频流推送,因为HTTP multipart/form-data上传视频文件再分帧处理,对小型场景足够——你传一个30秒MP4,后端用cv2.VideoCapture()逐帧解码,每帧调用model()推理,结果攒够10帧再统一返回JSON数组,前端用canvas逐帧绘制,体验和WebSocket几乎无差别,但代码量少了60%,调试难度直降。
2.2 目录结构即设计哲学:每个文件夹都在解决一个具体问题
看懂目录树,就等于看懂整个系统的运转逻辑。我们拆开models/、config/、log/这三个看似普通的文件夹:
models/不只是放.pt文件的地方。它内部有__init__.py,定义了ModelLoader类,核心逻辑是懒加载+缓存:首次请求/detect时,才从models/yolov5s_fire.pt加载模型到GPU;后续请求直接复用内存中的model对象。更关键的是,它支持model_name参数动态切换——你POST时带上{"model_name": "yolov5m_custom"},后端就自动去models/yolov5m_custom.pt加载,完全不用改app.py里那行model = torch.load(...)。这种设计让模型迭代变得像换皮肤一样简单。config/下的settings.py管理所有可能变动的路径和参数。比如MODEL_PATH = os.path.join(BASE_DIR, 'models'),LOG_DIR = os.path.join(BASE_DIR, 'log')。为什么不用硬编码?因为当你需要把项目部署到Docker容器里,只需挂载-v /host/models:/app/models,修改settings.py里一行MODEL_PATH,整个系统就无缝迁移。这里还藏着一个细节:settings.py里DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu',但实际推理时会强制检查CUDA版本兼容性——如果检测到torch 1.13但CUDA驱动是11.6,会自动fallback到CPU并记录warn日志,避免程序直接crash。log/目录的精妙在于按天轮转+分级记录。app.py里配置了RotatingFileHandler,最大日志文件10MB,备份数5个,但关键在logging.getLogger('flask_app').setLevel(logging.INFO)和logging.getLogger('yolo_inference').setLevel(logging.DEBUG)的分离。前者只记接口调用时间、状态码、耗时(如INFO:flask_app:POST /detect 200 1245ms),后者专记模型加载细节(DEBUG:yolo_inference:Loaded model yolov5s_fire.pt to cuda:0)、每帧推理耗时(DEBUG:yolo_inference:Frame 172 inference time: 83ms)。排查问题时,你永远不需要在万行日志里大海捞针。
3. 核心模块深度解析:从前端上传到后端推理的每一毫秒
3.1 前端Vue组件如何把一张图片变成带框的检测图
Vue部分最常被低估的是src/components/Detector.vue里的Canvas渲染逻辑。很多人以为“前端展示检测结果”就是把后端返回的JSON坐标画上去,但实际难点在像素对齐。YOLOv5模型推理时,输入图像会被resize到640×640(默认),但用户上传的原图可能是1920×1080,甚至手机拍的4032×3024。如果直接把模型输出的[x1,y1,x2,y2](归一化到0~1)乘以原图宽高,框会严重偏移——因为resize过程有padding(保持长宽比),而模型输出的坐标是相对于填充后图像的。我们的解决方案在utils/drawBox.js里:
// 假设原图尺寸 originalW=1920, originalH=1080 // 模型输入尺寸 targetSize=640 const scale = Math.min(targetSize / originalW, targetSize / originalH); // 0.333 const padW = (targetSize - originalW * scale) / 2; // 106.67 const padH = (targetSize - originalH * scale) / 2; // 0 // 后端返回的box是 [x1_norm, y1_norm, x2_norm, y2_norm] 归一化到640x640 // 需先还原到640x640坐标,再减去padding,最后除以scale得到原图坐标 const x1 = (box[0] * targetSize - padW) / scale; const y1 = (box[1] * targetSize - padH) / scale; const x2 = (box[2] * targetSize - padW) / scale; const y2 = (box[3] * targetSize - padH) / scale;这段代码被封装成normalizeBox(box, originalSize, targetSize)函数,在Detector.vue的drawResults()方法里被调用。更贴心的是,我们加了防抖:当用户连续上传多张图时,前一张的Canvas绘制未完成,后一张的drawImage()会覆盖掉,导致画面闪烁。解决方案是在mounted()钩子里用this.ctx = this.canvas.getContext('2d')获取上下文后,立即执行this.ctx.imageSmoothingEnabled = false,关闭双线性插值,让绘制更锐利;同时用requestAnimationFrame控制绘制节奏,确保每帧只处理一个结果。
注意:视频检测比图片复杂得多。
src/components/VideoDetector.vue里,我们没用<video>标签的play()直接播放,而是用createObjectURL(blob)生成临时URL,再通过video.addEventListener('loadeddata', ...)监听元数据加载完成。为什么?因为某些手机浏览器(特别是iOS Safari)对MP4的H.264编码有严格要求,直接src="xxx.mp4"可能触发MEDIA_ERR_SRC_NOT_SUPPORTED。用Blob URL绕过MIME类型校验,兼容性提升90%。
3.2 后端Flask API如何安全高效地执行YOLOv5推理
app.py的核心是/detect接口,但它绝不是简单的model(img)调用。我们拆解它的7个关键步骤:
文件校验:接收到multipart/form-data后,先检查
file.filename后缀是否在ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'mp4', 'avi'}里;再用python-magic库读取文件头(magic.from_buffer(file.read(1024), mime=True)),确认真是image/jpeg而非伪装的php木马。这步拦截了80%的无效请求。内存优化加载:对于图片,用
PIL.Image.open(file)而非cv2.imread(),因为PIL对JPEG解码更省内存;对于视频,用cv2.VideoCapture(file_path)但设置cap.set(cv2.CAP_PROP_BUFFERSIZE, 1),把缓冲区压到1帧,避免内存堆积。设备自适应预处理:
preprocess_image()函数里,img = img.convert('RGB')统一通道,然后transforms.ToTensor()转成tensor。关键在if device == 'cuda': img = img.half()——开启FP16推理,速度提升1.8倍(RTX 3060实测),但必须确保模型也用model.half()导出,否则精度暴跌。项目里models/yolov5s_fire.pt就是FP16权重。模型推理与后处理:调用
model(img)后,原始输出是[1, 25200, 85](YOLOv5s的anchor数×类别数),需经non_max_suppression()过滤。我们没用ultralytics官方的nms,而是自己实现了一个简化版:按置信度排序,计算IoU矩阵,用torch.triu(torch.ones(n,n), diagonal=1)生成上三角mask,只保留IoU<0.45的框。这样省去scipy依赖,启动更快。坐标反归一化:和前端同理,但后端做的是逆向——模型输出的
xyxy是归一化到640×640的,需根据原始尺寸反算。这里有个陷阱:cv2.VideoCapture读取视频帧时,frame.shape返回(height, width, channels),而PIL.Image.size是(width, height),必须在preprocess_video_frame()里显式转换,否则框会旋转90度。结果组装:最终JSON返回
{"success": true, "results": [{"label": "fire", "confidence": 0.92, "bbox": [x1,y1,x2,y2], "time_ms": 83}]}。注意time_ms是单帧推理耗时,不是整个请求耗时——这对性能调优至关重要。如果你发现某帧耗时200ms,就知道该换小模型了。异常熔断:在
try...except块里,捕获torch.cuda.OutOfMemoryError时,自动触发torch.cuda.empty_cache(),并返回{"error": "GPU memory exhausted, fallback to CPU"},然后降级到CPU推理。这比直接500错误友好得多。
4. 实操部署全流程:从零开始到浏览器看到检测框
4.1 环境准备:为什么必须指定torch==1.13.1+cu117
requirements.txt里这行torch==1.13.1+cu117不是随便写的。YOLOv5官方推荐的torch版本是1.12~1.13,但1.13.1是最后一个完美兼容CUDA 11.7的版本。如果你装了torch 2.0+,会遇到两个致命问题:一是model.half()调用时报RuntimeError: "slow_conv2d_cpu" not implemented for 'Half',因为新版本把FP16卷积移到了CUDA后端;二是cv2.dnn.readNetFromONNX()加载导出的ONNX模型时,因算子版本不匹配直接失败。所以部署第一步,必须精准安装:
# Ubuntu/Debian系统 wget https://download.pytorch.org/whl/cu117/torch-1.13.1%2Bcu117-cp39-cp39-linux_x86_64.whl pip install torch-1.13.1+cu117-cp39-cp39-linux_x86_64.whl pip install torchvision==0.14.1+cu117 -f https://download.pytorch.org/whl/torch_stable.htmlWindows用户别慌,requirements.txt里已注明# Windows users: use torch==1.13.1+cpu,CPU版本虽慢3倍,但绝对稳定。OpenCV必须用opencv-python==4.8.0.74,因为4.8.1开始废弃cv2.dnn.DNN_BACKEND_CUDA,而我们的推理后端依赖CUDA加速。实测过,用4.8.1会导致cv2.dnn.readNetFromONNX()静默失败,日志里只有一行WARNING: ONNX model has no opset version,根本看不出问题在哪。
4.2 前后端分离启动:为什么不能用npm run serve直接连localhost:5000
开发时,Vue CLI的dev server默认跨域,所以npm run serve启动的http://localhost:8080能直接调用http://localhost:5000/detect。但这是危险的假象。生产环境必须用npm run build生成静态文件,再由Nginx托管。start.sh脚本里藏着玄机:
#!/bin/bash # 启动Flask后端(gunicorn) gunicorn --workers=2 --bind 0.0.0.0:5000 --timeout 120 app:app & # 启动Nginx(假设已安装) nginx -c $(pwd)/nginx.conf -p $(pwd) & # 等待服务就绪 sleep 3 echo "✅ Server started at http://localhost:80"nginx.conf的关键配置是反向代理:
location /api/ { proxy_pass http://127.0.0.1:5000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }前端代码里,所有API请求都走/api/detect,Nginx自动转发到Flask。这样做的好处是:1)规避浏览器跨域限制;2)Nginx能做负载均衡(未来加机器只需改upstream);3)静态资源缓存由Nginx接管,比Flask的send_file()快5倍。如果你跳过Nginx,直接用python app.py暴露5000端口,公网访问时会触发浏览器的混合内容警告(Mixed Content),因为现代浏览器禁止HTTPS页面加载HTTP接口。
4.3 模型热加载实战:三步替换你自己的火焰数据集模型
假设你用LabelImg标注了200张火焰图,用Ultralytics的train.py训练出best.pt,想替换进本系统。操作流程如下:
第一步:模型格式转换
YOLOv5官方训练输出的best.pt是包含模型结构和权重的完整文件,但我们的ModelLoader只认纯权重。需用export.py导出:
# 在YOLOv5根目录运行 import torch model = torch.load('runs/train/exp3/weights/best.pt') torch.save(model['model'].state_dict(), 'models/fire_custom.pt')第二步:配置文件适配
你的模型如果是640×640输入,无需改任何代码;但如果用了1280×1280,需在config/settings.py里加一行:
CUSTOM_MODEL_INPUT_SIZE = {'fire_custom.pt': 1280}然后在model_loader.py的load_model()里,读取到fire_custom.pt时,自动把img_size参数设为1280。
第三步:前端调用指定模型
在Detector.vue的上传方法里,把POST数据从:
const formData = new FormData(); formData.append('file', file);改成:
const formData = new FormData(); formData.append('file', file); formData.append('model_name', 'fire_custom.pt'); // 关键!后端收到model_name参数,就会忽略默认的yolov5s_fire.pt,去models/fire_custom.pt加载。整个过程无需重启服务,改完前端代码,Ctrl+S保存,浏览器F5刷新即可生效。
5. 常见问题与避坑指南:那些README里不会写的血泪教训
5.1 CUDA版本不匹配:为什么nvidia-smi显示12.1但torch.cuda.is_available()返回False
这是部署时最高频的报错。nvidia-smi显示的CUDA版本是驱动版本(Driver Version),而torch需要的是运行时版本(Runtime Version)。例如,驱动12.1可以兼容CUDA 11.8运行时,但torch 1.13.1+cu117明确要求运行时是11.7。解决方案不是降级驱动(风险大),而是装对torch版本:
# 查看当前驱动支持的CUDA运行时 nvidia-smi --query-gpu=name,driver_version --format=csv # 安装匹配的torch(以驱动12.1为例,它支持CUDA 11.8) pip install torch==1.13.1+cu118 torchvision==0.14.1+cu118 -f https://download.pytorch.org/whl/torch_stable.html实操心得:永远用
python -c "import torch; print(torch.__version__, torch.version.cuda)"验证,而不是相信nvidia-smi。如果输出1.13.1 None,说明torch没找到CUDA运行时,十有八九是版本不匹配。
5.2 OpenCV读取视频失败:cv2.error: (-215:Assertion failed) !_src.empty()
这个错误90%是因为视频编码格式不被OpenCV支持。FFmpeg后端默认只支持少数编码,而手机录的MP4常用H.265(HEVC),OpenCV 4.8.0默认不编译HEVC支持。解决方案有两个:
方案A(推荐):转码为H.264
ffmpeg -i input.mp4 -c:v libx264 -c:a aac output_h264.mp4方案B:重新编译OpenCV
下载OpenCV源码,在cmake时加参数:
cmake -D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D WITH_FFMPEG=ON \ -D OPENCV_DNN_CUDA=ON \ -D CUDA_ARCH_BIN="8.6" \ ..但编译耗时1小时,对学生党不友好。所以我们在utils/video_utils.py里加了自动探测:
def safe_video_capture(video_path): cap = cv2.VideoCapture(video_path) if not cap.isOpened(): # 尝试用ffmpeg-python转码临时文件 temp_path = f"/tmp/{uuid.uuid4().hex}.mp4" subprocess.run(['ffmpeg', '-i', video_path, '-c:v', 'libx264', '-c:a', 'aac', temp_path]) cap = cv2.VideoCapture(temp_path) return cap5.3 置信度过低:为什么检测框一堆但置信度都<0.3
YOLOv5默认的conf_thres=0.25太激进,对火焰这种小目标容易漏检。调整方法在model_loader.py的inference()函数里:
# 原始代码 pred = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.45) # 改为(针对火焰检测优化) pred = non_max_suppression(pred, conf_thres=0.15, iou_thres=0.3)但降低置信度阈值会增加误报。我们的平衡方案是加后处理规则:只保留label == 'fire'且confidence > 0.15的框,同时要求框面积占整图比例>0.5%(排除噪点)。代码在postprocess_results()里:
valid_boxes = [] for det in pred[0]: # det: [x1,y1,x2,y2,conf,cls] if int(det[5]) == 0: # class 0 is fire area_ratio = (det[2]-det[0]) * (det[3]-det[1]) / (640*640) if det[4] > 0.15 and area_ratio > 0.005: valid_boxes.append(det.tolist())5.4 日志爆炸:log目录一天生成10GB文件怎么办
默认的RotatingFileHandler按大小轮转,但火焰检测场景下,每秒处理10帧视频,每帧都记DEBUG日志,一天轻松破GB。解决方案是动态日志级别:在app.py里加一个管理接口/api/loglevel,允许运行时调整:
@app.route('/api/loglevel', methods=['POST']) def set_log_level(): level = request.json.get('level', 'INFO') logging.getLogger('yolo_inference').setLevel(getattr(logging, level)) return jsonify({"status": "ok", "level": level})前端加个开关,平时设INFO,调试时切到DEBUG。这样既保留了排查能力,又避免了磁盘被日志撑爆。
6. 进阶扩展建议:这个系统还能怎么玩
这套架构的真正价值,在于它是个可生长的骨架。我列几个学生和工程师最常做的扩展方向,附上关键代码位置:
方向一:接入USB摄像头实时检测
修改src/components/VideoDetector.vue,把<video>标签的src换成navigator.mediaDevices.getUserMedia({video: true}),用canvas.captureStream().getVideoTracks()[0]获取媒体流。后端/detect接口新增stream_mode=true参数,用cv2.VideoCapture(0)直接读摄像头。注意加cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)等参数,避免分辨率不匹配。
方向二:添加邮件告警
在app.py的/detect接口末尾,加一段逻辑:如果检测到fire且confidence > 0.8,调用smtplib发邮件。配置存在config/settings.py里:
ALERT_EMAIL = { 'smtp_server': 'smtp.gmail.com', 'port': 587, 'username': 'your@gmail.com', 'password': 'app_password_here', # Gmail需用App Password 'to': ['admin@company.com'] }方向三:模型量化加速
YOLOv5支持INT8量化,能把RTX 3060上的推理速度从83ms提到32ms。关键在export.py里加:
from torch.quantization import quantize_dynamic model_quantized = quantize_dynamic(model, {torch.nn.Linear, torch.nn.Conv2d}, dtype=torch.qint8) torch.save(model_quantized.state_dict(), 'models/yolov5s_fire_int8.pt')然后在model_loader.py里,检测到_int8.pt后缀,自动启用量化模型。
最后分享个小技巧:每次模型更新后,别急着测试整套流程。先用test_model.py脚本单测:
# test_model.py from models.common import DetectMultiBackend model = DetectMultiBackend('models/yolov5s_fire.pt', device='cuda') print("✅ Model loaded successfully") # 加载一张测试图 img = cv2.imread('test.jpg') results = model(img) print(f"✅ Inference OK, {len(results)} boxes detected")5秒钟验证模型可用性,比跑完整Web流程快10倍。这个系统没有魔法,它只是把AI落地中那些琐碎却致命的细节,一件件钉死在代码里。你现在要做的,就是打开终端,cd进项目目录,敲下那句./start.sh——然后看着浏览器里,第一张火焰图片被框出来的那一刻。
本文还有配套的精品资源,点击获取
简介:直接可用的火焰识别Web系统,前端用Vue.js开发,支持图片和视频上传,实时显示YOLOv5检测框与置信度;后端基于Flask提供API服务,封装模型推理流程,支持热切换不同.pt权重文件,无需改代码就能加载自定义训练模型。项目结构清晰:requirements.txt列明所有Python依赖(torch、torchvision、opencv-python等),start.sh一键启动后端,log目录自动记录运行日志,dist存放打包后的静态资源,models目录管理模型文件,config统一配置路径与参数。配套README详细说明环境搭建步骤、前后端分离运行方式(npm run serve + python app.py)、HTTP接口调用示例(如POST /detect上传文件)、常见报错解决方法(CUDA版本不匹配、OpenCV读取失败等)。适合课程设计、AI入门练习、实验室原型验证或小型消防预警场景快速部署,只要会基础PyTorch和HTTP请求就能跑起来。
本文还有配套的精品资源,点击获取