☰
火灾烟雾检测数据集2059张+YOLOv5标签转换与训练避坑指南
2026/10/10 15:30:42 网站建设 项目流程

简介:这套火灾烟火烟雾检测数据集面向目标检测、安防监控与火灾预警方向的开发者,提供2059张覆盖大火小火、建筑/草原/森林/车辆起火、白天/黑夜、室内/室外等多样场景的带标签图像,按Pascal VOC格式组织,Annotations中为XML标注,JPEGImages为原始图像,ImageSets包含训练验证划分。随包附带基于YOLOv5的火灾烟雾检测模型、使用说明以及voc_label.py等格式转换脚本,可快速完成VOC转YOLO并开展微调与部署验证。资源共332个文件,整体约200MB,除了JPEG图像与XML标注外,还包含77个o目标文件、70个c源文件、61个h头文件、31个py脚本、16个yaml配置、11个cu文件以及pt、weights、cfg等模型相关文件,覆盖darknet框架的编译、训练、推理与转换全流程。目前已有839人学习下载,是火灾检测项目可直接使用的数据与基线模型方案,适合需要系统训练和快速实验的工程师与学生。

1. 火灾烟火烟雾检测数据集:2059张图像+标签能帮你绕开哪些坑

火灾烟雾检测最头疼的不是模型结构,而是数据。监控摄像头要想识别远处若有若无的烟和刚起势的火苗,需要几千张带标签的现场图,光靠人工标注很容易标到崩溃。这套“火灾烟火烟雾检测数据集(2059张图像含标签)+yolov5烟雾火灾检测模型+使用说明”给的就是一条完整落地路径:2059张已标注图像、一个能直接用的YOLOv5烟雾火灾检测模型和说明文档。它适合做消防告警、安防监控和森林防火的开发者:你既可以用它训练出自己的模型,也能借它理解自己的数据该怎么整理。下文按处理这类项目的经验展开,讲数据怎么拆、标签怎么转、模型怎么训、现场哪些地方最容易翻车。新手能照着走,熟手直接看参数边界。

2. 拆解2059张标注图像:目录结构、标签格式与VOC转YOLO脚本

2.1 先摸清家底:统计文件分布和类别数量

拿到一个带图像的检测数据集,第一件事不是急着训练,而是先把目录和标签普查一遍。常见做法是压缩包里有 images(或 JPEGImages)、labels(或 Annotations)、一个 class_names.txt 和一份使用说明。先跑几个命令把文件数和类别分布确认下来。

# 统计图像数量,jpg/png 都算 find images -name "*.jpg" -o -name "*.png" | wc -l # 统计标签文件数量,YOLO 格式是 txt,VOC 格式是 xml find labels -name "*.txt" | wc -l # 查看所有标签里出现过的类别编号(txt 首列) cat labels/*.txt | awk '{print $1}' | sort | uniq -c

第一条命令确认是不是恰好 2059 张;第二条确认标签文件是否完整,我见过有些包里的 xml 齐全、txt 缺了一半的情况,这类问题不查会直接导致训练时大量空标注;第三条非常关键,它把每个 txt 的首列 class id 拉出来排序。如果预期的 smoke、fire 两类分别对应 0、1,结果却冒出 2、3,说明标注文件里混了别的类别,或者类别顺序被改过,后面训练会非常难受。

这个统计在 Windows 上也能做,用 PowerShell 的 Get-ChildItem 同样能数,但我建议你直接把项目环境切到 Linux 或 WSL 下再操作。后面所有训练命令都是 Linux 习惯,路径分隔符统一,坑少很多。我一般还会顺手统计图片尺寸分布,因为烟雾和火灾经常是远距离小目标,如果多数图是 1080p 而标注框很小,训练时就要考虑提升输入分辨率,而不是无脑 640。

2.2 标签格式选型:VOC的XML和YOLO的txt,为什么训练前要统一

这份数据集里的标注对象通常是两类:fire 和 smoke。标注格式不外乎两种:LabelImg 导出的 VOC XML,或者标注时直接存成 YOLO 的 txt。VOC 的 XML 适合人工检查和二次编辑,字段里有 object 的 name,还有 bndbox 的 xmin、ymin、xmax、ymax;YOLO 的 txt 则是一行一个目标,五个数字依次是 class id、归一化中心 x、归一化中心 y、归一化宽、归一化高。YOLOv5 原生吃 txt,所以如果拿到手的是 XML,第一步就是转换。

