烟雾明火火灾目标检测数据集:VOC/YOLO格式与YOLOv8训练实战
简介这是一套适合目标检测入门与实战的烟雾明火数据集包含3007张真实场景图片覆盖消防监控、森林防火、室内外明火等典型烟火识别场景可用于构建烟火预警系统。数据集采用VOC与YOLO双格式标注每张图片均配套xml和txt文件标注类别为fire、smoke合计6849个矩形框其中fire框5198个、smoke框1651个使用labelImg按统一规则框选标注准确合理可直接供YOLO、SSD、Faster R-CNN等常见检测框架训练使用也能用于验证算法在烟火目标上的识别精度与泛化能力尤其适合迁移学习与模型微调场景。压缩包内共有2000个文件以xml标注文件为主体附带说明txt整体大小约299.85MB便于按文件索引、快速开展实验。已有656人浏览学习适合需要真实烟火样本的目标检测开发者、学生及消防智能化项目相关人员可显著节省数据筛选与格式转换的时间更专注于模型训练、调参与场景落地。1. 烟雾明火烟火火灾目标检测数据集3000张图拆给你看做火灾识别的目标检测模型最头疼的往往不是网络结构而是没数据可喂。公开的火焰数据集不是数量太少就是标注不规范跑出来的模型放到真实摄像头前面框全在飘。这套烟雾明火烟火火灾目标检测数据集总共3000多张JPEG图片配齐了VOC和YOLO两种标注格式共标注6849个目标框其中fire类5198个、smoke类1651个类别是[“fire”, “smoke”]用labelImg画矩形框完成。如果你是正在训练烟雾明火检测模型、需要一套能直接跑的标注数据的从业者这套资源能省下你两三周的数据清洗时间。我把它解码开把文件结构、格式互转、训练参数和踩坑点逐一讲透。2. VOC和YOLO双格式文件结构拆解与格式互转逻辑2.1 一张图对应三个文件的目录组织压缩包解压后你首先会看到一个说明.txt里面写清楚了格式约定。整套数据是典型的jpg xml txt三件套结构每一张jpg原图配套一个VOC格式的xml标注文件再配套一个YOLO格式的txt标签文件。文件名完全同名只是后缀不同打开目录后不管你的解压工具是平铺还是按文件夹分好实际看到的内容是长这样的ESE_fire_557.jpg ESE_fire_557.xml ESE_fire_557.txt清单里没有给出images、labels之类的分类子目录说明原数据大概率是一张图片配两个标签文件平铺存放。拿到手之后你不需要反向构造标注直接用同名匹配就能完成数据划分。这里有一个别的数据集不太一样的点摘要里写明了txt文件不包含分割路径只有类别和归一化坐标没有一条多余通路信息。也就是说这个YOLO标签是拿来做目标检测的不是拿来做实例分割的别到时候拿着它去跑分割任务那肯定会翻车。2.2 VOC的xml字段每个框的信息源头labelImg画框后保存的VOC格式信息结构是固定的。以ESE_fire_1498.xml为例解压后看到的内容骨架是这样的annotation folder./folder filenameESE_fire_1498.jpg/filename size width实际宽度/width height实际高度/height depth3/depth /size object namefire/name bndbox xmin100/xmin ymin80/ymin xmax420/xmax ymax360/ymax /bndbox /object /annotation不要迷信我写的具体坐标值不同图片差异极大重要的是字段含义。name就是类别名本数据集里只有fire和smoke两种bndbox给出矩形框的左上角xmin/ymin和右下角xmax/ymax单位是像素基于size里的图片宽高。如果你的框想转换成YOLO格式像素坐标是唯一的原始依据。另外VOC格式允许一张图片多个object节点比如一张图里同时有明火和浓烟那就有两个object一个name是fire另一个是smoke。一张图多个框直接导致每个类别框数与图片数不对等。3007张图、6849个框平均每图约2.3个目标说明这套数据不是“一图一框”的玩具集图像中多目标情况非常普遍训练时不光要能检出目标还得扛得住密集场景。2.3 YOLO的txt归一化之后的四元组YOLO格式把每个目标压缩成一行五个数字空格隔开秘密全在归一化0 0.482910 0.366406 0.620313 0.355469 1 0.721875 0.531250 0.181250 0.497656第一个数字是类别索引如果按摘要里给出的顺序[fire,smoke]转出来0对应fire1对应smoke。但这个顺序是“转换脚本按什么顺序写classes”决定的拿到数据的第一件事不是信任这个约定而是自己验证。后面四个数字依次是归一化中心横坐标、中心纵坐标、框宽、框高。计算规则是把VOC的像素坐标除以图片宽高x_center (xmin xmax) / 2 / width y_center (ymin ymax) / 2 / height width (xmax - xmin) / width height (ymax - ymin) / height这个数据集的txt全部按这个规则生成你拿上面那个ESE_fire_1498.xml手算一下就能对上。暴力验证的办法是把tx输出类别0和类别1的框个数分别应该接近5198和1651如果是反过来的说明我推断的索引顺序和你的假设不一致。具体怎么验第四章写避坑时会给脚本。2.4 从VOC到YOLO的转换脚本兼作格式核验工具下载的资源里没有带转换工具这是正常的标准做法是自己写一个。我一般在拿到这类双格式数据时会先写个小脚本做两件事一是把xml转成txt做交叉核验二是打印每个类别的框数分布。脚本如下import xml.etree.ElementTree as ET import os from collections import Counter CLASSES [fire, smoke] def convert_xml_to_yolo(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) # 对单个文件做转换并统计整个数据集的框数 counter Counter() for f in os.listdir(.): if not f.endswith(.xml): continue base f[:-4] convert_xml_to_yolo(f, base .yolo.txt) for line in open(base .yolo.txt): cls_id line.split()[0] counter[cls_id] 1 print(counter)这段代码里CLASSES的先后顺序就是YOLO类别索引的先后顺序必须和你运行时一致。size/width和size/height是相对路径查找方式ElementTree用斜杠表示多层路径。当你把2500多个xml全部转换完控制台打印的Counter应该稳定在{0: 5198, 1: 1651}上下如果出现明显偏差说明要么转换脚本顺序理解错了要么某个xml里的name不是标准值得单独抽出来看热。这也是我用这个脚本复用为验证工具的原因。3. 用YOLOv8训练这套火灾数据集完整参数与流程3.1 数据划分train/val/test的比例你这套数据没有自带划分目录压缩包里平铺的jpg直接拿来开训是不行的得先划分。我的做法是固定随机种子保证每次重跑结果可复现。参考代码如下import os import random from shutil import copyfile random.seed(2024) # 平铺目录下所有jpg文件 images [f for f in os.listdir(.) if f.lower().endswith(.jpg)] random.shuffle(images) train_cnt int(len(images) * 0.8) val_cnt int(len(images) * 0.9) train images[:train_cnt] val images[train_cnt:val_cnt] test images[val_cnt:] for split_name, split_files in [(train, train), (val, val), (test, test)]: os.makedirs(fimages/{split_name}, exist_okTrue) os.makedirs(flabels/{split_name}, exist_okTrue) for fname in split_files: base fname.rsplit(., 1)[0] copyfile(fname, fimages/{split_name}/{fname}) copyfile(base .txt, flabels/{split_name}/{base}.txt)划分比例按80/10/10走训练集2400多张、验证集300张、测试集300张对3000张出头的小数据集是合理选择。random.seed(2024)固定洗牌顺序同一份数据反复跑不会因为划分不同导致指标波动这是对比实验结果前必须做的一件事。txt与jpg同名匹配复制时只拷贝标签不拷贝xml因为YOLO训练只消费txt。3.2 data.yaml配置路径与类别名的对应接下来建立data.yaml。注意YOLOv8的路径基准是自动识别你填的绝对路径不要写相对路径训练脚本启动目录一变就全废path: F:/datasets/fire_smoke # 改成你解压后的实际根目录 train: images/train val: images/val test: images/test nc: 2 names: 0: fire 1: smokenc必须等于2这个不写对后面所有环节都会崩。names的索引顺序就是上面转换脚本里CLASSES列表的顺序。如果我转换时CLASSES [fire, smoke]这里0就是fire、1就是smoke两个文件必须达成一致否则训练出来的模型预测结果和可视化标签会对不上。3.3 训练命令与超参从yolov8s起步的基线方案先用yolov8s.pt做预训练权重打底。火灾烟火目标不算极端小目标s模型复杂度适中跑出来的效果有参考价值又不至于像nano小模型那样精度被压太多yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience20 \ workers4几个关键参数拆开说。epochs100对3000张图片偏保守配合patience20可以让它在验证集指标20轮不涨时自动早停不必死跑完100轮。imgsz640是默认推荐和YOLOv8的预训练尺寸一致不需要刻意加大到1280火焰和烟雾主体区域占比较大640足够。batch16以单卡12GB显存为前提显存不够降到8如果batch太小导致BN统计不稳定精度会断崖式下跌别硬撑。跑完之后一个重要动作是看runs/detect/train下的results.png里面有train/loss和val/loss的曲线。正常情况val loss随epoch下降如果val loss前20轮降完就开始回升那是过拟合信号把epochs裁到40再试。3.4 类别不平衡fire 5198 vs smoke 1651的处理策略这是全套数据里最显眼的数字冲突fire框数是smoke的三倍多。很多人的第一反应是给smoke加loss权重但我建议不要刚上手就这么干。YOLOv8默认就带cls损失盲目调class_weights反而会让fire精度暴跌。优先做法是数据增强里给smoke倾斜并对smoke类的样本做过采样。写训练配置时可以通过超参平衡但更通用的做法是在数据层面复制smoke的txt对应图片让两类框数接近。当然也可以直接改YOLOv8训练脚本里的损失权重只是这样做会让你的实验结果很难和别人的对比因为大家都在同一个默认配置下比mAP。实操中我建议先用默认配置跑一版验证mAP50能否到70%以上。如果smoke的AP值远低于fire再针对smoke小幅上调。训练集里每次迭代自动做了mosaic等增强真实暴露出来的不平衡会比框数显示的小一些不用紧张到一上来就重写损失函数。4. 避坑这套火灾数据集最容易翻车的五个点4.1 现象Loss降得很低但可视化预测全是乱框fire和smoke画反了原因txt里的类别索引和你训练配置里的names顺序不一致。我见过不止一次转换脚本里classes是[fire, smoke]而data.yaml里names恰好填反了模型训练全程都在学反标签自然收敛到“识别正常但语义颠倒”的诡异状态。解决动手训练前先跑一次标签统计import os from collections import Counter counter Counter() total 0 for f in os.listdir(labels/train): if not f.endswith(.txt): continue for line in open(flabels/train/{f}): cls_id line.split()[0] counter[cls_id] 1 total 1 print(counter, total)输出应该是{0: 约4158, 1: 约1321}这一量级0多1少。如果你看到的相反说明txt里0对应smoke把data.yaml的names顺序调转。4.2 现象图片能训练但验证集mAP50-95差值大smoke的AP比fire低十几个点原因smoke本身只有1651个框又叠加烟雾半透明、边界模糊难标注学不到清晰纹理是物理规律决定的不是模型问题。解决先不要动模型结构。把imgsz提到960让烟雾区域有更多有效像素数据增强里把hsv_h调低一点因为烟雾颜色对色调敏感过度色偏增强会让模型把背景雾霾当烟雾。我一般用albumentations针对烟雾样本单独做一次裁剪增强而不是全数据集统一洗色。4.3 现象训练到一半报Image ... is corrupted or has EXIF issues进程直接终止原因3000张原图并非全由同一部相机产出部分网络爬取的图片EXIF信息异常或文件尾部截断。YOLO训练时读图采用OpenCV后端遇到断图就会抛异常。解决先跑一轮全量体检from PIL import Image import os bad [] for root, _, files in os.walk(images): for fname in files: path os.path.join(root, fname) try: img Image.open(path) img.load() except Exception as e: bad.append((path, str(e))) print(bad)把打印出来的坏图连同对应xml/txt一并剔除再跑划分问题消失。4.4 现象模型框出的火焰区域比实际明火大一圈IoU怎么都提不上去原因标注规则是画矩形框但火焰往往是不规则扩散形态标框时必然包含部分无火焰区域这是矩形标注的固有限制不算标注错误。同样的问题在smoke上更严重烟雾边缘软标框时不同标注员的手抖程度完全不同。解决评价这套数据时别死磕IoU0.5结合AP50和AP75一起看。部署时用置信度阈值过滤而不是IoU阈值过滤宁可框略大也不漏检这在消防场景里是能接受的工程权衡。如果要做框回归精度竞赛级调优唯一出路是自己二次标注或改成旋转框。4.5 现象Windows解压后出现乱码文件名或者jpg和xml对不上原因压缩包内文件用UTF-8编码存储Windows自带的zip工具按GBK解出来中文目录没影响但部分带有特殊字符的英文文件名也偶尔出现冲突。这类平铺数据一旦文件名错位三个文件就拆家。解决不用系统自带解压用Bandizip或7-Zip右键以“自动检测编码”方式解压。解压后不要急着开训先跑一段校验脚本统计同名的jpg/xml/txt数量差值三方数量都等于3007才能进训练管线。提示下载后先验证三个文件数是否一致再谈训练。这一步花5分钟省下后面几十个小时的返工。5. 数据增强与边界3000张图能覆盖哪些真实火灾场景5.1 能跑的增强HSV、mosaic、仿射的组合3000张图片数量不大直接训练容易过拟合增强是必选项。YOLOv8自带的mosaic、fliplr默认开启但火焰场景有自己的特殊性我习惯在外部管线里再加一层albumentations增强针对烟雾半透明特征做强化import albumentations as A fire_transform A.Compose([ A.HueSaturationValue(hue_shift_limit10, sat_shift_limit20, val_shift_limit10, p0.5), A.RandomBrightnessContrast(brightness_limit0.1, contrast_limit0.1, p0.5), A.ShiftScaleRotate(shift_limit0.02, scale_limit0.05, rotate_limit5, border_mode0, p0.3), A.OneOf([ A.MotionBlur(blur_limit3, p0.5), A.GaussNoise(var_limit(5.0, 20.0), p0.5), ], p0.2), ])这套组合里的参数说直白点hue_shift_limit10只做小幅色调偏移避免把黄色火焰调成绿色假样本rotate_limit5控制旋转角度在5度以内因为火灾检测场景里摄像头基本固定大幅度旋转不符合真实部署视角。MotionBlur模拟摄像头抖动对室内固定机位来说不是最优选择但对于手持巡检设备却是刚需是否启用要看你的实际目标场景。5.2 不建议的增强无脑翻转与暴力Cutout很多人拿到新数据集先一键开启全部增强结果在火灾场景里翻车。垂直翻转尤其要少用摄像头画面里的火焰和烟雾具有明显的重力方向烟永远是往上飘的你把图片竖直翻转模型就会学到“烟往下沉”这种物理上不存在的模式推理时对真实视频很难适应。Cutout随机遮挡也要谨慎使用。火焰区域如果被遮住一半残留的烟和火的纹理依然有判别力但如果你遮挡比例过大负样本语义被破坏模型会学到不合理的补全验证集上AP奇高到了真实环境立刻现形。控制遮挡比例在0.2以下且只能作为辅助增强不能当主力。5.3 这套数据的真实边界白天厂房与自然光场景从标注的框数和内容名称看这套数据偏向可见光下的室内厂房、户外堆场和开阔地带的火焰与烟雾目标适合做早期火灾预警模型的预训练基底。它的短板也明显缺乏夜间红外或低照度下的样本缺乏燃气管道、电气柜等小目标密集场景没有俯视视角的无人机画面。检测边界必须心里有数。如果你拿这套数据集训练完直接部署到夜晚的化工园区漏检率会比你想象的高得多因为夜间火焰和灯光的视觉特征重叠严重、烟雾在暗光下几乎不可见。一个合格的做法是用这套数据做预训练再采集目标场景数据做微调而不是把它当成终态数据用。6. 验证与下钻不只看mAP用混淆矩阵和置信度阈值做工程取舍6.1 用验证命令产出混淆矩阵看fire和smoke怎么互相干扰YOLOv8在验证阶段可以直接产出可视化图表跑一次带plotsTrue的验证yolo detect val \ modelruns/detect/train/weights/best.pt \ datadata.yaml \ plotsTrue执行完成后runs/detect/val目录下会生成confusion_matrix.png。你要重点看两个格子真实fire被预测成smoke的比例以及真实smoke被预测成fire的比例。一般规律是smoke漏检多于fire漏检因为烟气边缘像素与背景混合严重。如果矩阵里两类交叉项超过15%说明模型对“烟包火”的复合目标区分度不足优先做法是增加两类同时出现的训练样本而不是调模型宽度。6.2 置信度阈值消防场景的漏检与误报权衡学术评测里conf0.25是通用默认值但工程上属于“裸奔参数”。火灾检测的代价函数极度不对称漏一次警是重大事故误报一次只是让值班员去确认一下。我会把置信度阈值压到0.05-0.10宁多框不漏框yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcetest_images/ \ conf0.05 \ iou0.45iou0.45是NMS阈值控制同一个目标保留几个框。压得越低重复框越少但对密集目标的召回率也会下降0.45是一个不会翻车的起始点。实践时先跑一遍看输出的预测结果里有多少低置信度框是真实火焰再微调决定。6.3 小目标框的处理AP50-95不理想时先查框面积分布smoke的平均框面积天然小于fire因为烟雾在早期阶段就是一小缕。验证时如果smoke的AP50和AP50-95差距巨大往往说明小目标检测能力不足。这时先别上注意力机制先做一件事统计训练集里所有框的归一化宽度和高度看有多少框面积低于0.05。占总框20%以上的小目标imgsz640可能不够把它提至960再训练一个版本做对比。如果提分辨率后smoke的AP50-95涨了但速度明显下降部署时就做双路推理一路640快速筛查一路960只对视频画面中的ROI区域精细复核。自从那以后我每次拿到新数据集都要先统计框的面积分布再见小目标就不焦虑了希望帮到你。本文还有配套的精品资源点击获取