
简介本资源面向目标检测方向的工程技术人员、高校学生及课题研究者提供一套可直接运行的火焰检测完整方案涵盖YOLO格式数据集、已训练模型文件与QT可视化界面既能满足实际工程项目开发需求也适合技术入门者快速查看识别效果或作为大学生课程设计与论文实验的基础素材。压缩包共234个文件约997.51MB包含jpg、jpeg等火焰图像样本yaml数据配置与模型参数py源码与pyc编译文件pt权重文件以及png界面截图、sh脚本、csv训练日志、Dockerfile部署文件等覆盖数据、训练、推理到界面展示的完整链路。目前已有935人学习下载。模型文件已训练完成代码可直接运行读者可据此复现火焰识别流程、替换自有数据微调模型并借助QT界面直观验证检测结果省去从零搭建环境与调试的时间成本。1. 火焰检测算法落地从 YOLO 数据集到 QT 界面的一条完整链路厂区里那台老监控摄像头拍到的画面烟雾已经起来了值班室的屏幕却还在安静地轮播。这种场景我见过不止一次后来自己动手做了一套火焰检测的小系统才明白问题不在摄像头而在从数据到界面这条链路上每一环都容易断。火焰检测算法本身不神秘YOLO 系列做目标检测已经足够成熟真正让人翻车的是数据集标得对不对、模型文件怎么导出、QT 界面怎么把视频流和推理结果拼到一起。这篇笔记就按这条链路走一遍从 YOLO 格式数据集的准备到模型训练与导出再到 QT 界面上位机的视频监控界面搭建每一步都给出能直接抄的命令和参数。适合手里有摄像头、想自己搭一套火焰检测演示或小规模部署的工程师也适合刚接触 YOLO 部署教程、想找一个完整项目练手的人。不追求论文级精度追求的是整条链路能跑通、能复现、知道坑在哪。2. YOLO 格式火焰数据集从打标到训练集划分的完整操作2.1 火焰检测为什么优先选 YOLO 格式而不是 COCO火焰检测这个任务有个特点目标形态极不固定。早期火苗可能只有几个像素中期火焰边缘模糊后期又可能连成一片。用 COCO 那种多边形分割标注标注成本高而且火焰边界本身就没有清晰定义标出来的掩码一致性很差。YOLO 格式用的是矩形框加类别索引标注速度快对火焰这种“大致范围”比“精确轮廓”更重要的任务来说反而更稳。另一个现实原因是生态。YOLOv5、YOLOv8 这一系列训练脚本默认吃的就是 YOLO 格式目录结构简单images 放图labels 放同名 txt每行class x_center y_center width height坐标全部归一化到 0 到 1。你从 LabelImg 打标完 YOLO 格式的标导出的就是这种 txt不需要再写转换脚本。如果一开始选了 COCO JSON后面还要写一轮格式转换转换脚本里归一化算错、类别索引对不上都是血泪经验。我一般会先把所有原始图片按 8:1:1 分好 train、val、test 三个目录再分别建 images 和 labels 子目录。这样后面写 data.yaml 的时候路径清晰不会出现训练时找不到验证集的情况。2.2 用 LabelImg 打标并检查 YOLO txt 的四个边界坑LabelImg 的安装和基本操作网上教程很多这里只说火焰检测场景下容易出问题的四个点。第一类别只设一个fire。有人会把烟雾也标成smoke结果模型在早期火焰上疯狂误检因为烟雾和火焰在低分辨率下纹理太像。如果确实要区分建议先只做 fire 单类跑通后再加类。第二标注框不要贴着火苗最外沿画。火焰边缘是渐变的贴边画会让模型学到大量背景噪声。我一般框到肉眼能确认是火焰的区域留一点余量。第三保存格式选 YOLO不是 PascalVOC。LabelImg 默认可能是 VOC XML要在保存前切换。切换后每张图对应一个 txt文件名和图片名一致。第四打完标一定要抽查归一化坐标。写个十行脚本随机抽几张把框画回图上看看。import cv2 import os import random img_dir dataset/images/train lbl_dir dataset/labels/train names os.listdir(img_dir) random.shuffle(names) for name in names[:5]: img cv2.imread(os.path.join(img_dir, name)) h, w img.shape[:2] lbl_path os.path.join(lbl_dir, os.path.splitext(name)[0] .txt) with open(lbl_path) as f: for line in f: cls, xc, yc, bw, bh map(float, line.split()) # 反归一化回像素坐标 x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(check, img) cv2.waitKey(0) cv2.destroyAllWindows()这段代码的逻辑是读图读对应 txt把归一化坐标乘回宽高得到像素框画上去人工看。参数上注意xc、yc是框中心点bw、bh是宽高都是相对整图的比例。如果画出来的框明显偏移说明打标时图片被缩放或旋转过LabelImg 里要重新检查。2.3 写 data.yaml 与用 YOLOv8 训练火焰数据集的命令数据集目录整理好后在项目根目录建一个fire.yamlpath: ./dataset train: images/train val: images/val test: images/test nc: 1 names: [fire]path是数据集根目录train、val、test是相对 path 的图片目录YOLO 会自动去找同级的 labels 目录。nc是类别数火焰单类就是 1。names顺序要和打标时的类别索引一致LabelImg 里第一个类别索引是 0。训练命令用 YOLOv8 的 CLIyolo detect train \ datafire.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/fire \ nameexp1modelyolov8n.pt是 nano 版本火焰检测如果部署在边缘设备上nano 够用且快。imgsz640是输入分辨率火焰目标小的话可以提到 960但显存和速度要权衡。patience20表示验证集指标 20 轮不提升就早停防止过拟合。lr0初始学习率火焰数据集通常几千张图0.01 是安全起点。训练过程中重点看mAP50和mAP50-95。火焰检测里 mAP50 到 0.85 以上基本可用但更要看验证集上的漏检。如果漏检集中在远距离小火苗说明小目标召回不够可以试试提高 imgsz 或加 P2 小目标检测层。2.4 数据集划分与增强参数怎么设才不翻车火焰数据集有个隐蔽问题同一段视频抽帧出来的图片如果随机划分训练集和验证集里会出现几乎一样的帧验证指标虚高。正确做法是按视频源划分同一段视频的帧只进训练集或只进验证集。增强参数方面YOLOv8 默认开了 mosaic、mixup、hsv 增强。火焰检测里 hsv 的色调增强要小心火焰颜色从红到黄到蓝都有但把红色火焰调成绿色就变成噪声了。我一般把hsv_h降到 0.01hsv_s和hsv_v保持默认。mosaic 对火焰有帮助因为能合成多火源场景但close_mosaic设 10 到 20让最后几轮用原图微调。如果数据集里负样本无火画面太少模型会把所有偏红偏亮的区域都当火。负样本比例我一般控制在 1:5 到 1:10也就是每 5 到 10 张有火图配 1 张无火图。3. 模型文件导出与推理从 .pt 到部署可用的格式3.1 训练完的 .pt 模型文件里到底存了什么YOLOv8 训练完在runs/fire/exp1/weights/下会生成best.pt和last.pt。best.pt是验证集指标最好的那一轮last.pt是最后一轮。很多人直接拿last.pt部署结果发现效果不如验证时好因为最后一轮可能已经过拟合。我一般用best.pt。.pt文件里不只是权重还包含模型结构、类别名、输入尺寸等元信息。用torch.load能看到这些。但部署时不一定用 PyTorchQT 界面如果走 C 路线就需要转成 ONNX 或 TensorRT。3.2 导出 ONNX 与用 Python 做火焰推理的最小代码导出 ONNXyolo export modelruns/fire/exp1/weights/best.pt formatonnx opset12 simplifyTrueopset12兼容性较好simplifyTrue会做图优化。导出后在同目录得到best.onnx。用 ONNX Runtime 推理的最小代码import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name img cv2.imread(test.jpg) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) resized cv2.resize(img_rgb, (640, 640)) blob resized.astype(np.float32) / 255.0 blob np.transpose(blob, (2, 0, 1))[None, ...] outputs session.run(None, {input_name: blob}) # YOLOv8 输出形状为 [1, 4nc, 8400]取置信度最高的框 preds outputs[0][0] scores preds[4:].max(axis0) best_idx np.argmax(scores) if scores[best_idx] 0.5: x, y, w, h preds[:4, best_idx] print(fire detected, x, y, w, h)逻辑说明预处理要把 BGR 转 RGB、缩放到 640、归一化到 0 到 1、转成 NCHW。输出是[1, 4nc, 8400]前 4 行是框的 xywh后面是各类别分数。0.5是置信度门限火焰检测里这个门限可以调到 0.4 提高召回但误报会增多需要根据场景权衡。3.3 置信度门限与 NMS 参数在火焰场景下的调法火焰检测的置信度门限不是固定值。白天光照强、背景有暖色物体时门限要高0.5 到 0.6夜间或烟雾遮挡时门限要低0.3 到 0.4。我一般会在 QT 界面上留一个滑动条让值班人员能手动调。NMS 的 IoU 阈值默认 0.45。火焰如果连成一片多个框会重叠IoU 阈值太低会把真实火区分割成多个框太高又会保留重复框。火焰场景我一般设 0.5 到 0.6让连片火焰合并成一个框。3.4 模型文件版本管理与回滚习惯每次训练完我会把best.pt、best.onnx、fire.yaml、训练命令和验证集指标写进一个version.md放在runs/fire/exp1/下。这样三个月后回头看知道这个模型是用什么数据、什么参数训出来的。后悔药就是版本管理别等部署翻车了才去找当初的配置。4. QT 视频监控界面把火焰检测接进上位机4.1 QT 界面选型QWidget 还是 QMLPython 还是 CQT 做视频监控界面常见做法是 QWidget 加 QLabel 显示视频帧用 QTimer 定时刷新。QML 更适合做动画和触屏交互但火焰检测界面主要是视频显示加几个按钮和滑动条QWidget 足够。语言上如果推理用 Python 的 ONNX Runtime界面也用 PyQt 或 PySide整条链路都是 Python开发快。如果部署在嵌入式板子上C 加 QT 加 TensorRT 性能更好但开发周期长。我一般先用 PySide6 做原型验证效果后再决定要不要转 C。4.2 用 PySide6 搭一个能显示火焰检测结果的视频界面下面是一个最小可运行的 PySide6 界面打开摄像头每帧跑 ONNX 推理画框显示。import sys import cv2 import numpy as np import onnxruntime as ort from PySide6.QtCore import QTimer, Qt from PySide6.QtGui import QImage, QPixmap from PySide6.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, QPushButton class FireMonitor(QWidget): def __init__(self): super().__init__() self.setWindowTitle(火焰检测监控) self.label QLabel() self.label.setAlignment(Qt.AlignCenter) self.btn QPushButton(开始/暂停) self.btn.clicked.connect(self.toggle) layout QVBoxLayout(self) layout.addWidget(self.label) layout.addWidget(self.btn) self.session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) self.input_name self.session.get_inputs()[0].name self.cap cv2.VideoCapture(0) self.timer QTimer() self.timer.timeout.connect(self.update_frame) self.running False def toggle(self): if self.running: self.timer.stop() else: self.timer.start(30) # 约 33fps self.running not self.running def update_frame(self): ret, frame self.cap.read() if not ret: return h, w frame.shape[:2] img cv2.resize(frame, (640, 640)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0 blob np.transpose(img, (2, 0, 1))[None, ...] preds self.session.run(None, {self.input_name: blob})[0][0] scores preds[4:].max(axis0) idx np.argmax(scores) if scores[idx] 0.5: x, y, bw, bh preds[:4, idx] # 映射回原图尺寸 x1 int((x - bw / 2) * w / 640) y1 int((y - bh / 2) * h / 640) x2 int((x bw / 2) * w / 640) y2 int((y bh / 2) * h / 640) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(frame, FIRE, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2) rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) qimg QImage(rgb.data, rgb.shape[1], rgb.shape[0], rgb.strides[0], QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg).scaled(self.label.size(), Qt.KeepAspectRatio)) def closeEvent(self, event): self.cap.release() event.accept() app QApplication(sys.argv) win FireMonitor() win.resize(900, 700) win.show() sys.exit(app.exec())逻辑说明QTimer每 30 毫秒触发一次update_frame读摄像头帧预处理后送 ONNX 推理置信度超过 0.5 就画红框和 FIRE 文字。QImage从 numpy 数组构造时要注意 strides否则图像会斜。scaled保持宽高比避免视频变形。参数上timer.start(30)控制刷新率太高会占满 CPU太低会卡顿。火焰检测不需要 60fps20 到 30fps 足够。置信度门限 0.5 可以改成从滑动条读取。4.3 视频帧刷新与推理线程分离避免界面卡死的做法上面的代码在 UI 线程里跑推理如果模型大或 CPU 慢界面会卡。正确做法是把推理放到 QThread 里通过信号把结果传回 UI 线程。PySide6 里可以继承QThread在run里循环读帧和推理用Signal发画好框的帧。我一般会用一个FrameGrabber线程负责读摄像头一个InferenceWorker线程负责推理UI 线程只负责显示。这样即使推理偶尔慢一帧界面也不会冻结。线程间用Queue传帧注意加锁或限制队列长度防止内存涨。4.4 界面上的置信度滑动条与报警逻辑怎么接QT 界面加一个QSlider范围 0 到 100映射到 0.0 到 1.0 的置信度门限。滑动条值变化时更新推理线程里的门限变量。报警逻辑可以简单做连续 5 帧检测到火焰才触发报警避免单帧误报。报警可以是界面变红、播放声音或写日志。写日志时记录时间戳和置信度方便事后排查。5. 火焰检测落地避坑五条踩坑记录与排查路径5.1 现象训练 mAP 很高实际部署漏检严重原因验证集和训练集来自同一段视频的相邻帧指标虚高。另外验证集图片可能经过 resize和部署时摄像头原始分辨率不一致。解决按视频源划分数据集验证集用完全没在训练中出现的场景。部署前用实际摄像头拍一段测试视频抽帧跑推理统计漏检率。如果漏检集中在远距离提高输入分辨率或加小目标层。5.2 现象QT 界面显示的视频颜色发蓝或发绿原因OpenCV 读进来是 BGRQImage默认按 RGB 解释通道顺序错了。解决在构造QImage前用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)转一次。如果用的是Format_BGR888就不用转但 PySide6 里这个格式支持不如 RGB888 稳。5.3 现象ONNX 推理结果和 PyTorch 不一致原因预处理没对齐。PyTorch 训练时用的是 RGB、归一化到 0 到 1、letterbox 填充而推理时直接 resize 没做 letterbox导致框偏移。解决推理预处理严格复现训练时的 letterbox记录缩放比例和填充偏移画框时再映射回去。YOLOv8 的predict模式内部做了这些自己写 ONNX 推理时要手动补。5.4 现象摄像头读帧越来越慢内存持续上涨原因VideoCapture缓冲区堆积或者推理线程和 UI 线程之间队列没限制长度。解决设置cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)减少缓冲。线程间队列用queue.Queue(maxsize2)满了就丢旧帧。火焰检测丢几帧不影响报警但内存涨会直接崩。5.5 现象夜间红外画面下模型几乎不工作原因训练集全是可见光白天画面红外成像的火焰纹理和颜色分布完全不同。解决如果摄像头有红外模式训练集里要加入红外场景的火焰图。没有的话夜间可以切回可见光加补光灯或者单独训一个红外火焰模型按时间切换。6. 把火焰检测从演示推到可用几个我常做的验证与调优动作最后一章说几个让这套东西从“能跑”到“敢用”的具体动作。第一个是构建一个固定测试集包含白天、夜间、有烟、无烟、远距离、近距离各若干张每次模型更新都跑一遍记录漏检和误报。这个测试集不参与训练只做验收。第二个是置信度门限的动态调整我一般会在 QT 界面上留一个“灵敏度”滑动条值班人员根据现场情况调同时后台记录调整前后的误报率用数据决定默认值。第三个是模型文件的热切换。部署后如果发现漏检不想重启整个界面可以在 QT 里加一个“加载模型”按钮用QFileDialog选新的 ONNX重新创建InferenceSession旧 session 释放。注意切换时暂停推理线程避免半路换模型导致崩溃。第四个是日志与回放。每帧检测到火焰时把时间戳、置信度、框坐标写进 CSV同时保存前后 5 秒的视频片段。这样误报后能回看判断是模型问题还是场景问题。我一般用cv2.VideoWriter在检测到火焰时开始写连续 5 秒无火就停。第五个是性能验证。在目标机器上跑 1000 帧统计平均推理耗时和峰值内存。如果推理耗时超过帧间隔就要降分辨率或换更小的模型。火焰检测的底线是 10fps 以上低于这个值班人员会觉得卡。我自己的习惯是每次改完参数或换模型都先在测试集上跑一遍再在实际摄像头前站十分钟看有没有玄学误报。这套链路不复杂但每个环节都有细节希望帮到你。本文还有配套的精品资源点击获取