简介:面向目标检测与火灾预警场景,这份火焰烟雾数据集配套XML与TXT标注,适合使用YOLOv5或YOLOv7训练火焰、烟雾识别模型,可支撑火灾预警系统开发。压缩包共2000个文件,约461.88MB,以jpg图像、xml标注和txt标签为主,另含xml转txt的Python脚本,便于直接送入YOLO系列框架训练。标注覆盖火焰和烟雾目标的边界框及类别,基于该数据训练可达到约0.9的检测精度,适用于从入门到进阶的深度学习开发者。目前已有2769人学习下载。压缩包内还包含fireandsmoke-best相关文件,指示了经过调优的最佳模型权重或配置,可帮助读者对比训练效果、节省调参时间,并快速复现高精度检测结果,用于实际报警场景的模型部署与二次开发。
1. 为什么火焰烟雾检测的数据集这么难搞,精度还容易恰在0.9上下
做目标检测做到火焰烟雾这个场景的人,应该都清楚一个尴尬现状:公开数据集不算少,COCO、VOC 里也偶尔能翻到 fire、smoke 的类别,但真正想拿来直接训练出能落地的模型,十有八九要翻车。原因很简单,火焰和烟雾在图像里的形态跟普通物体完全不一样——普通物体有清晰的边缘轮廓,火焰的轮廓时刻在变,烟雾更不用说,半透明、有光照穿透、颜色随环境变化,同一个“物体”在不同背景下的特征可能天差地别。
我自己的做法是从三个方向混合凑出一个规模可用的数据集:一部分来自公开的火焰烟雾数据集(GitHub 上有人整理过几批),一部分从视频里截帧做清洗,还有一部分是自己补拍的实验室酒精灯、油盘火这类可控场景。最后整理出来大概一万两千多张图,标注框接近两万六,类别就两类:fire 和 smoke,没有细分火焰种类,也没有把“烟带火”的情况做多标签叠加,理由后面会细说。
标题里提到的“精度0.9左右”,这里的精度其实要拆开看。如果你说的是 mAP@0.5 到 0.9,那是一个比较常见的、算是不错的水平;如果是 mAP@0.5:0.95 也能到 0.9,那说明你的测试集相对简单,或者数据分布比较收敛。我这套跑下来,验证集 mAP@0.5 大概在 0.92 附近,mAP@0.5:0.95 稳定在 0.66 左右,说实话后者才是更硬核的指标,尤其对动态目标来说,框的贴合度直接影响这个数。
所以这篇就按我实际走的流程来写,从原始图像收集、标注格式处理,到转成 YOLO 能直接读的格式,再到训练策略和精度的排查,把每个你可能踩的坑都摊开。适合正在做消防预警、安防监控、或者其他视觉检测方向但苦于数据不会整理的朋友参考,哪怕你不是做火焰烟雾,这套“乱数据清洗 + 格式转换 + 指标定位”的思路也完全能搬过去用。
2. 数据收集与清洗:公开数据别直接用,混合数据源才能撑住真实场景
2.1 公开数据集为主,但必须做一次“肉眼审查”
很多文章一上来就让你去下载某某数据集,然后直接开始标注格式转换,听起来顺理成章,实际做的时候你会发现一堆问题。以我用的几批公开数据为例,有两类典型毛病:一是图像分辨率参差不齐,最小的缩略图才240×160,最大的来自监控截图能到 4K,如果混在一起不做统一处理,训练时的图像缩放会很痛苦;二是大量图像里火焰只占几个像素,或者整张图就是烟雾弥漫,标注框和实际目标严重不匹配。
所以我拿到原始数据的顺序是:先解压,写个 Python 脚本把所有图片统一缩放到最长边不超过 1280,保证后面训练加载时不会一帧图撑爆显存;然后人工快速过一遍,把模糊到人眼都看不清的、标注框明显错位的、以及重复帧大概去掉了两成。这个步骤别偷懒,省掉的每一张低质量图都是在给训练阶段的模型减负。
2.2 视频截帧补充负样本,但别截太多
火焰烟雾检测最容易出现的问题不是漏检,而是误检。阳光反射、红色灯光、灰色雾气、甚至穿红色衣服的行人,都可能被模型当成 fire 或者 smoke。想压住误检,最有效的手段不是调阈值,而是在训练集里加入足够的“难负样本”。
我用了大约 20 段工业现场和城市监控的视频,每段视频按每秒一帧抽帧,抽出来之后人工挑,只留那些包含“像火但又不是火”“像烟但又不是烟”的图,比如夕阳下的红墙、路灯下的雾霾天、车灯过曝造成的亮斑。这批图不打标签,直接当 background 类放进数据集,数量控制在总图片量的 15% 左右。训练时 YOLO 会自动把它们当作背景,帮模型学会“看着像但实际不是”的边界。
2.3 混合数据源的标注口径统一
这步是整个数据集构建里最容易被低估、但影响最大的环节。公开数据集的标注习惯各不相同:有的框只框住火焰最亮的芯部,有的把火焰连同周围热浪区域都框进去,有的把烟和火合并成一个框,有的则拆开标。如果直接混训,模型一会儿学“框住最亮部分”,一会儿学“框住整个火团”,损失函数会震荡得厉害,这也是很多人训练火焰模型怎么调都不收敛的一个重要原因。
我当时花了一整天时间把所有来源的 XML 标签拉出来做了统计,按图像查看每个框的坐标比例,统一成同一套规则:火焰框包含可见燃烧区域,不包含周围大量热浪变形区;烟雾框只框不透明的主体烟雾团,如果烟雾太淡、透明到背景清晰可见,就放弃标注这一处。宁可少标几个模糊目标,也不要标出错误目标,这个原则后面验证非常关键——它直接决定你能不能让指标稳定在 0.9 附近。
3. xml标签到yolo格式的转换链路:VOC结构和YOLO结构的核心差异
3.1 为什么拿到的多是xml标签,而yolo训练却需要txt
早期目标检测数据集大多沿用 VOC 的标注格式,也就是每个图像对应一个 XML 文件,里面记录了 object 的名称、bndbox 的坐标值,这是一个基于绝对像素坐标的格式;而 YOLO 系列训练时读的是 txt 格式,每一行代表一个目标:第一个数字是类别 id,接着是归一化后的中心点 x、中心点 y、框宽 w、框高 h。
这两个格式不互通,必须写转换脚本。但真正的坑从来不是“格式字段怎么对应”,而是坐标归一化的精度。XML 里 bndbox 是 int 型像素值,转换时要先除以图像的原始宽和高,如果图像在预处理时被改过尺寸,而 XML 里的坐标还是改之前的像素值,转换出来的坐标就会整体偏移,模型学出来的框永远是歪的。所以做转换前务必确认 XML 描述的图像和实际拿到的图像分辨率一致,最好直接写个断言检查。
3.2 转换脚本的骨架和边界情况处理
我用的转换脚本核心逻辑很简单,先读图像尺寸,然后解析 XML 拿 name 和坐标,最后写入 txt。贴个核心片段,你应该一眼能看懂:
import xml.etree.ElementTree as ET import cv2 import os def voc_label(txt_file, xml_path, img_path, classes): img = cv2.imread(img_path) h, w = img.shape[:2] tree = ET.parse(xml_path) root = tree.getroot() with open(txt_file, 'w') as f: for obj in root.iter('object'): cls = obj.find('name').text if cls not in classes: continue cls_id = classes[cls] box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) # 坐标越界截断,防止归一化后出现负数或大于1的值 xmin, xmax = max(xmin, 0), min(xmax, w) ymin, ymax = max(ymin, 0), min(ymax, h) if xmax <= xmin or ymax <= ymin: continue center_x = (xmin + xmax) / 2.0 / w center_y = (ymin + ymax) / 2.0 / h box_w = (xmax - xmin) / w box_h = (ymax - ymin) / h f.write(f"{cls_id} {center_x:.6f} {center_y:.6f} {box_w:.6f} {box_h:.6f}\n")这中间有两个必须处理的边角情况:一是 XML 里标注框偶尔会超出图像边界,可能是标注工具的手误,直接 clip 到图像范围内;二是会有空标签文件,也就是某个图像明明存在但没有任何目标,这种情况保留空的 txt 文件没问题,但不能直接删掉对应图片,否则数据集里 picture 和 label 数量对不上,训练时会报错。
3.3 目录结构按 YOLO 的规矩来
转换完 txt 之后,目录组织也要按 YOLO 系列的习惯来。我最后还是采用了最常见的布局:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── fire_smoke.yaml图片和标签只通过同名文件对应,yaml 文件里用绝对路径指向 images 的两个子目录即可。这里要把训练集、验证集、测试集在文件层面就隔离好,别用程序随机划分,否则同一个视频相邻帧可能一张进训练集一张进验证集,mAP 会虚高,后面换真实视频推理时立刻现原形。
4. 训练前的最后一道关卡:yaml配置文件与类别不平衡处理
4.1 写yaml时最容易翻车的两个字段
YOLOv5 和 YOLOv8 的配置文件差别不算大,核心就三个字段:
train: /your_abs_path/dataset/images/train val: /your_abs_path/dataset/images/val nc: 2 names: ['fire', 'smoke']很多人会把 train 和 val 写成相对路径,然后在不同的工作目录下启动训练,结果报 FileNotFoundError。还有一个容易忽略的是nc必须跟names的数量严格一致,你定义了两类,nc写成了 3,YOLO 不会直接拒绝启动,但输出的类别索引会对不上,推理时你会看到 fire 被标成 class 2,排查起来非常痛苦。
4.2 火焰多还是烟雾多,直接影响训练策略
我数据集的分布是这样的:
| 类别 | 标注框数量 | 占比 |
|---|---|---|
| fire | 15400 | 59.5% |
| smoke | 10500 | 40.5% |
两者没有极端失衡,但 smoke 的框通常更大、更模糊,对小目标检测不友好;fire 的框偏小,很多远距离火焰框可能只有 20×20 像素,属于典型的小目标。针对这种分布,我没有刻意做类别重采样,而是靠 YOLO 自带的 mosaic 和 mixup 增强去提升小目标表现。如果某类占比低于 20%,我才会考虑对少数类做两倍重复采样,否则容易过拟合。
4.3 预训练权重怎么选
火焰烟雾不属于 COCO 的常见类别,但底层特征,比如纹理、边缘、颜色变化,和 COCO 数据集里的很多东西是共通的。所以我没有从零训练,而是直接加载了 YOLOv8m 的 COCO 预训练权重。选 m 而不是 s 的原因很直接:火焰烟雾检测对框的定位精度要求高,s 的参数量往往在前面几轮就饱和了,m 在速度和精度上更均衡。如果你算力太紧张,s 也能跑,但要做好 mAP 掉 3~5 个点的心理准备。
5. 从训练到逼近0.9:我的参数设置和实测效果
5.1 一组可以直接复用的训练参数
贴出我跑通的一组参数,YOLOv8m,单卡 3060Ti,batch 16,图像尺寸 640。这组条件下显存占用大概 10GB,属于能接受的水平。
yolo detect train \ --model yolov8m.pt \ --data fire_smoke.yaml \ --epochs 120 \ --batch 16 \ --imgsz 640 \ --patience 20 \ --optimizer AdamW \ --lr0 0.001 \ --weight_decay 0.0005 \ --augment几个参数的具体考量如下:patience设为 20,意味着连续 20 轮验证集指标不涨就提前停,我实际跑到第 96 轮就触发了早停;lr0用 0.001 而不是默认的 0.01,是因为火焰和烟雾的边界不确定性强,学习率太大容易出现震荡,后面 mAP 会在 0.85 和 0.7 之间反复横跳;augment保持开启,mosaic 和 HSV 增强对火焰这种颜色特征明显但形状多变的目标非常友好。
5.2 跑到第几轮开始能看到0.9 的迹象
我记录了几组验证集指标的变化节点,可以给你一个比较直观的参考:
| epoch | mAP@0.5 | mAP@0.5:0.95 | Precision | Recall |
|---|---|---|---|---|
| 10 | 0.41 | 0.19 | 0.58 | 0.43 |
| 30 | 0.74 | 0.43 | 0.81 | 0.72 |
| 60 | 0.87 | 0.56 | 0.89 | 0.85 |
| 90 | 0.92 | 0.65 | 0.93 | 0.88 |
| 96 | 0.92 | 0.66 | 0.93 | 0.88 |
从 60 轮到 90 轮,mAP@0.5 只涨了 0.05,但 mAP@0.5:0.95 涨了 0.09,说明中后期的优化更多是在抠框边界,而不是发现新目标。这个阶段模型已经能稳定识别火焰和烟雾,但有些烟雾框会大一圈,导致 IOU 略微偏低,反映到 mAP@0.5:0.95 上就不是很好看。
5.3 不同输入尺寸带来的精度差异
我也试过把imgsz从 640 提升到 960,验证集 mAP@0.5 能从 0.92 涨到 0.93,但训练时间几乎翻倍,推理速度明显下降。火焰烟雾检测的应用场景常常是实时摄像头视频流,帧率比那 1 个点的精度提升更值钱,所以最后我还是保留在 640 推理,必要的时候只对输入 ROI 区域做二次放大检测,而不是全局提升分辨率。
6. 精度上不去时,先按这个顺序排查
“精度0.9左右”这个说法听着简单,但每个人跑出来的差一点可能差的是天上地下。我自己调试过程中遇到过三种最典型的掉点原因,排查顺序固定,省了很多无用功。
6.1 先看标签质量,别急着动网络结构
模型不收敛,第一反应不该是换 Attention、加小目标检测头,而是把验证集里预测错误的图可视化出来,拿原图、真实框、预测框放一块对比。我有一次发现 smoke 一类漏检特别严重,查了很久才发现是标注人员把很多半透明烟雾全部放弃标注了,导致正样本数量虚低。补齐这部分标注后,recall 直接涨了四个点。所以任何精度问题出现时,我永远先怀疑labels目录里的 txt 文件,抽样检查坐标是否合理、是否有多余空格、类别 id 是否在范围内。
6.2 再看训练日志里的 loss 曲线
如果 loss 曲线下降正常但 mAP 不动,大概率是数据划分出了问题,比如训练集和验证集存在严重重叠,或者验证集存在大量重复帧。如果 loss 曲线本身震荡剧烈,优先降低学习率,并关闭部分增强策略。Mosaic 增强对火焰这种大面积目标有时候反而有害,它会把多个火焰贴图拼在一起,造成不自然的场景,模型学到的分布偏了,实测会掉点。
6.3 最后才是调 NMS 和后处理阈值
模型训练完毕之后,你可以做两手部署前优化:一是把conf_thres和iou_thres在验证集上做个网格搜索,一般在 0.25 和 0.45 附近能找到平衡点;二是针对火焰烟雾场景专门调高 smoke 类别的置信度阈值,因为烟雾误检的相对代价更低,宁可漏一点也不能让系统频繁报警。这不是模型层面的改动,但对最终用户体验影响很大,值得花时间。
7. 实测落地的几点体会和后续扩展空间
数据集和模型跑通之后,我最大的感受是:火焰烟雾检测的精度瓶颈从来不在模型结构,而在于数据边界到底有没有划清楚。火焰的亮度和形态受环境光照影响极大,烟雾的透明度更是全无规律,你在训练集里定义好的“什么是火、什么是烟”,到真实场景里一定会遇到没见过的变体,这时候你能依赖的只有数据的覆盖度和标注的稳定性。精度 0.92 这个数,脱离场景谈没有意义,同一套权重放到受控的室内场景可能能到 0.97,放到大范围森林监控上可能一下跌到 0.8。
如果后续你想继续扩展,我建议优先考虑两个方向:一是用视频序列做时序信息融合,火焰烟雾在单帧里容易跟静态干扰混淆,但加上前后几帧的运动特征,误检率能骤降;二是针对无人机视角或高点位监控做专项采集,这个视角下的火焰尺寸特别小,且往往被树木或建筑遮挡一部分,和常规地面视角的数据分布差异很大。
这套“数据收集、格式清洗、训练调参、精度排障”的流程我走了不止一遍,每次换到新的检测对象都能复用。要注意的工具链并不复杂,核心就是 LabelImg、转换脚本、YOLO 官方训练命令三样,真正吃时间的是标注口径的统一和脏数据的清洗。耐心做完这一步,后面的训练往往比预想中顺利得多。
本文还有配套的精品资源,点击获取