<!-- VOC XML 示例:一个 fire 目标 --> <annotation> <filename>smoke_001.jpg</filename> <size><width>1920</width><height>1080</height></size> <object> <name>fire</name> <bndbox> <xmin>812</xmin><ymin>433</ymin> <xmax>978</xmax><ymax>610</ymax> </bndbox> </object> </annotation>

转换后对应的 YOLO txt 行如下。注意 YOLO 的坐标是中心点加宽高的归一化值,而 VOC 给的是左上角和右下角绝对坐标。转换时要用图片真实宽高做除法,不能只依赖 XML 里的 size 字段。

# 格式:class_id, x_center, y_center, width, height(均归一化) 0 0.4661 0.4830 0.0865 0.1640

很多包里的 XML 和 JPG 是分开整理的,图片被重压缩后宽高没变时倒还好,一旦有过缩放,XML 里的 size 就可能失真。这也是为什么我的转换脚本在运行时重新用 OpenCV 读一遍图片尺寸,而不是信任 XML 里的 size 标签。YOLOv5 的类别顺序以 data.yaml 的 names 为准,常见做法是names: ['fire', 'smoke'],那 fire 就是 0、smoke 就是 1。如果你原来 XML 里的 name 顺序是 smoke 在前,转换脚本里的 classes 列表就必须严格保持这个顺序,否则训练完推理时会把 smoke 当 fire 输出,现场告警直接错乱。这里就是标签格式选型最容易埋雷的地方。

2.3 写一个VOC转YOLO的转换脚本:坐标归一化与防越界

直接给一份我常用在火灾数据集上的转换脚本。它做四件事:读 XML、按 class_names 映射类别、写 YOLO 格式 txt、顺手处理越界框。

import os import cv2 import xml.etree.ElementTree as ET # 和 data.yaml 里的 names 顺序保持一致 CLASSES = ["fire", "smoke"] def convert_voc_to_yolo(voc_dir, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(voc_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(voc_dir, xml_file)) root = tree.getroot() # 运行时读取真实宽高,避免依赖 XML 里的 size img_path = os.path.join(img_dir, root.find("filename").text) img = cv2.imread(img_path) if img is None: print(f"skip missing image: {img_path}") continue h, w = img.shape[:2] lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in CLASSES: continue cls_id = CLASSES.index(name) bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) # 归一化中心坐标和宽高 x_center = ((xmin + xmax) / 2) / w y_center = ((ymin + ymax) / 2) / h box_w = (xmax - xmin) / w box_h = (ymax - ymin) / h # 裁剪到 [0,1],防止标注线画到图外 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) box_w = min(box_w, 1.0) box_h = min(box_h, 1.0) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") if lines: txt_name = os.path.splitext(xml_file)[0] + ".txt" with open(os.path.join(out_dir, txt_name), "w") as f: f.write("\n".join(lines)) else: print(f"empty after convert: {xml_file}") convert_voc_to_yolo("annotations", "images", "labels")

说明几个参数:CLASSES的顺序必须和最终 data.yaml 里的 names 完全一致,这是整个转换的核心,排错时必须盯住。w和h用cv2.imread实时读取,防止图片被预处理后尺寸变化。坐标做min(max())裁剪,能挡住标注手滑把 bndbox 画到图像边缘外的越界框。末尾的空标签直接跳过,避免 YOLOv5 训练遇到空 txt 时报 negative sample 相关的难懂报错。

还有一个坑:如果一张图里有多个目标,YOLO 格式要求每行一个目标,不能像 VOC 那样并列在多个 object 里;如果某个目标类别不在 CLASSES 里,脚本会静默跳过,可能让某张图的真值数量变少。建议转换完后再跑一次统计,比对 XML 里的 object 总数和 txt 行数是否一致。

2.4 划分 train/val:2059张图怎么切不容易让模型过拟合

