简介:一份面向火灾与烟雾检测任务的完整实践包,适合计算机视觉学习者、安防消防项目开发者,以及需要快速构建检测模型的技术人员。数据集包含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 放在 0,smoke 放在 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.pt,best 是验证集上 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
训练日志里每一轮会打印 mAP@0.5 和 mAP@0.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 mAP@0.5。假如输出里 smoke 这一行 R 只有 0.6,fire 的 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 张带标签图像加上预训练模型,真正的价值是给你省去标注时间和环境搭建成本,剩下的准确性优化,还是得靠自己对场景的理解去调。希望这趟踩坑记录能帮你少走几步弯路,把精力留在解决真实场景的问题上。
本文还有配套的精品资源,点击获取