恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
烟火检测数据集与YOLO训练实战:从标注格式到模型部署
首页
资讯中心
/
烟火检测数据集与YOLO训练实战:从标注格式到模型部署
烟火检测数据集与YOLO训练实战:从标注格式到模型部署
发布时间:2026/10/12 0:33:41
简介烟火检测数据集面向目标检测与YOLO系列模型训练包含一千张真实场景图像及对应XML标注适用于烟火识别、安全监控等视觉任务也适合作为目标检测入门学习的练习数据。压缩包整体约89.87MB共两千个文件目录分为JPEGImages与Annotations两部分前者存放一千张JPG原始图片后者提供对应XML标注文件可直接配合YOLOv3、YOLOv5等主流框架使用。已有三千余人学习下载数据在社区中具有一定参考价值也可作为入门或项目验证的样本集。标注以边界框形式标出烟火位置并覆盖不同光照、背景和烟火类型帮助模型学习形状、颜色、亮度等关键特征。配合数据增强与多尺度预测可训练出在复杂环境下准确率较高的检测模型为火灾预防提供可靠视觉支撑。1. 烟火检测数据集为什么1000张已标注图像是刚需真正在产线上跑过烟火检测的人都有体会模型结构再新缺了高质量训练数据效果也是空中楼阁。这个烟火检测数据集11000IMG已标注.zip解决的问题很直接——你不需要自己扛着摄像头去化工园区、林场、仓库里蹲几个月采集画面也不需要为了一张图里那个只有十几个像素的火苗拉几天标注框。它把1000张图像和对应的目标框打包在一起类别覆盖火焰与烟雾两个核心对象解压之后就能直接进入训练流程。对于算法工程师、消防物联网集成商以及需要快速验证YOLO系模型在烟火场景表现的团队来说这是把项目从零推进到第一版原型的最短路径。2. 拿到ZIP先别急着训练目录结构与标注格式解读2.1 解压后的第一件事确认images与labels的对应关系我习惯把数据集解压到一个固定目录比如/data/smoke_fire/再用tree命令扫一眼整体结构。烟火检测数据集的打包方式多种多样但绝大多数遵循图像目录与标注目录分离的原则。最常见的布局是images/与labels/同级也有部分数据集沿用VOC的JPEGImages/加Annotations/结构。先别管是哪种第一步一定是验证每张图都有对应标注这个基本事实。unzip 烟火检测数据集11000IMG已标注.zip -d /data/smoke_fire cd /data/smoke_fire find images -type f | wc -l find labels -type f | wc -l这段代码先解压再分别统计图像和标注文件的数量。如果两边数字对不上说明数据集里存在无标注的废图这类图在训练时轻则浪费算力重则让模型学到无目标的错误映射。我一般还会加一步交叉验证文件名是否一一对应把没有labels对应项的images挑出来直接删掉或移入unlabeled/目录避免后续训练脚本报错。这一步看着简单却是最容易埋雷的地方。2.2 一眼识别标注格式VOC、COCO与YOLO三种外观烟火数据集的标注格式五花八门常见的无外乎三种VOC的XML、COCO的JSON以及YOLO系的TXT。我拿到标注目录后先看扩展名就能排除一半疑惑。.xml是VOC系用objectnamefire/namebndbox嵌套结构描述目标框.json可能是COCO也有可能是LabelMe导出.txt则大概率是YOLO格式每行五个数字依次是类别ID、中心点x、中心点y、框宽高全部是相对图像的归一化坐标。表里列一下三种格式的关键差异。格式扩展名坐标形式类别表达典型配套VOC.xml绝对值xmin, ymin, xmax, ymax字符串类别名Pascal VOCCOCO.json绝对值x, y, w, h数字类别ID映射表COCO2017YOLO.txt归一化cx, cy, w, h数字类别IDDarknet/Ultralytics识别的核心逻辑是看坐标值是否落在0到1之间。YOLO的txt文件里所有数值必然在[0,1]区间而VOC和COCO的绝对值坐标一般会大于1取决于图像宽高。还有一个细节COCO的标注框宽高可能出现负值或包含分割多边形segmentation字段烟火检测数据集很少附带分割标注遇到带多边形的大概率是COCO格式且做了实例分割扩展。2.3 用一段脚本摸清类别分布与图像质量拿到1000张图我从不直接开训。先统计类别分布是烟火检测的必修课因为火焰和烟雾在现实中出现的频率天然不对等——很多标注员习惯把图像里所有烟火都打框但烟雾往往比火焰多几倍。如果smoke占80%而fire只有20%训练出来的模型会天然偏向烟雾对小型火灾初期的火苗反应迟钝。下面这段脚本可以快速统计YOLO格式标注的类别数量。import os from collections import Counter label_dir /data/smoke_fire/labels class_names [fire, smoke] # 按训练脚本里的类别顺序 total Counter() for name in os.listdir(label_dir): if not name.endswith(.txt): continue with open(os.path.join(label_dir, name)) as f: for line in f: cls_id int(line.strip().split()[0]) total[class_names[cls_id]] 1 print(f共统计 {len(os.listdir(label_dir))} 个标注文件) for cls in class_names: print(f{cls}: {total[cls]} 个目标框)逻辑很简单遍历labels目录下所有txt文件按空格切分每一行取第一个字段作为类别ID累加到计数器里。这里有个注意点class_names列表的顺序必须和你后续训练配置里的names完全一致否则统计结果和实际类别会对不上。跑完看到分布之后如果一方占比超过70%我会考虑在训练时给少样本类别加权重或者用下文讲到的增强手段补样本而不是直接硬训。3. 把数据集喂进YOLO格式转换、数据集划分与最小训练配置3.1 从VOC或COCO转到YOLO格式转换脚本与三个参数坑绝大多数公开的烟火检测数据集都提供VOC或COCO格式而YOLO系列训练要求的却是归一化txt。转换这一步绕不开我直接给出一个从VOC XML转YOLO txt的Python脚本这是烟火检测项目里最常复用的工具。import xml.etree.ElementTree as ET import os def convert_voc_to_yolo(xml_path, out_path, classes): 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.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) # 把绝对值坐标转成YOLO的归一化中心点宽高 cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) classes [fire, smoke] # 这个顺序就是训练时的类别ID xml_dir /data/smoke_fire/Annotations out_dir /data/smoke_fire/labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): convert_voc_to_yolo( os.path.join(xml_dir, xml_file), os.path.join(out_dir, xml_file.replace(.xml, .txt)), classes )逻辑说明解析XML拿到图像宽高遍历每个object节点读取bndbox下的四个坐标值再转换成归一化的中心点、宽高组合。这里的三个坑必须提醒第一classes.index(name)会抛异常如果XML里有不在列表里的类别名必须先做if name not in classes过滤第二img_w和img_h必须从XML的size节点读取不能自己猜固定值否则宽高比不同的图转换后全部错位第三w和h不能加绝对值虽然VOC坐标一般保证xmax xmin但COCO数据集偶尔有反框转换前最好判断一下。如果拿到的是COCO的JSON格式转换逻辑也类似只不过从annotations数组里遍历每个目标的bbox字段注意COCO的bbox是[x, y, width, height]且坐标绝对值除以图像宽高即可归一化。3.2 按比例切分train/val并生成dataset.yaml划分数据集的比例我习惯用8:1:1即800张训练、100张验证、100张测试。烟火检测场景下测试集尽量保留因为你最终要做的是没见过的火情泛化验证而不是在训练集上自嗨。切分脚本不建议直接用随机数而是先random.shuffle再切片这样保证每个子集里都有白天、夜晚、近景、远景的样本。python -c import os, random from shutil import copy2 imgs os.listdir(/data/smoke_fire/images) random.seed(42) # 固定种子保证可复现 random.shuffle(imgs) n len(imgs) train, val, test imgs[:int(n*0.8)], imgs[int(n*0.8):int(n*0.9)], imgs[int(n*0.9):] for split, files in [(train, train), (val, val), (test, test)]: os.makedirs(f/data/smoke_fire/{split}/images, exist_okTrue) os.makedirs(f/data/smoke_fire/{split}/labels, exist_okTrue) for f in files: copy2(f/data/smoke_fire/images/{f}, f/data/smoke_fire/{split}/images/{f}) copy2(f/data/smoke_fire/labels/{f.replace(\.jpg\, \.txt\)}, f/data/smoke_fire/{split}/labels/{f.replace(\.jpg\, \.txt\)}) print(ftrain{len(train)}, val{len(val)}, test{len(test)}) 这个脚本把图像和对应的txt标注按同一套随机顺序同步复制到新的划分目录。关键参数是seed42别小看这一行没有固定随机种子每次划分结果都不同你复现实验时连baseline都对不上。另外还要确认图片后缀烟火数据集里jpg、png、bmp混用很常见上面脚本默认jpg如果遇到png或webp要么统一改后缀要么在代码里用os.path.splitext处理扩展名。划分完目录后在/data/smoke_fire/下新建一个dataset.yaml内容是训练和验证路径加类别名列表。path: /data/smoke_fire train: train/images val: val/images test: test/images names: 0: fire 1: smoke nc: 2names的顺序必须和转换脚本里的classes顺序一致这是YOLO训练最容易出的隐蔽错误——两个地方顺序不一致模型不会报错但loss会诡异地不降你检查代码也看不出问题。nc是类别数这里直接写2。3.3 最小训练命令与三个必须改的超参数据就绪之后训练命令以YOLOv8为例极简版本是下面这条。yolo detect train data/data/smoke_fire/dataset.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ device0 \ patience30逻辑说明modelyolov8s.pt表示从COCO预训练权重开始微调不是从头训练这对只有800张训练图的烟火项目至关重要迁移学习能让模型在冻结点上迅速收敛。patience30是早停参数如果验证集mAP连续30个epoch不涨就自动停止防止过拟合也省时间。三个必调参数里第一个是imgsz烟火目标往往小而远640的输入尺寸对10像素以下的火苗基本无效我建议先跑一版640看基线再上960对比。第二个是batch暴力设大没有意义根据显存来一般12G显存配batch8到16batch太小导致batchnorm统计不稳定。第三个是epochs1000张规模的数据集160个epoch足够模型收敛往上加只会过拟合。4. 只有1000张图怎么够用烟火场景的增强策略与扩充套路4.1 火焰和烟雾的视觉特性决定了增强方向通用目标检测在COCO上做增强那套方案搬到烟火场景上效果会打折扣。火焰不是刚性物体它没有固定边缘颜色从内焰的蓝色到外焰的橙红色连续过渡形状每秒都在变。烟雾更极端半透明、低对比度、边缘几乎不可定义经常与背景融为一体。这决定了烟火检测模型学习的核心不是形状而是颜色分布 纹理模式 局部亮度突变。增强策略也要围绕这三者展开而不是像普通物体那样做大量几何变换。明亮的场景下模型容易把火焰理解成红色高温物体一旦夜晚光线变暗、火光颜色被环境光中和同一个火焰在图像上的特征完全变了。所以在增强时我格外看重亮度扰动和色彩抖动这两类让模型见过尽量多的光照条件下的烟火。4.2 在YOLOv8的增强参数里做文章推荐配置表YOLOv8原生支持一批数据增强超参不需要额外写脚本就能直接调整。表里是我在烟火检测上反复试过的一组低配参数配置直接写进训练命令或对应的yaml即可。参数名推荐值作用烟火场景的解释hsv_h0.015色相扰动让火焰颜色在橙红范围内小幅变化模拟不同燃料燃烧的色差hsv_s0.7饱和度扰动压火苗的季节性差异比如夏天阳光强导致颜色发白hsv_v0.5明度扰动模拟阴天、黄昏、夜晚的光照变化对烟雾可见度影响巨大fliplr0.5水平翻转烟火目标没有左右语义翻转等于白嫖一倍样本scale0.5随机缩放模拟摄像头在不同焦距下的目标大小提升小目标鲁棒性mosaic1.0四图拼接强制模型在小目标的上下文中学习解决远距离火情漏检4.3 用albumentations做离线扩充脚本与复检要点在线增强有一个问题它只作用于训练时的输入验证集和测试集还是原始分布。如果火焰样本实在太少我还是建议做一轮离线扩充。下面这段用albumentations实现亮度、对比度和随机裁剪的扩充脚本可以把重点类别补到够用的程度。import albumentations as A import cv2, os # 定义针对烟火场景的增强管线 transform A.Compose([ A.RandomBrightnessContrast(brightness_limit(-0.3, 0.3), contrast_limit(-0.2, 0.2), p0.9), A.HueSaturationValue(hue_shift_limit10, sat_shift_limit30, val_shift_limit40, p0.8), A.GaussNoise(var_limit(10.0, 40.0), p0.3), A.RandomScale(scale_limit(-0.3, 0.3), p0.5), ]) src_img_dir /data/smoke_fire/train/images src_lbl_dir /data/smoke_fire/train/labels out_img_dir /data/smoke_fire_aug/train/images out_lbl_dir /data/smoke_fire_aug/train/labels os.makedirs(out_img_dir, exist_okTrue) os.makedirs(out_lbl_dir, exist_okTrue) def read_yolo_txt(txt_path, img_w, img_h): boxes [] with open(txt_path) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) cx, cy, w, h map(float, parts[1:]) # 归一化转像素坐标 boxes.append([cls_id, int((cx - w/2) * img_w), int((cy - h/2) * img_h), int((cx w/2) * img_w), int((cy h/2) * img_h)]) return boxes def write_yolo_txt(boxes, txt_path, img_w, img_h): with open(txt_path, w) as f: for b in boxes: cls_id, xmin, ymin, xmax, ymax b # 框可能被裁出图像外先裁回来 xmin max(0, min(xmin, img_w-1)) xmax max(0, min(xmax, img_w-1)) ymin max(0, min(ymin, img_h-1)) ymax max(0, min(ymax, img_h-1)) if xmax xmin or ymax ymin: continue # 像素坐标再转回归一化 cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h f.write(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n) for fname in sorted(os.listdir(src_img_dir))[:]: img cv2.imread(os.path.join(src_img_dir, fname)) if img is None: continue h, w img.shape[:2] txt_path os.path.join(src_lbl_dir, fname.replace(.jpg, .txt)) boxes read_yolo_txt(txt_path, w, h) # 对同一张图做3次不同增强 for i in range(3): transformed transform(imageimg, bboxesboxes, class_labels[b[0] for b in boxes]) t_img, t_boxes transformed[image], transformed[bboxes] t_h, t_w t_img.shape[:2] out_base fname.replace(.jpg, f_aug{i}.jpg) cv2.imwrite(os.path.join(out_img_dir, out_base), t_img) write_yolo_txt(t_boxes, os.path.join(out_lbl_dir, out_base.replace(.jpg, .txt)), t_w, t_h) print(增强完成输出目录:, out_img_dir)代码说明先定义albumentations增强管线RandomBrightnessContrast模拟不同光照强度下的烟火可见度GaussNoise模拟监控摄像头在弱光环境下的噪点这对夜间可燃气体火焰识别尤其关键。中间的辅助函数read_yolo_txt把归一化坐标转成像素坐标交给albumentations处理后再由write_yolo_txt转回归一化格式。最后对每张原始图生成3张增强图把总样本量从800训练图扩到3200张左右。这里有个必须强调的参数细节albumentations要求bboxes参数传入的是像素坐标的[xmin, ymin, xmax, ymax]列表而且class_labels要和bboxes一一对应少传一个就会报格式错误。输出时还要防止增强把目标框整个裁出画面代码里做了xmax xmin的判断遇到这种框要直接丢弃不丢就会写出一条负宽高的非法标注训练时损失函数直接给你算出NaN。5. 烟火检测训练避坑指南5个常见的翻车现场与排查方法5.1 现象mAP看着不错小火苗和远烟全漏掉训练完看验证集mAP能到0.8以上以为万事大吉真正拿一段园区实时画面去测发现30米外的火苗和远景轻烟一个都检不出来。这类烟火目标只有20到30个像素对下采样32倍的YOLO模型来说经过多层卷积池化后特征几乎被抹平。原因很直接模型本身在下采样过程中丢失了小目标信息与训练是否充分无关。解决上我建议先把输入分辨率从640提到960小目标的有效特征至少增加一倍。其次在推理阶段用SAHISlicing Aided Hyper Inference做切片推理把大图切成512x512的重叠块分别检测再合并。最后训练时单独把imgsz调大往往比换大模型更划算因为烟火检测吃显存换大模型容易OOM。5.2 现象一到夜间场景模型就变瞎子白天测试一切正常晚上监控画面一推流FPS掉不下去但检测框全部消失。翻看训练集分布发现800张train图里只有不到50张是夜间或弱光样本。烟火检测的数据集里白天的占比天然偏高这是采集成本决定的。解决分两条路一是用第4章的增强策略把亮度下限调低对原图做RandomBrightnessContrast之后扩充夜间样本占比二是用图像处理手段把部分正常样本的亮度通道压低、叠加噪声模拟红外夜视相机的均匀亮度画面。还有一种常见做法是引入红外光下的烟火图像作为额外训练数据如果数据集里没这类图可以用红外相机补拍几十张做微调。注意夜间增强不能把图像压成纯黑否则模型学到的是黑色背景没有目标不是真的学会识别暗光里的火苗。5.3 现象红色车尾灯、晚霞、红色霓虹灯被当成火焰这个坑基本上是烟火检测的招牌问题。火焰在RGB空间里具有高红、中绿、低蓝的特征而红色车灯和晚霞在颜色特征上和火焰高度相似。模型如果只在原图上学习颜色分布而不关注纹理、闪烁频率和形状动态误报几乎无法避免。解决的关键是给模型提供足够多的难负样本hard negative。可以把背景图中出现的红色物体打上背景类别的标签或者直接引入一批完全没有烟火但含有大量红色光源的负样本图像参与训练。另一个有效思路是使用background类别让模型显式学习这是红色但不是火。如果数据集本身没有负样本目录我建议从监控视频里抽帧专门选晴天夕阳、车流密集的场景手动标注为背景类。5.4 现象训练loss不降验证集mAP来回震荡训练跑了一百多个epochloss曲线像心电图验证mAP在高位和低位之间反复横跳。检查模型结构没问题超参也调过问题大概率出在标注数据上。烟火数据集的标注质量参差不齐标注员对半透明烟雾的边界判断标准不一致同一个烟雾有的人框住整体有的人只框了浓烟中心区域。另外一个高频原因是标注坐标越界。有的烟火发生在图像边缘标注框跑到图像之外负值或超过宽高的坐标会在loss计算时产生异常梯度。解决方法是写一段脚本扫描所有txt标注文件检查归一化坐标是否都在0~1之间把越界的框直接裁回边界。如果裁回后宽高小于两三个像素这个框就没有保留意义了直接删除。这种脏数据清理比调任何超参都管用。5.5 现象模型FPS跑不满实时要求监控流掉帧卡顿一个烟火检测模型在GPU上推理速度能到200FPS一上到8路监控视频流服务器CPU立刻打满画面开始掉帧。问题不出在模型推理本身而在于多路视频流的解码、缩放、预处理没有管线化。烟火检测部署大多在边缘端或推理服务器上运算瓶颈往往在图像缩放和归一化上。常见做法是把图像的letterbox缩放、颜色通道转换、归一化操作全部放在GPU上做用cv2.cuda或torchvision.transforms的张量操作替代CPU循环。其次堆叠式检测场景建议用batch4以上的批量推理把多路画面拼成一个大张量一次过模型吞吐量能提升三到五倍。真跑不满的时候把模型从YOLOv8m降到YOLOv8n或者导出TensorRT的FP16引擎一般能拉回实时性代价是mAP掉1到3个百分点对烟火这种粗粒度检测来说通常可以接受。6. 验证与进阶用mAP、FPS和误报率判断模型能不能上场6.1 混淆矩阵和PR曲线是烟火检测的照妖镜YOLO训练完会在runs/detect/train/下生成一堆验证文件我最先打开的不是results.png而是confusion_matrix.png。在烟火检测里这张图能直接告诉你模型哪里在蒙。看火焰类别那一行如果大量真实框被预测成背景说明召回率不足小或者暗的火焰没学到如果预测到火焰的框里有大量来自背景区域的点说明误报严重。PR曲线的面积同样关键烟火检测的P和R是此消彼长的关系作为消防预警产品我宁可牺牲一点precision换取recall漏报的代价远高于误报。6.2 用热力图看模型到底盯住了什么跑通一版模型后我会拿几张典型的火焰图像做梯度加权类激活图Grad-CAM观察模型关注的区域。如果热力图集中在火焰中心而不是覆盖整个火焰区域甚至边缘的烟雾说明模型学的是局部亮点这会导致火势蔓延后反而检测不到——因为大范围火焰的画面里单点亮点的特征被稀释了。这时需要回去增强纹理信息或者把标注框调整到包含完整的火焰边缘过渡区。6.3 我压箱底的一个习惯最后分享一个让我少被坑的习惯每次训练前都把dataset.yaml里的names顺序、转换脚本里的classes顺序、验证脚本里的类别读取顺序三个地方用同一份常量文件统一管理。烟火检测数据集本身不复杂但项目一旦接手多人协作最频繁翻车的永远是这类低级不一致而不是模型结构。先跑10个epoch看看loss合理下降再用真实视频流做一次端到端验证确认检测延迟和误报率满足实际需求后再回去调参和扩充数据。这套流程下来基本不会出现训练一周才发现数据格式错了的惨剧。希望帮到你。本文还有配套的精品资源点击获取