转换之后要划分训练集和验证集。很多人图省事,直接随机切 9:1,但对视频抽帧来的火灾数据,这个做法很危险。同一段烟雾视频的连续帧之间外观几乎一样,随机切会让同一场景同时出现在训练集和验证集里,验证分数虚高,现场换一个镜头就崩。正确做法是按视频片段或拍摄时间划分。

import os, random, shutil random.seed(42) images = sorted(os.listdir("images")) random.shuffle(images) split = int(len(images) * 0.9) train_files = images[:split] val_files = images[split:] # 建立目录结构 for split_name, files in [("train", train_files), ("val", val_files)]: os.makedirs(f"images/{split_name}", exist_ok=True) os.makedirs(f"labels/{split_name}", exist_ok=True) for f in files: shutil.copy(f"images/{f}", f"images/{split_name}/{f}") txt = f.replace(".jpg", ".txt").replace(".png", ".txt") src_txt = f"labels/{txt}" if os.path.exists(src_txt): shutil.copy(src_txt, f"labels/{split_name}/{txt}")

参数说明:random.seed(42)固定随机种子,保证每次重跑结果一致,这对可复现很重要。random.shuffle是列表原地操作,后面紧跟的split是对全体图像按比例切分。如果发现按文件名随机切的效果仍然虚高,就改成按目录名分组切:比如 src_01、src_02 这样的源摄像头目录,整个目录进训练或验证,不做跨目录取样。

2059 张图像不算多,划分后训练集通常不到 1900 张。这种体量下,我更建议用 90% 训练、10% 验证,而不是 80/20,因为烟雾形态本身变化大,验证集留多了会让训练数据不够。如果后面你觉得现场泛化差,再把验证集比例往上调,但不要动划分方式本身。

3. 用YOLOv5跑通烟雾火灾训练:环境、命令与超参数怎么调

3.1 搭环境:依赖版本与GPU/CPU选择

YOLOv5 官方仓库拉下来后,requirements.txt 会带一批依赖,用虚拟环境装,避免污染系统 Python。常用做法是 conda 建 Python 3.9 环境,然后先装与 CUDA 匹配的 PyTorch,再装其余依赖。

git clone https://github.com/ultralytics/yolov5 cd yolov5 conda create -n smoke_fire python=3.9 -y conda activate smoke_fire # 以 cu117 为例,实际按你的 CUDA 版本选 pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install -r requirements.txt

依赖版本别乱升。YOLOv5 对 torch 版本有隐式依赖,太新的 torch 会导致某些算子编译报错,太旧则不支持新卡。装完先用官方权重跑一次python detect.py --weights yolov5s.pt --source data/images/bus.jpg确认环境通。如果没有 NVIDIA GPU,CPU 跑 100 轮会很痛苦,可以把 imgsz 降到 448、epochs 降到 50 做冒烟测试,或者用云 GPU 训练、本地 CPU 推理。我不建议在树莓派 4B 上训练,那台机器更适合部署推理。

显存占用要提前算:8G 显存跑 yolov5s、batch=16、imgsz=640 基本是上限,16G 显存可以尝试 yolov5m 或者把 imgsz 提到 960。对于 2059 张图的数据量,yolov5s 已经足够,yolov5m 能提升一点精度,但训练时间会翻倍。

3.2 准备 data.yaml:类别顺序、路径与验证集

YOLOv5 训练自己的数据集时,data.yaml 是唯一和业务绑定的配置文件。下面是我给烟雾火灾项目常用的模板。

# data.yaml train: /absolute/path/to/dataset/images/train val: /absolute/path/to/dataset/images/val nc: 2 names: 0: fire 1: smoke

路径建议用绝对路径,相对路径在换机器或换工作目录时容易找不着。nc必须和 names 数量一致。顺序必须和标签 txt 里的 class id 对应:第 2 章转换脚本的 CLASSES 是["fire", "smoke"],这里 names 就必须先 fire 后 smoke。验证集不参与训练,但训练时会用它每 10 轮算一次验证指标,所以 val 路径不能指向训练集,否则指标失真。

还要注意路径不能有中文字符和空格。我用 YOLOv5 处理过带中文目录的数据集,coco 工具链在读取标注路径时对中文支持不稳定,训练到一半报文件找不到,非常折腾。尽量把所有数据集放到dataset/这种纯英文路径下,从根目录开始就不要混入中文。

