简介:面向火灾预警与安全监控需求,这份火焰烟雾数据集收录了六千七百四十四张真实场景图像,每张图像均配有相应的XML标签和TXT标注,可直接用于YOLOv5、YOLOv7等目标检测模型的训练与验证。XML文件遵循Pascal VOC格式,记录火焰和烟雾的类别、边界框坐标等关键信息;TXT标注则转换为YOLO训练所需的归一化格式,两者完整覆盖了从数据标注到模型输入的流程。压缩包内还附带一个Python脚本,能够批量将XML转换成TXT,并允许自定义类别映射,可大幅减少预处理时间。资源包约四百六十二兆字节,内含六千七百四十四组图像及对应标注,外加一个脚本,文件总数超过两万个,目录组织清晰,便于按批加载。已有两千七百七十三人浏览学习,基于该数据训练的火焰烟雾检测模型精度约为零点九,适合用于实时报警、智能消防监控等场景。对于需要定制模型或优化精度的开发者,还可根据作者提供的技术服务进一步调整模型与数据策略。
1. 火焰烟雾数据集:XML标签与0.9精度之间,隔着一整套数据工程
拿到「火焰烟雾数据集,xml标签,yolo数据集,精度0.9左右」这个需求时,第一反应往往是:数据有了、标签有了、训练框架也有了,跑一把不就行了吗?实际做下来你会发现,XML标签只是起点,真正决定精度能不能到0.9的,是数据检查、格式转换、划分策略和训练参数这一整套流程。火焰烟雾检测又是一个特别容易翻车的场景——烟雾没有清晰边界、火焰在强光下会过曝、夜间目标像一团亮斑,同样的网络在人脸数据集上轻松上0.9,换到火焰烟雾上可能连0.7都费劲。这篇笔记就是把从XML标签到YOLO训练、最终把精度拉到0.9左右的完整路径讲清楚,包括中间那些能让你少加三天班的坑。
2. 拿到火焰烟雾XML标签数据后,先做什么:数类别、查异常、画框验标签
很多人拿到数据包第一件事就是急着写转换脚本,我一般会先花一个小时把数据摊开看一遍。XML标签对应的是Pascal VOC格式,每个XML文件描述一张图里所有目标的类别和坐标框。先别急着转格式,因为YOLO训练翻车的原因里,标签本身的结构问题占了很大比例,尤其是火焰烟雾这种带难例的数据。
2.1 解析XML标签:先搞清楚里面装的是什么
Pascal VOC格式的XML文件核心结构是固定的:<filename>存图片名,<size>存图片宽高和通道数,<object>列表里每个<name>是类别名,<bndbox>是目标框坐标。第一步,我会用一个脚本把所有XML解析出来,统计类别数量、每类目标数、图片总数,同时检查有没有缺字段的坏文件。这个流程跑一遍,数据集的底子就清楚了。
import os import xml.etree.ElementTree as ET from collections import Counter xml_dir = "annotations" class_counter = Counter() total_images = 0 bad_files = [] for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue total_images += 1 try: tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) for obj in root.findall("object"): name = obj.find("name").text 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) # 检查框是否越界,越界会在转换后导致YOLO训练报错或精度骤降 if xmax > img_w or ymax > img_h or xmin < 0 or ymin < 0: bad_files.append((xml_file, name)) class_counter[name] += 1 except Exception as e: bad_files.append((xml_file, str(e))) print("图片总数:", total_images) print("类别统计:", class_counter) print("异常文件:", bad_files[:20])这段代码做完,你应该知道三件事:数据集里有哪些类别(火焰、烟雾还是只有一类)、各类别数量差多少倍、有没有坐标越界的框。参数说明里有个容易被忽略的点:xmax > img_w这类越界框,转换时如果不裁剪,YOLO在训练计算损失时会给你报一堆坐标超范围的警告,精度直接掉几个点。这类异常文件必须单独拎出来处理,不是删除就是裁剪后重标。
2.2 类别平衡与尺寸统计:预测精度的第一道门槛
统计完类别数量后,重点看两个数字:最大类与最小类的比例、小目标占比。火焰烟雾数据集的典型问题是:火焰样本多、烟雾样本少,或者大火焰框多、小烟头框少。类别失衡的后果是模型偏向多数类,在测试时对烟雾的召回率很低,整体mAP被拖下来。解决手段就两招:对少样本类别做过采样(复制样本并做增强),或者对多样本类别做欠采样。实操上我一般优先过采样烟雾样本,因为火焰烟雾数据集本身规模不大,扔掉数据太可惜。
目标尺寸分布同样要画一下。把每个框的宽高除以图片宽高,得到归一化尺寸,统计有多少框的面积占比小于0.01。这个比例如果超过30%,就属于小目标密集的数据集,得考虑在训练时开多尺度、设置合理的锚框,或者在推理时用更高分辨率的输入。这一步不是玄学,它直接决定你后面选模型和设参数的方向。
2.3 图像增强是精度0.9的隐藏变量
火焰烟雾检测的目标外观很特殊:火焰颜色从黄色到红色渐变,亮部过曝后几乎纯白;烟雾半透明,边界模糊,背景透过来会干扰判断。默认的YOLO增强策略(HSV扰动、随机翻转、缩放)对这类目标不够。我一般会额外关注两个增强方向:亮度对比度扰动要大胆一些,模拟白天强光、夜晚暗光、火光映射到墙上的不同亮度场景;另外对烟雾样本,加轻度高斯模糊来模拟距离远时的烟雾形态,但这招要克制,加多了模型学到的全是模糊特征。
增强策略要在训练配置里统一落定,而不是每张图单独处理。常见做法是写一个独立的增强配置表,固定在训练脚本里应用,这样训练集和验证集的处理逻辑可复现,也好排查问题。
3. 把XML标签转成YOLO格式:目录规划、转换脚本与四个边界坑
XML标签本身不是YOLO能直接吃进去的格式,YOLO需要的是每个图片对应一个同名txt文件,里面每一行是类别id 中心x 中心y 宽度 高度,坐标全部归一化到0到1之间。转换逻辑不复杂,但坑都在细节里,尤其是火焰烟雾数据里那些标注框的边界情况。
3.1 目录规划与数据集清单
先安排好目录,常见做法是严格遵循YOLO的标准目录结构,这会让后面训练和验证省很多事,因为YOLO的训练脚本会按约定路径去找图片和标签。
dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 训练标签txt │ └── val/ # 验证标签txt ├── data.yaml # 数据集配置文件目录结构的核心原则是:images/train里的每一张fire_001.jpg,对应labels/train里必须有一个同名的fire_001.txt,文件名完全一致,只是扩展名从.jpg变成.txt。任何一张图缺标签,或者标签文件名对不上,训练时就会报No labels found的警告。我一般会在转换脚本最后加一个校验环节:遍历所有图片文件名,检查对应的txt是否存在,缺了就单独打日志。这一步值得做,尤其是当XML标签文件和图片文件来自不同来源时。
3.2 转换脚本:坐标归一化、类别映射、text标签生成
转换脚本的核心逻辑是:解析XML的size节点拿图片宽高,解析每个object拿类别名和bndbox坐标,然后将绝对坐标转成YOLO需要的归一化相对坐标,最后按class_id x_center y_center width height的格式写入txt。类别名到id的映射要写死在一个字典里,顺序一旦确定就不要再改,否则换一次模型跑出来的结果全对不上。
import os import xml.etree.ElementTree as ET # 类别到id的映射,一次定死,后续训练推理统一沿用 class_mapping = {"fire": 0, "smoke": 1} # 如果只有火焰一类,就写成 {"fire": 0} # 如果你的数据里有 fire、smoke、person 三类,要按你的标签名加进去 def convert_xml_to_txt(xml_path, txt_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_mapping: # 遇到没映射的类别,宁可直接跳过并打印,也不要硬编一个id print(f"跳过未知类别: {name} in {xml_path}") continue cls_id = class_mapping[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) # 边界裁剪:防止标注越界导致训练报错 xmin = max(0, min(xmin, img_w)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) # 转换到归一化的中心点与宽高 x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") with open(txt_path, "w") as f: f.write("\n".join(lines)) # 批量转换示例 xml_dir = "annotations" txt_dir = "labels/train" os.makedirs(txt_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue base = os.path.splitext(xml_file)[0] convert_xml_to_txt( os.path.join(xml_dir, xml_file), os.path.join(txt_dir, base + ".txt") )参数说明里最值得看的是坐标裁剪那三行。很多火焰烟雾数据的框是从视频帧里自动标注出来的,目标运动到画面边缘时框会出界,不裁剪的话,归一化坐标会出现大于1或小于0的值,YOLO训练时计算anchor匹配会直接忽略这些目标,等于白白丢了一部分样本。另外,class_mapping的id顺序要跟后面data.yaml里的names列表完全一致,这是转换环节最容易踩的坑,后面章节会详细说。
3.3 训练集/验证集划分与文件路径检查
转换完标签后,划分训练集和验证集。常见做法是随机打乱后按8:2或9:1切分。但这里有个火焰烟雾场景特有的问题:如果数据是从监控视频抽帧得到的,相邻帧内容几乎一样,随机划分会把同一段视频的相似帧同时分进训练集和验证集,验证结果会虚高。正确做法是:先按视频片段分组,一个片段的所有帧要么全进训练集,要么全进验证集,然后再从这些组里随机抽取。
划分完成后,逐行检查data.yaml里的路径是不是相对路径。我见过不少人把路径写成C:\Users\xxx\dataset\images\train这种绝对路径,换台机器就全废。写相对路径./dataset/images/train,配合在项目根目录下运行训练命令,最稳。
4. 训练配置与超参数:如何把精度稳定拉到0.9左右
数据准备好了,训练环节才是真正决定精度能不能摸到0.9的地方。火焰烟雾检测的模型选型和参数设置,跟通用目标检测有明显差异,下面按我的习惯从头到尾捋一遍。
4.1 模型选型:从YOLOv5s到YOLOv8m的取舍
没有指定的模型版本时,我一般默认用YOLOv5s起步做基线。原因很实际:YOLOv5的生态成熟,报错信息齐全,训练脚本对数据集格式的容忍度高,出问题好排查。火焰烟雾目标有大有小,多数场景下v5s的精度已经能在验证集上跑到0.85到0.9之间,如果数据质量好,冲到0.9并不难。
如果v5s跑完精度离0.9差一口气,再考虑换YOLOv8m或者v5m。这里有个选型原则:不要一上来就上最大的模型。火焰烟雾数据通常只有几千张图,大模型在小数据上过拟合很快,验证精度甚至会低于小模型。常见做法是:先跑v5s建立基线,观察过拟合程度(训练loss下降但验证精度停滞),再决定是否换更大的模型。
4.2 数据YAML与训练命令:epochs、imgsz、batch的基准值
data.yaml是YOLO数据集的配置文件,内容非常简单,但错误率极高。常见错误包括:nc写错、names顺序跟转换脚本的class_mapping不一致、路径后缀写错。
# data.yaml train: ./dataset/images/train val: ./dataset/images/val nc: 2 # 类别数,fire和smoke一共2类 names: ["fire", "smoke"] # 顺序必须与转换脚本的class_mapping一致这个文件里最坑的是names的顺序。转换脚本里我们定义了{"fire": 0, "smoke": 1},那么这里names必须是["fire", "smoke"]。如果顺序写成["smoke", "fire"],模型会把第0类当成smoke来学,所有预测标签全部错位,mAP看起来还有模有样,但实际输出的每个框类别都是错的,这种错误最隐蔽。
训练命令的基准参数我通常是这样设的:
python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --device 0参数含义逐一说清楚。--img 640是输入分辨率,火焰烟雾这种小目标多的场景,我会优先用640起步,如果小目标占比高再试832,但显存占用会变大。--batch 16是批大小,显存不够就降到8,但batch太小会导致batchnorm统计不稳定,loss波动大。--epochs 150是训练轮数,火焰烟雾数据集一般100轮就能收敛,但设150轮是为了观察后面有没有过拟合。--device 0指定显卡,没有显卡就用--device cpu,但训练速度会慢几十倍,只适合验证流程用的极小型数据。
4.3 训练过程监控:从loss曲线判断0.9还有多远
训练开始后,重点看两个东西:loss曲线和验证集mAP。YOLO训练终端会每隔一个epoch打印一次box_loss、cls_loss、obj_loss和mAP@0.5。判断方法很简单:前30个epoch里,mAP从0快速爬到0.7以上,属于正常;到100轮后mAP还在持续上涨但涨幅很小,说明模型还在缓慢优化;如果val mAP先升后降,train loss还在降,那就是典型的过拟合,需要提前停止或者加正则手段。
还有一个细节:火焰烟雾数据里如果火焰和烟雾经常同时出现,模型会在两者间产生混淆。此时看每一类的AP比看整体mAP更有用。训练日志里会按类别分别打印AP,我要看到的是fire和smoke各自的AP都在0.85以上,整体mAP到0.9才有说服力。如果烟雾AP只有0.6,说明烟雾样本太少或者边界模糊问题没解决,这时候要回到数据层面补样本,而不是继续调参。
5. 火焰烟雾训练避坑:5条血泪经验,每一条都让精度掉好几个点
这个章节是我最想写的。火焰烟雾检测训练里,有一批非常典型的问题,现象各不相同,但共同点是:都会让精度从0.9掉到0.7附近,然后你开始怀疑模型、怀疑参数,实际上问题在数据和配置上。
5.1 标签错位:XML和图像文件名对不上
现象:训练正常跑完,mAP只有0.5左右,loss曲线看起来也没明显异常,但预测时发现很多框的位置是对的,类别却是乱的。
原因:XML里的<filename>和实际图片文件名不一致。比如XML里写的是fire_001.jpg,但实际图片叫frame_001.jpg,转换脚本按XML文件名生成txt,图片和标签对不上。更隐蔽的是,数据包里有多个子目录,不同目录下存在同名文件,脚本一合并就覆盖了。
解决:转换前先做一次文件名统一。写一个校验函数,把每张图片的宽高和XML里记录的大小对比,不一致立即报警。转换后抽查10张图,用OpenCV把txt里的框画回原图,一眼就能看出框的位置对不对。这一步时间不长,落一次保平安。
5.2 少量前景、多数背景:烟雾目标太小被忽略
现象:验证集里漏检严重,特别是远处或刚冒出来的烟雾,模型完全没预测。mAP卡在0.8附近上不去。
原因:烟雾通常是小目标,一张1920x1080的监控画面里,烟雾可能只占几十乘几十像素。YOLO默认的anchor策略对极小的目标不敏感,加上训练时下采样倍数太大,小目标的特征图响应弱,被当成背景忽略了。
解决:两个方案叠加使用。第一,把输入分辨率从640提到1280,小目标在特征图上的像素占比变大,代价是训练时间翻倍;第二,检查数据集里小目标占比,如果确实很高,训练时开启多尺度训练参数,让模型在不同尺度下都能看到小目标。这两个操作对火焰烟雾的漏检改善非常明显。
5.3 精度0.9的“虚高”:mAP@0.5与mAP@0.5:0.95的差距
现象:报出来的mAP@0.5是0.91,感觉达标了,但一上实际场景,框是歪的,IoU不高,用户体验很差。
原因:mAP@0.5只要求预测框与真值框的重叠度超过0.5就算正确,门槛很低。尤其在火焰这种大色块目标上,框稍微偏一点也能拿到0.6、0.7的IoU,于是精度虚高。或者只看验证集不看测试集,而验证集里恰好是那些最容易预测的样本。
解决:做两件事。第一,训练完除了看mAP@0.5,还要看mAP@0.5:0.95,这个指标对框的定位质量更敏感,我一般要求它在0.6以上才认为模型可用;第二,自己留一部分完全没参与训练的数据做盲测,模拟真实场景(夜间、远距离、不同摄像头角度),盲测精度才是真实水平。
5.4 验证集切分不当导致模型“作弊”
现象:训练时val mAP从第一轮就很高,甚至比训练集的精度还高,模型看起来完美,但拿到新场景就失灵。
原因:最常见的切分错误就是把连续视频帧随机切分。相邻帧里火焰烟雾的位置几乎一样,模型在训练时见过了“考试题”,验证结果自然好看。这个我之前吃过亏,做了一个视频抽帧数据集,随机8:2切分后val mAP 0.92,换了一个没见过的视频场景测试,直接掉到0.6。
解决:严格按视频片段划分。同一视频的所有帧归同一侧,片段之间再做随机分配。另外,划分时保证每类目标在训练集和验证集里都有足够数量,不要让某个类别只在验证集里出现。
5.5 夜间与半透明烟雾漏检:增强策略翻车
现象:白天场景检测良好,夜间或者逆光场景漏检率飙升。更常见的是烟雾检测框不稳定,同一段烟雾,有时画框有时不画框。
原因:训练数据白天样本占绝大多数,夜间样本少,模型没有见过足够的低光照特征。而烟雾半透明特性导致其边缘特征极弱,本质上是对比度问题。
解决:第一,训练时把亮度、对比度、饱和度增强的幅度调大,模拟夜景光照变化;第二,把采集到的夜间样本单独做一次过采样,让模型在训练过程中能看到更多暗光场景。注意增强幅度不是越大越好,过度增强会把白天样本变成“半黑不黑”的怪图,模型学到的特征反而扭曲,建议分场景比例做增量调整,增强后的样本占比控制在合理范围。
6. 验证模型:PR曲线与两次自检,确认0.9不是黑匣子里的数字
训练完成后,验证是最后一道工序,也是区分“模型能用”和“模型看起来能用”的关键环节。只看终端输出的mAP是不够的,我会做三件事确认0.9是真实水平。
第一,跑一次正式的验证脚本,输出每类的AP、PR曲线和混淆矩阵。PR曲线看的是precision和recall的权衡关系:曲线越靠近右上角越好,如果烟雾类的曲线明显瘪进去,说明烟雾漏检比火焰严重。混淆矩阵看的是“预测成什么”:如果fire行里有不少被预测成smoke的格子,说明两类特征在模型内部混叠了,典型解决方向是补数据或调cls_loss权重。
第二,挑验证集里的困难样本做可视化预测:夜间帧、小火焰、半透明烟雾各选十几张,运行预测并把框画回原图。这一步靠眼睛看,框的位置、大小、置信度是否符合直觉。置信度低于0.3的框如果全是烟雾,说明目标太模糊是普遍现象,要考虑降低推理阈值,但降低后误检会增多,要有对误检的容忍度再调。
第三,做一个真正没参与训练的视频片段盲测。跑一个连续几十帧的视频,逐帧检测,观察框的抖动程度。火焰燃烧本来就是动态的,框轻微抖动正常,但如果是帧间跳变剧烈、一会儿有框一会儿没有,那说明模型对时序信息完全没利用,单帧检测的上限就在这。常见做法是推理时加轻量级的跟踪平滑,或在数据里加入更多连续帧样本让模型学得更稳,但单帧精度的硬天花板依然存在,需要额外模块解决。
最后说一个我的习惯:每次训练完,我都会把训练命令、数据划分的随机种子、增强参数、最终指标四项记录在一起。下次有人问“这个0.9是怎么来的”,直接翻记录就能全部复现,而不是凭记忆复述。这算是我在这个数据工程里收获的最实在的一条经验,希望帮到你。
本文还有配套的精品资源,点击获取