ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

火焰烟雾检测数据集实战:近500张图双格式标注与YOLOv8训练全流程

火焰烟雾检测数据集实战:近500张图双格式标注与YOLOv8训练全流程 简介这份资源是面向计算机视觉开发者与安全监控方向研究者的YOLO火焰和烟雾检测数据集针对火灾预警场景中火焰、烟雾形态多变、检测难度大的问题提供可直接用于目标检测模型训练与验证的标注样本。压缩包共1444个文件包含481张jpg图像、481个xml标注与482个txt标注分别对应PASCAL VOC与YOLO两种主流格式包体约3.93MB便于快速解压与格式转换。目前已有2779人学习下载适合作为入门目标检测或搭建火灾预警原型的练手数据。读者可基于该数据集训练YOLOv3、YOLOv4等模型结合旋转、缩放、翻转等数据增强与迁移学习策略提升泛化能力并通过mAP、召回率、F1分数等指标评估优化效果从而掌握从标注解析到模型迭代的完整流程。1. 火焰烟雾检测数据集近 500 张图、双格式标注值不值得下做烟火检测的同行大概都有过这种体验网上能找到的公开数据集要么是几千张起步、标注质量参差不齐要么是只有图片没有标注、拿到手还得自己标一遍。fire_smoke-dataset.zip这个包走的是另一条路——体量不大近 500 张图但每张都给了 PASCAL VOC 和 YOLO 两套标注解压就能直接喂给训练脚本。它解决的核心问题不是数据量够不够大而是能不能在半小时内跑通一条完整的训练链路。这个数据集适合三类人一是刚接触 YOLO 目标检测、想找个真实场景练手的新手烟火这类目标形态多变、背景复杂比 COCO 里那些规整物体更能暴露问题二是做火灾预警原型验证的工程师需要快速验证某个 backbone 或数据增强策略有没有效果三是教学场景500 张图的规模在单卡上几十分钟就能跑完一轮适合课堂演示。不适合指望靠它直接产出工业级模型的人——这个量级必须配合迁移学习和强增强后面会细说。2. 拆包先看结构VOC 与 YOLO 双格式到底怎么对应2.1 目录结构与两种标注格式的差异拿到压缩包先别急着解压到训练目录找个临时路径展开看一眼。常见的组织方式是这样fire_smoke-dataset/ ├── images/ # 全部 jpg 原图 │ ├── 299.jpg │ ├── 254.jpg │ └── ... ├── annotations_voc/ # PASCAL VOC 格式 xml │ ├── 299.xml │ └── ... └── labels_yolo/ # YOLO 格式 txt ├── 299.txt └── ...实际包内目录名可能略有出入但逻辑一致同一张图299.jpg对应一个299.xml和一个299.txt。VOC 的 xml 里存的是绝对像素坐标bndbox下有xmin/ymin/xmax/ymax类别写在name里通常是fire和smoke两类。YOLO 的 txt 每行一个目标格式是class_id cx cy w h全部归一化到 0~1 之间。这里有个容易翻车的点VOC 的坐标原点在左上角xmax是包含边界的而 YOLO 的cx cy是框中心点。两者换算时如果忘了-1或者把w/h算成xmax-xmin而没除图像宽高框会整体偏移。我一般会先写个校验脚本把两种格式互相转一遍再转回来看能不能对上原图。2.2 用脚本核对标注一致性在动手训练前花五分钟跑一个校验脚本能省掉后面几小时的排查。下面这段代码做三件事统计每类目标数量、检查是否有越界框、验证 VOC 与 YOLO 是否一一对应。import os import xml.etree.ElementTree as ET from PIL import Image from collections import Counter IMG_DIR fire_smoke-dataset/images VOC_DIR fire_smoke-dataset/annotations_voc YOLO_DIR fire_smoke-dataset/labels_yolo cls_counter Counter() mismatch [] for img_name in os.listdir(IMG_DIR): if not img_name.endswith(.jpg): continue stem os.path.splitext(img_name)[0] img_path os.path.join(IMG_DIR, img_name) w, h Image.open(img_path).size # 解析 VOC voc_path os.path.join(VOC_DIR, stem .xml) voc_boxes [] if os.path.exists(voc_path): tree ET.parse(voc_path) for obj in tree.findall(object): name obj.find(name).text cls_counter[name] 1 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 越界检查 if xmin 0 or ymin 0 or xmax w or ymax h: mismatch.append((stem, voc_out_of_bound)) voc_boxes.append((xmin, ymin, xmax, ymax)) # 解析 YOLO yolo_path os.path.join(YOLO_DIR, stem .txt) yolo_boxes [] if os.path.exists(yolo_path): with open(yolo_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cid, cx, cy, bw, bh map(float, parts) # 反归一化 xmin (cx - bw / 2) * w ymin (cy - bh / 2) * h xmax (cx bw / 2) * w ymax (cy bh / 2) * h yolo_boxes.append((xmin, ymin, xmax, ymax)) if len(voc_boxes) ! len(yolo_boxes): mismatch.append((stem, fcount_diff voc{len(voc_boxes)} yolo{len(yolo_boxes)})) print(类别统计:, cls_counter) print(不一致条目数:, len(mismatch)) for m in mismatch[:10]: print(m)逻辑说明脚本遍历images下所有 jpg用 PIL 读原图宽高因为 YOLO 反归一化必须依赖真实尺寸。VOC 解析用ElementTree逐个object取类别和框YOLO 解析按空格切分cx cy w h反算回像素坐标。最后对比两种格式的框数量数量对不上就记进mismatch。参数说明cls_counter统计的是 VOC 里的类别名如果输出只有fire和smoke两个键说明标注类别干净如果出现flame、Fire这类变体训练前必须统一否则 YOLO 的class_id会错位。mismatch里如果大量出现count_diff说明两种格式不是同一批标注得确认以哪套为准。提示这个脚本只做数量级校验不做 IoU 级别的逐框比对。如果数量一致但框位置有偏差需要再写一个按顺序比对的版本但那种情况在正规发布的数据集里很少见。3. 从零跑通 YOLOv8 训练数据配置与增强参数怎么设3.1 转成 YOLOv8 目录规范并写 data.yamlYOLOv8 对目录结构有固定要求不能直接把images和labels_yolo丢进去。标准布局是images/train、images/val、labels/train、labels/val四组。500 张图按 8:2 划分训练 400 张、验证 100 张。下面脚本完成划分和拷贝import os import shutil import random SRC_IMG fire_smoke-dataset/images SRC_LBL fire_smoke-dataset/labels_yolo DST fire_smoke-yolo random.seed(42) for split in [train, val]: os.makedirs(f{DST}/images/{split}, exist_okTrue) os.makedirs(f{DST}/labels/{split}, exist_okTrue) stems [os.path.splitext(f)[0] for f in os.listdir(SRC_IMG) if f.endswith(.jpg)] random.shuffle(stems) split_idx int(len(stems) * 0.8) train_stems stems[:split_idx] val_stems stems[split_idx:] for split, subset in [(train, train_stems), (val, val_stems)]: for stem in subset: shutil.copy(f{SRC_IMG}/{stem}.jpg, f{DST}/images/{split}/{stem}.jpg) lbl_src f{SRC_LBL}/{stem}.txt if os.path.exists(lbl_src): shutil.copy(lbl_src, f{DST}/labels/{split}/{stem}.txt) else: # 无目标图也要保留YOLO 允许空 label 文件 open(f{DST}/labels/{split}/{stem}.txt, w).close() print(train:, len(train_stems), val:, len(val_stems))逻辑说明random.seed(42)保证每次划分一致方便复现。无标注的图纯背景也要拷贝并生成空 txtYOLOv8 会把空文件当作负样本这对降低误报率有帮助。如果直接跳过无标注图模型会没见过没有火的场景部署后容易把夕阳、灯光误判成火焰。参数说明0.8是训练集比例500 张图下验证集 100 张足够看趋势但指标波动会比较大建议训练时多看 loss 曲线而不是只盯 mAP。seed换成别的值会得到不同划分对比实验时固定住。接着写data.yamlpath: /abs/path/to/fire_smoke-yolo train: images/train val: images/val nc: 2 names: 0: fire 1: smokenc是类别数names的顺序必须和 YOLO txt 里的class_id对应。如果校验脚本发现class_id只有 0 和 1但names写反了训练能跑但推理时火焰会被标成烟雾这种错误在指标上看不出来只有可视化才暴露。3.2 训练命令与关键超参YOLOv8 用 ultralytics 包一条命令启动yolo detect train \ datafire_smoke-yolo/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ augmentTrue \ mosaic1.0 \ mixup0.1 \ degrees10 \ translate0.1 \ scale0.5 \ fliplr0.5 \ namefire_smoke_v8n逻辑说明modelyolov8n.pt加载 COCO 预训练权重这是小数据集能跑出效果的前提。epochs100配合patience20如果 20 轮验证指标不升就早停避免过拟合。imgsz640是 YOLOv8 默认输入尺寸烟火目标通常占画面比例不小640 够用如果图里火点很小可以提到 960 但显存翻倍。参数说明mosaic1.0是 YOLOv8 默认开启的四图拼接增强对烟火检测特别有用因为拼接后会出现多火源、多烟雾的复杂场景逼模型学更鲁棒的特征。mixup0.1做图像混合比例别太高0.1~0.2 即可太高会让火焰颜色失真。degrees10小角度旋转烟火没有固定朝向旋转增强合理。scale0.5缩放范围 0.5~1.5模拟不同距离的火源。fliplr0.5水平翻转火焰左右对称安全。注意batch16是 8G 显存下的保守值如果报 OOM 就降到 8 或 4同时把lr0按比例调小否则等效学习率变大容易震荡。3.3 训练过程看什么指标启动后终端会打印每轮的 box_loss、cls_loss、dfl_loss 和 mAP50、mAP50-95。小数据集上 box_loss 下降快但 mAP 可能卡在 0.6 左右这是正常的——500 张图覆盖不了烟火的全部形态。重点看验证集的 mAP50 有没有持续上升如果训练 loss 降但验证 mAP 平了说明过拟合该早停或加增强。混淆矩阵在runs/detect/fire_smoke_v8n/下打开confusion_matrix.png看两类之间的误判。烟火检测最常见的混淆是 smoke 被漏检标成背景因为烟雾边缘模糊、和云、雾、蒸汽难区分。如果混淆矩阵显示大量 smoke→background说明烟雾样本的特征不够需要针对性补充烟雾图或调低置信度阈值。4. 避坑与排查小数据集训练烟火检测的五个血泪经验4.1 现象训练 loss 正常但推理全是误报原因数据集里负样本无火无烟的纯背景图太少或没有模型没学过什么不是火。500 张图里如果全是带火带烟的模型会把所有暖色调区域都当火焰。解决从验证集里挑出无目标的图或者额外找一批夜景、夕阳、灯光、红色物体的图生成空 label 文件加入训练集。比例控制在 10%~15% 左右。YOLOv8 对空 label 文件是支持的会当作背景负样本参与分类损失计算。4.2 现象mAP 忽高忽低不同 seed 差异超过 0.1原因验证集只有 100 张某些类别比如 smoke在验证集里可能只有二三十个实例一两个漏检就能让 mAP 掉一大截。这是小数据集的统计涨落不是模型不稳定。解决用 K 折交叉验证代替单次划分或者至少跑三个不同 seed 取平均。如果只是自己验证方案固定 seed 看相对趋势即可别拿绝对值去和论文比。另外可以把验证集比例提到 0.25代价是训练样本更少需要权衡。4.3 现象训练到一半 loss 突然变 NaN原因学习率太大或者某批数据里有异常框宽高为 0、坐标越界。YOLOv8 虽然会做 clamp但极端值仍可能引发梯度爆炸。解决先用第 2 章的校验脚本过滤掉越界框和零面积框。训练命令里加lr00.001降初始学习率或者加warmup_epochs5让前几轮慢慢升。如果已经 NaN检查runs/下最近保存的权重从 NaN 前一轮的last.pt恢复继续训。4.4 现象烟雾检测 mAP 明显低于火焰原因烟雾没有固定形状和颜色标注边界主观性强不同标注者对烟雾范围的判断可能差很多。而且烟雾经常半透明和背景融合特征不明显。解决这是任务本身的难点不是数据集的问题。可以尝试在 YOLOv8 基础上加注意力模块或者用更大的输入尺寸保留烟雾纹理细节。另外检查标注里烟雾框是不是画得太紧如果只框了浓烟核心而忽略了边缘模型学到的特征会偏。常见做法是把烟雾框适当外扩 10%~15%。4.5 现象VOC 转 YOLO 后框整体偏移原因转换时用了xmax-xmin算宽但忘了 VOC 的坐标是 1-based部分工具导出时xmin从 1 开始或者归一化时除了(w-1)而不是w。解决统一用w和h做归一化分母不要用w-1。转换后随机抽 10 张图用cv2.rectangle画出来目视检查这是最可靠的验证方式。如果偏移量固定多半是 1-based 和 0-based 的差异减 1 即可。5. 进阶技巧用预训练权重和冻结策略把 mAP 再拉一截小数据集训练最怕过拟合而 YOLOv8 的预训练权重是在 COCO 上学的通用特征直接全量微调容易把底层特征带偏。我一般会先冻结 backbone 训 20 轮再解冻全量训 80 轮这样 mAP 通常比直接全量训高 3~5 个点。命令如下# 第一阶段冻结 backbone yolo detect train \ datafire_smoke-yolo/data.yaml \ modelyolov8n.pt \ epochs20 \ freeze10 \ lr00.001 \ batch16 \ namefire_smoke_freeze # 第二阶段解冻全量用第一阶段权重初始化 yolo detect train \ datafire_smoke-yolo/data.yaml \ modelruns/detect/fire_smoke_freeze/weights/best.pt \ epochs80 \ lr00.0005 \ batch16 \ namefire_smoke_fullfreeze10表示冻结前 10 层YOLOv8n 的 backbone 大致对应这个范围只训 head 和 neck。第一阶段学习率可以稍大因为只调随机初始化的 head。第二阶段用更小的学习率精调全网络避免破坏已学好的特征。另一个技巧是测试时增强TTA。推理时加augmentTrueYOLOv8 会对同一张图做多尺度、翻转后融合结果mAP 通常能再涨 1~2 个点代价是推理速度慢 2~3 倍。对离线火灾预警场景可以接受实时视频流就不建议开。yolo detect predict \ modelruns/detect/fire_smoke_full/weights/best.pt \ sourcetest_images/ \ augmentTrue \ conf0.25 \ iou0.5 \ saveTrueconf0.25是置信度阈值烟火检测建议比通用检测低一些因为漏报火灾的代价远大于误报。iou0.5是 NMS 的 IoU 阈值如果同一区域火焰和烟雾框重叠严重可以降到 0.4 让 NMS 更激进地合并。验证模型有没有真正学到东西别只看 mAP。我习惯抽三类图做目视检查一是小目标火点远处火源看模型有没有漏二是强光干扰夕阳、车灯看误报多不多三是烟雾和云混杂的场景这是最容易翻车的。如果这三类都过得去模型才算能拿去试部署。从那以后我每次拿到新数据集都强制先跑一遍标注校验脚本再开训这个习惯帮我省下了至少三次通宵排查的精力。希望帮到你。本文还有配套的精品资源点击获取
返回列表