3.3 训练命令与核心参数:epochs、batch、imgsz、超参数

训练命令本身不复杂,参数边界才是决定成败的地方。

python train.py \ --data data.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --cache \ --name smoke_fire_run

--weights yolov5s.pt用 COCO 预训练权重做迁移学习,收敛速度远快于从零训练;--cache把所有图像加载到显存或内存,2059 张图总量不大,能省掉训练时读盘的等待;--imgsz 640是训练输入边长,火焰这种稍大目标没问题,但烟雾如果是远景小目标,我建议直接用 960,代价是显存占用变大,可配合--batch-size 8。--epochs 100对这份数据量是合理起点,如果训练集太小,100 轮基本已经能看到过拟合拐点。

YOLOv5 的超参数写在data/hyp.scratch-low.yaml里,训练时会自动加载。我一般会改两个:mosaic=1.0对密集小目标有奇效,但 2059 张小数据里 mosaic 会产生大量半真半假的图像,如果发现早期 loss 震荡,可以降到 0.5;hsv_h、hsv_s等颜色增强对夜间烟雾很重要,烟雾是半透明白灰色,颜色增强太强会让模型把白墙当烟,建议把饱和度增强从默认 0.7 降到 0.4。改完超参数后存一份新文件,再在训练命令里加--hyp data/hyp.scratch_low_custom.yaml。

训练过程中要盯三个东西:results.png里 train_loss 和 val_loss 的走势;每轮结束后的best.pt和last.pt;以及confusion_matrix.png是否出现类别混淆。如果 train_loss 一直降、val_loss 在某个 epoch 后回升,说明开始过拟合,可以用提前停止,或者增加验证集比例。最终部署用的是best.pt,它按验证集 mAP 自动挑选,不是最后一轮的last.pt。

4. 训练踩坑与排查:标签、小目标和昼夜场景的常见翻车点

4.1 类别顺序错乱:loss正常但AP全为0

现象:训练日志显示 loss 在下降,但验证集的 mAP 始终接近 0,PR 曲线几乎贴底。

原因:转换脚本的 CLASSES 顺序和 data.yaml 的 names 不一致。比如转换脚本里写的是["smoke", "fire"],标注时把烟当成 0、火当成 1,但 data.yaml 却写names: {0: fire, 1: smoke},那模型学到的类别语义就是反的。由于 loss 仍然能收敛,很多人在这一步懵很久。

解决:训练前先做一次标签抽查,打印几张图的 txt 首行和对应 xml 的 object name 对照。

# 随机抽 5 个 txt,打印每行第一个数字 for f in labels/train/*.txt; do head -1 "$f"; done | sort | uniq -c # 再抽对应 xml,打印 object name grep -r "<name>" annotations/ | head -5

如果发现 0 对应 smoke、1 对应 fire,有两个改法:一是改 data.yaml 的 names 顺序,二是重新跑转换脚本统一顺序。不要在训练中途去改标签,那会让缓存里的数据索引失效,产生更隐蔽的错位。这个坑我在用 YOLOv5 训练自己的数据集时踩过一次,后来把“先查标签分布”写进了 checklist,再没犯过。

4.2 小目标烟雾被漏检:网络结构里的下采样瓶颈

现象:近处大火框得很准,远处轻烟全漏,尤其是刚起火时那种稀薄的白烟。

原因:YOLOv5 网络结构图里,CSPDarknet 的 stride=32 分支对 16×16 像素以下的目标响应很弱。摄像头拍到的远距离烟可能只有 10×10 像素,经过多次下采样后特征几乎消融,检测器自然看不到。

解决:优先把 imgsz 从 640 提到 960 或 1280。代价是显存占用按平方涨,2059 张图的数据量下,1280 容易过拟合,我一般折中到 960。如果现场是实时视频流,还可以用切片推理,把一帧切分成多个 640×640 的 patch 分别检测,再合并结果。我试过先用图像超分辨率重建放大两倍再检测,对夜间远处白烟有一点帮助,但单张耗时增加明显,只适合低帧率巡检或事后分析,不适合报警链路。

