多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

火灾烟雾检测数据集+ YOLOv5训练部署全流程实战

火灾烟雾检测数据集+ YOLOv5训练部署全流程实战 简介一份面向火灾与烟雾检测任务的完整实践包适合计算机视觉学习者、安防消防项目开发者以及需要快速构建检测模型的技术人员。数据集包含2059张真实场景图像覆盖大火/小火、建筑/草原/森林/车辆起火车辆类型包括汽车、卡车、摩托车、电动车同时涵盖白天/黑夜、室内/室外等多样化条件烟雾场景与火场景一一对应。所有图像均配有Pascal VOC格式的XML标注标签类别为fire标注与图像一一对应组织规范可直接用于YOLOv5等模型的训练与评估。压缩包共332个文件整体约200MB除图像与标注外还提供Python训练与格式转换脚本、YAML模型配置、C/CUDA底层源码、预训练权重pt/weights以及详细使用说明帮助用户完成从数据准备、格式转换、模型训练到最终推理的完整流程。已有839人学习下载适合希望节省数据采集与标注成本、快速启动烟雾火灾检测项目的读者。1. 火灾烟火烟雾检测数据集2059 张图先看懂标注再谈训练部署做火灾检测的人应该都遇到过同一个尴尬网上公开的烟火数据集不是太旧就是类别太杂真正带统一标注、能直接喂给 YOLOv5 的少之又少。这份火灾烟火烟雾检测数据集一共 2059 张图像每张都配了 txt 标签并且附带了已经训练好的 yolov5 烟雾火灾检测模型和使用说明。也就是说你不需要从零开始标数据也不用等到训练完才知道效果拿回来先跑一次推理就能看到模型能力边界。这套资源适合两类人一是想快速验证 YOLOv5 在消防场景里能跑到什么程度的新手二是已经在做安防、无人机巡检、园区监控需要一份干净数据做迁移训练或算法比对的工程师。不过我要先泼一盆冷水数据集和模型是两回事数据集的目录结构、类别 id、标注坐标是否和你的训练脚本对齐直接决定了后面几天的效率。所以这篇笔记就从目录和标签讲起一步一步把训练、验证、部署的链路走通。2. 目录与标签格式先理清清单再动手训练2.1 常见资源目录images 和 labels 如何对应我拿到这类带 YOLO 标签的数据集第一件事不是急着打开 train.py而是先看目录结构。YOLOv5 约定俗成的格式是 images 文件夹放原图labels 文件夹放同名 txt 标注文件名主体必须一一对应否则训练时大量图像会被自动跳过。这份资源的典型目录结构大概是这样的dataset_root/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── classes.txt别看目录简单坑通常在细节里。打开 labels/train 下的任意一个 txt你会看到类似这样的内容0 0.512 0.483 0.364 0.122 1 0.211 0.376 0.158 0.089每行五个数字含义依次是类别 id、目标中心点 x 坐标、中心点 y 坐标、目标宽度 w、目标高度 h。注意后四个值全部是归一化坐标取值范围在 0 到 1 之间是用真实像素除以图像宽高得到的。这样做的好处是不同分辨率下标签通用坏处是一旦原图和标签不匹配你肉眼根本看不出来。我常用的做法是先写一个极简检查脚本读一张图和它的标签文件把归一化坐标反算回像素再在原图上画框。这个动作花不了五分钟但能立刻发现标签错位、类别 id 混乱、坐标超界三类问题。import cv2 import numpy as np img cv2.imread(images/train/000001.jpg) h, w img.shape[:2] with open(labels/train/000001.txt, r) as f: lines f.read().strip().splitlines() for line in lines: cls_id, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, str(int(cls_id)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check.jpg, img)脚本逻辑很简单读取图片尺寸把归一化坐标乘回宽高得到左上角和右下角像素坐标再画框。参数上我建议 cls_id 暂时按字符串打印不要只凭记忆判断 0 是火还是烟因为不同数据集的类别顺序经常不一样有的把 smoke 放 0有的把 fire 放 0直接决定后面 data.yaml 的 names 顺序。2.2 标签卫生检查坐标超界、空标签、类别不平衡数据集下载下来之后训练之前务必做一次标签卫生检查常见问题有三个第一坐标值出现负数或大于 1这通常是标注工具导出时出了问题第二txt 文件是空文件意味着这张图没有目标而 YOLO 会把空标签当成背景样本处理数量过多会影响训练第三类别分布严重失衡比如 fire 有 5000 个框而 smoke 只有 200 个框训练出来 smoke 的 recall 会非常难看。检查空标签和超界坐标我一般直接跑一段统计脚本import os from pathlib import Path label_dir Path(labels/train) empty [] out_of_range [] cls_count {} for txt in label_dir.glob(*.txt): lines [l for l in txt.read_text().strip().splitlines() if l] if not lines: empty.append(txt.name) continue for line in lines: parts line.split() if len(parts) ! 5: empty.append(txt.name) continue cls int(parts[0]) cls_count[cls] cls_count.get(cls, 0) 1 vals [float(p) for p in parts[1:]] if any(v 0 or v 1 for v in vals): out_of_range.append(txt.name) print(空标签数量:, len(empty)) print(坐标超界数量:, len(out_of_range)) print(类别分布:, cls_count)这段统计脚本不需要 GPU运行成本几乎为零但它能帮你规避之后 90% 的诡异训练问题。参数上稍微解释两句vals是归一化坐标理论上必须在 0 到 1 之间如果出现 1.05 这类值说明标签生成时图像尺寸读取和实际尺寸不一致cls_count则是给你看类别比例的如果某个类别不到另一个类别的十分之一建议后续做数据增强时单独处理。3. 用 YOLOv5 训练自定义烟雾模型data.yaml、超参与 anchor 的完整链路3.1 data.yaml 与模型选择2059 张图怎么分配最合理数据检查完成之后先做 data.yaml。YOLOv5 的数据配置是个 yaml 文件里面指定 train 和 val 的路径、类别数量、类别名字。注意这里的路径要写绝对路径或者相对于工程根目录的路径新手最常见的报错就是路径写错导致读取不到图片。train: /data/dataset_root/images/train val: /data/dataset_root/images/val test: /data/dataset_root/images/test nc: 2 names: [fire, smoke]我这里把 fire 放在 0smoke 放在 1这只是演示写法。实际使用前一定要回到上一章确认你的标签里 0 到底对应哪一类。原因很简单模型训练时只认数字 id不认字符串names 顺序和标注文件里 cls_id 不一致训练出来的模型在推理时会把火和烟完全搞反。模型选择上2059 张图属于小规模数据集我一般建议用 yolov5s 起步。s 模型参数量适中显存占用低训练速度快在烟火这种目标特征相对明显的场景里效果和 m 模型的差距并不大。如果你的目标是部署到树莓派或者 RK 系列边缘设备甚至可以考虑 yolov5n但那是后话。先把 s 模型跑通再根据实际效果决定是否降级或升级。3.2 训练命令与超参数batch、epochs、anchors 怎么调启动训练的命令如下python train.py \ --data fire_smoke.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --cache \ --device 0参数逐个说清楚--data指向刚才建的 yaml--weights用 COCO 预训练权重的好处是模型已经具备通用特征提取能力烟火检测任务和 COCO 中的 fire 类有一定重叠迁移学习能明显加快收敛--img 640是输入分辨率训练和推理要用同一个值否则最终效果会打折扣--batch 16在 8GB 显存的显卡上比较稳妥如果你的显存是 16GB 可以加到 32但小数据集上 batch 过大反而容易过拟合--epochs 150对 2000 多张图来说够用了再多几轮训练 loss 基本不再下降--cache把图像提前加载到内存里能省去每轮 epoch 重新读盘的时间前提是你内存够大--device 0指定第一块 GPU。另一个容易被忽略的参数是--multi-scale。烟火目标大小差异极大近处大火占据半幅画面远处烟柱只有几十个像素多尺度训练能让模型对不同尺寸目标更鲁棒。我会在训练命令里追加--multi-scale代价是训练时间增加约 20%但对检测效果的正向帮助值得。超参数方面YOLOv5 默认的 hyp.scratch-low.yaml 可以先用着不需要一上来就改。真要说优先级我最先关注的是anchor。YOLOv5 默认会在训练开始时用 k-means 重新计算当前数据集的锚框前提是你没有加--noautoanchor。训练日志里如果出现anchors: kmean相关输出说明自动计算已经生效。只有当数据集里目标尺寸分布极不均匀时我才手动去改 anchor 的初始值。训练结束后模型保存在runs/train/exp/weights/best.pt和last.ptbest 是验证集上 mAP 最高的权重last 是最后一个 epoch 的权重。实践上训练过程中可能出现过拟合此时 last 反而不如中间某个 epoch 的效果好所以验证和部署一律用 best.pt。4. 推理与验证conf-thres、iou-thres 与 mAP 怎么读才算数4.1 detect.py 预测从单张图到视频流阈值怎么调训练完成之后先用官方 detect.py 跑一跑验证集里的图片直观感受模型找烟火的能力。命令是python detect.py \ --weights runs/train/exp/weights/best.pt \ --source /data/dataset_root/images/val \ --conf-thres 0.35 \ --iou-thres 0.5 \ --img 640--conf-thres是置信度阈值只有得分高于这个值的框才会被保留。--iou-thres是 NMS 的交并比阈值用于消除同一目标周围的重复框数值越小抑制越狠。对烟火场景我建议把 conf-thres 放低一些比如 0.25 到 0.35因为烟在视觉上本来就模糊模型对它的置信度天然低于火阈值设在 0.5 会把大量真烟漏掉。这个权衡背后是火灾检测的核心诉求漏报的代价远高于误报。宁可多画几个假框也不能让真实烟雾溜过去。如果你后续接的是消防告警系统误报可以由后端逻辑二次确认但漏报没有后悔药。4.2 验证指标别只看 mAP要看单类别的 P 和 R训练日志里每一轮会打印 mAP0.5 和 mAP0.5:0.95很多人训练完只看这两个数字大就开心但烟火数据集经常出现一个假象整体 mAP 不错细看烟这个类别的 recall 低得可怜。我的习惯是用官方val.py单独跑一次详细验证命令如下python val.py \ --weights runs/train/exp/weights/best.pt \ --data fire_smoke.yaml \ --img 640 \ --conf-thres 0.25 \ --iou-thres 0.5跑完之后看每个类别的Class P R mAP0.5。假如输出里 smoke 这一行 R 只有 0.6fire 的 R 是 0.9那就说明模型对烟的漏检严重。这时候不需要急着加大训练轮数优先检查数据集里烟的样本占比、烟目标的尺寸分布以及是否很多烟雾与背景对比度极低。如果单类 R 不达标常见的补救手段有三条一是把烟类别单独做增强例如随机亮度调整和加高斯模糊模拟不同光照下的烟形态二是给烟目标增加难例挖掘把漏检的图挑出来重新标注并加入训练集三是调整 NMS 后处理对烟类别使用更低的 conf-thres 和更低的 iou-thres尽量让模型先框出来再说。5. 避坑 / 常见问题训练曲线的“假好”与烟火的误报排查5.1 现象mAP 训练时很高拿到真实监控场景误报成堆原因验证集和训练集来自同一个数据源场景单一模型实际上是记住了训练数据里的环境纹理而不是学习到了烟火本身的结构特征。这在小数据集上尤其明显2059 张图如果都来自相似的室内或室外场景模型泛化能力非常有限。解决把测试集换成完全没见过的场景图来评估比如从视频里手动截取几张夜间、雨天、逆光的画面。如果条件允许用不同 camera 拍一段 30 秒视频做推理观察误报主要出现在哪类物体上再把难例加入训练集做增量训练。5.2 现象训练能跑完但是 detect 时完全没有输出原因最常见的是 data.yaml 中 names 顺序和标签文件不一致或者推理时加载了 last.pt 而不是 best.pt或者输入图像的尺寸和训练时不一致导致特征尺度完全错位。解决先用python detect.py --source 某张训练图排除权重问题如果训练图能检出说明权重没问题问题出在输入数据上。确认推理图像的预处理方式YOLOv5 会做 letterbox 缩放训练用的是 640推理也必须用 640否则检测框的坐标归一会错位到几乎无法命中。5.3 现象远处小火点完全漏检conf-thres 调到 0.1 也检不出来原因小目标在 YOLOv5 的 8 倍下采样特征图上只占几个像素特征信息不足。尤其是烟在远处呈半透明状和背景混在一起模型根本没有可用特征。解决一是开启--multi-scale重训让模型适应不同尺度二是把--img从 640 提到 1280推理时小目标对应的像素区域更大检测率能明显提升三是考虑在数据预处理阶段对图像做切片把大图切成四块分别检测再合并结果。最后这条虽然增加耗时但在无人机巡检场景里很实用。5.4 现象loss 一直下降但 val 的 mAP 波动很大原因数据集小验证集划分不固定每个 epoch 的验证结果方差大。另一个原因是前提超参里设定了较大的学习率训练后期权重在最优解附近震荡。解决在train.py的--cos-lr参数让学习率在训练后半段按余弦曲线平滑下降同时固定 train/val 的划分不要每次都随机取否则不同 epoch 之间对比没有意义。小数据集上我还会把--patience设为 30连续 30 轮 val mAP 不提升就自动早停省时省力。5.5 现象标签里有些目标的面积小于 1 个像素训练时被 YOLO 自动忽略原因标注工具放大后能画出 2x2 像素的框但 YOLOv5 在解析标签时会把宽或高小于一定阈值的框当作无效样本跳过导致总框数和训练日志不一致也浪费时间。解决训练前做一次标签过滤把小于 3x3 像素的框删掉或并入邻近框。烟雾目标本身是弥散形态极小框即便保留也没有实际检测意义。用一段脚本统计所有标注框的最小宽高低于阈值就直接剔除省得训练时悄无声息被忽略。6. 把模型落到监控现场导出、量化与视频流抽帧部署6.1 模型导出为 ONNX 与边缘设备部署训练好的 best.pt 要部署到监控现场通常不是直接跑 python。先把模型导出成 ONNX再视硬件情况转成 TensorRT 或 RKNN。导出的命令python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --opset 12 \ --simplify--include onnx指定导出格式--opset 12是 ONNX 算子版本对大多数推理框架兼容性最好--simplify会用 onnx-simplifier 去掉冗余计算节点让模型更小、推理更快。导出之后找一个 ONNX Runtime 环境先跑一遍确认输出张量和在 PyTorch 下推理结果一致再去做 TensorRT 的 fp16 量化。需要提醒的是烟火检测场景我不建议直接上 INT8 量化视觉上我们要的是泛灰的烟和暗红色的火焰边缘INT8 对这类低对比度目标的表征力不够漏检率会明显上升。实测下来 fp16 是精度和速度的平衡点推理速度基本能翻倍精度损失可以忽略。6.2 视频流抽帧检测与误报重确认部署到监控现场我不会每帧都检测而是每两秒取一帧做检测。原因很直接烟火变化是缓慢过程每秒 25 帧全量推理对 GPU 的压力不必要地大。抽帧策略下GPU 空闲时间去处理其他摄像头的画面整体性价比更高。我的习惯是给检测结果加一个二次确认机制单帧检出烟或火不直接告警连续两帧都检出才触发同时把原始帧保存下来归档。这能滤掉汽车大灯、红色广告牌这类偶发误报又不至于明显增加响应延迟。这套思路落实下来之后我每次拿到新的烟火检测资源都会强制走一遍同样的流程先查标签卫生再跑 one epoch 快速验证数据链路最后做小目标专项评测。数据集的可用性从来不在描述里而在你跑通一遍之后的结果里。2059 张带标签图像加上预训练模型真正的价值是给你省去标注时间和环境搭建成本剩下的准确性优化还是得靠自己对场景的理解去调。希望这趟踩坑记录能帮你少走几步弯路把精力留在解决真实场景的问题上。本文还有配套的精品资源点击获取
返回列表