另外还有一个隐蔽因素:训练时 mosaic 增强会把小目标拼到大图上,大幅改变目标尺寸分布。如果发现小目标漏检,先不要急着换大模型,试着把--mosaic 0.5在自定义超参数文件里调低,验证集小目标 AP 可能会明显改善。

4.3 训练集和验证集同场景相邻帧:mAP虚高,现场翻车

现象:验证集 mAP 能到 0.95,可一接到实时视频就疯狂误报漏报,置信度阈值怎么调都不对。

原因:划分数据集时用了随机切,监控视频抽帧里同一场景的相邻帧几乎一模一样,验证时模型等于开了“开卷考试”。这不只是数据量的问题,而是评测方式失真。

解决:按视频片段或时间分组划分。比如从同一段视频里抽了 500 张,就让它整体落到训练集或验证集,不能拆开。这样做出来的验证指标虽然难看一点,但接近真实部署。我在一个消防项目中把随机切改成按日期切后,现场漏报率从 12% 降到 3%,验证 mAP 反而跌了 0.06,但这个 0.06 是诚实的。血泪经验是:宁可验证指标难看,也要让它代表真实场景。

4.4 夜间样本不足与颜色增强过度:白墙、灯光变成“烟雾”

现象:白天模型很准,晚上对着白墙、路灯和车灯频繁误报。

原因:2059 张图里如果夜间图占比很低,模型学到的其实是“亮灰色区域像烟”这种伪规律。再加上默认超参里 hsv 颜色增强较强,训练图经过色相扰动后,把原本偏灰的墙面转成偏白色,给了模型错误的特征。

解决:先把训练集里夜间图数量提上来,至少要占三成。如果数据来源有限,可以对夜间图做亮度扰动和对比度增强,模拟不同路灯下的亮灰变化。同时把超参数文件里的hsv_h、hsv_s调低,比如 hsv_h 从 0.015 降到 0.005,hsv_s 从 0.7 降到 0.4。夜间烟雾是半透明的,颜色饱和度很低,增强过度就是在制造噪声。如果还是误报,就在输出端加时序置信度:连续 3 帧都检测到 smoke 才告警,单帧偶发的高置信度直接丢弃。

5. 把模型用到现场:推理、导出与验证阈值

5.1 用best.pt做图片和视频推理:最小命令

训练完成后,推理命令很简单:

python detect.py \ --weights runs/train/smoke_fire_run/weights/best.pt \ --source test.jpg \ --conf-thres 0.25 \ --iou-thres 0.45

--conf-thres 0.25是置信度阈值,--iou-thres 0.45是 NMS 的 IoU 阈值。这个组合适合火灾这类宁可误报不可漏报的场景;如果误报太多,再把阈值往上提。输出会落在runs/detect/exp下,直接看标注结果即可。

5.2 导出ONNX并在真实场景验收:混淆矩阵和阈值选择

部署到边缘设备前,通常把模型导出为 ONNX,方便后续转 RKNN 或 TensorRT。命令如下:

python export.py \ --weights runs/train/smoke_fire_run/weights/best.pt \ --include onnx \ --dynamic

导出后先用onnxruntime跑一遍同一张图,确认输出和 PyTorch 推理一致。树莓派 4B 或 RK3568 上部署时,ONNX 还只是中间产物,需要转成目标平台的格式,这一步的关键是让模型输入尺寸和设备实际输入保持一致。我在树莓派 4B 上试过 640 输入的单帧推理,速度约 3 FPS,够用;如果改成 960,速度会掉到 1 FPS 以下,这种场景建议用--imgsz 640的固定尺寸导出。

上线前的验收不要只看 mAP,一定要看confusion_matrix.png。这张图会告诉你 smoke 被误判成 fire 的比例,以及背景 false positive 集中在哪个类。根据现场可接受的误报率反推置信度阈值。我吃过一次亏:某个项目里室内测试 ok,拉到楼道就疯狂报警,后来发现楼道灯光色温偏暖,模型把暖光中心当成 fire。从那以后,每次落地前都会先录一段该场景的原始视频跑一遍,再决定阈值和告警策略。数据集的这 2059 张图帮你解决了训练起点,但现场验证才是决定模型能不能用的最后一公里。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询