简介面向火灾预警、安全生产监控、智慧园区等场景这份YOLO火焰烟雾数据集包含18800张实拍图片及对应目标检测标注可直接用于YOLOv5、YOLOv8等主流模型的训练与算法验证适合算法工程师、研究人员及竞赛团队快速搭建烟火识别任务。压缩包为zip格式整体约765.03MB资源文件共2000份以XML类型标注文件为主描述说明同时提供YOLO所需TXT标注数据拿到后无需任何格式处理即可投入训练。标注覆盖火焰与烟雾两大类兼顾VOC格式XML与YOLO格式TXT两套标准省去自行转换环节样本来源丰富文件名显示来自YouTube视频帧、室内外不同尺度目标等多样采集渠道有助于提升模型在真实监控画面中的鲁棒性。目前已有1428人浏览学习需要高质量火焰烟雾样本的开发者可据此快速启动训练与模型调优。1. 18800张火焰烟雾图YOLO和VOC都能直接喂这个数据集到底能干嘛做火灾检测的同行应该都体会过找数据比调模型还痛苦。公开的火焰烟雾数据集要么只有几百张要么全是室内小火的摆拍拉到野外厂区、隧道、森林场景里一测误报和漏报一起炸。这个标题里说的18800张火焰、烟雾数据集是少见的规模够大、标注格式够全的集合图片给齐YOLO用的TXT标注和VOC用的XML标注都带意味着你不用自己写转换脚本拿过来就能同时喂给YOLOv5/v8和各类VOC系检测框架。适合消防预警、电力巡检、安防监控这几条线的人用它做预训练或直接微调。先说结论数据本身大概率是混合来源拼出来的不是某个实验室统一拍摄的所以拿到手后格式整理和脏数据清洗这一步省不了这也是本文后面要花大篇幅讲的东西。2. 先看清格式再动手YOLO的TXT和VOC的XML到底差在哪很多人在数据集上翻车不是模型不行是没搞懂两种标注各自的世界观。YOLO把一切归一到0到1之间XML里存的是像素绝对值两个格式一混用轻则训练loss不降重则目标框全跑到图外面。2.1 YOLO TXT标注归一化坐标怎么读怎么写YOLO格式的标注文件是纯文本每个目标占一行五个字段分别是类别编号、归一化中心点x、归一化中心点y、归一化宽度w、归一化高度h。注意是中心点不是左上角这是和COCO、VOC最大的区别。一行数据大概长这样0 0.483203 0.354687 0.126562 0.198958第一个0代表类别ID比如约定0是fire1是smoke。后面四个数除以图片宽高后全部落在0到1区间。这套设计的直接好处是图片缩放、裁剪时标注不会失效因为坐标跟着比例走。写代码解析时我习惯先把所有类别写进一个classes.txt防止每次凭脑子记编号。with open(classes.txt, r) as f: classes [line.strip() for line in f.readlines()] def read_yolo_txt(txt_path, img_w, img_h): boxes [] with open(txt_path, r) as f: for line in f.readlines(): parts line.strip().split() cls_id int(parts[0]) cx, cy, bw, bh map(float, parts[1:]) x1 (cx - bw / 2) * img_w y1 (cy - bh / 2) * img_h x2 (cx bw / 2) * img_w y2 (cy bh / 2) * img_h boxes.append((cls_id, x1, y1, x2, y2)) return boxes这段代码把归一化中心点还原成像素绝对坐标方便后续画框检查。关键参数是img_w和img_h必须和实际图像尺寸一致否则还原出来的框就是歪的。很多人在这一步偷懒读TXT时直接从XML里拿宽高但TXT和XML又不是一一对应图换了尺寸就全乱套。建议每张图片用PIL或cv2读一遍真实尺寸再进解析函数。2.2 VOC XML标注绝对像素坐标和文件夹结构VOC格式的XML是个完整的小档案里面记录了文件夹名、文件名、图像路径、尺寸以及每个目标的name和bndbox。bndbox里存的是xmin, ymin, xmax, ymax全是像素值对应目标框的左上角和右下角。结构长这样annotation folderJPEGImages/folder filenamefire_001.jpg/filename size width1280/width height720/height depth3/depth /size object namefire/name bndbox xmin468/xmin ymin201/ymin xmax694/xmax ymax411/ymax /bndbox /object /annotationxml.etree.ElementTree是Python标准库自带的不需要额外装依赖直接解析就行。读XML的核心思路是拿filename找到对应图片再根据size里的宽高确认bndbox有没有越界。写代码时我会这么做import xml.etree.ElementTree as ET def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() img_name root.find(filename).text size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) boxes [] for obj in root.iter(object): name obj.find(name).text 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) boxes.append({name: name, bbox: [xmin, ymin, xmax, ymax]}) return img_name, img_w, img_h, boxes注意一个细节有些标注工具生成的XML里bndbox的标签不是xmin而是x_left或X1解析前最好先打印一条看看字段名再写通用代码不然又是一个坑。VOC格式适合人要直观检查的场景XML能直接打开看到目标名和坐标但它的问题是文件体积大、解析慢所以训练框架大多只吃YOLO的TXT或COCO的JSON很少有人拿一堆XML直接去train。2.3 一份数据两套格式坐标换算不能凭感觉数据集同时提供TXT和XML本质上是同一批标注的两种序列化方式。YOLO的归一化坐标和VOC的像素坐标之间的换算公式是固定的没有玄学空间。从VOC转YOLO核心就是做除法反过来就是做乘法。公式如下建议直接写成函数复用def voc_to_yolo(xmin, ymin, xmax, ymax, img_w, img_h): cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h return cx, cy, w, h公式本身不难但有几个边界条件必须注意。第一VOC的坐标是闭区间还是开区间有的标注工具会差一个像素对训练影响不大但如果目标只有一两个像素宽误差会放大。第二归一化后的x和y如果算出来是负数或者大于1说明原始XML的bbox已经越界这种样本要直接剔除而不是硬转。第三类别名到ID的映射表必须和数据集的类别定义一致比如你用0代表fire但某个XML里把fire放在了第5个位置转换后模型学到的错位类别就毁了整个训练。2.4 拿到数据集先做一次普查类别、尺寸、坏图排查不管数据集描述写得多漂亮拿到手第一件事永远是盘一下家底。写过一堆数据清洗脚本后我的固定流程是这样先统计所有XML里的类别名看有没有拼写变体比如“Fire”和“fire”会被当成两个类再扫描图片尺寸分布看是统一尺寸还是混合尺寸最后检查TXT和XML是不是一一对应有没有某张图只有TXT没有XML或反过来。import os import xml.etree.ElementTree as ET from collections import Counter xml_dir Annotations all_classes Counter() img_sizes Counter() missing_xml [] files os.listdir(xml_dir) for f in files: if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) root tree.getroot() size root.find(size) img_sizes[(int(size.find(width).text), int(size.find(height).text))] 1 for obj in root.iter(object): all_classes[obj.find(name).text] 1 print(类别分布:, all_classes) print(尺寸分布:, img_sizes.most_common(10))类别分布这一步能直接看出数据集的偏斜程度。火焰和烟雾往往不是均衡的如果fire有15000个框smoke只有3000个训练时模型会对smoke严重欠拟合。尺寸分布也重要如果数据集里同时有640x480的图片和1920x1080的图片YOLO的Mosaic增强会自动resize但极端尺寸差异会导致小目标被拉伸变形。坏图排查看两点一是图片能不能被cv2.imread正常读出来二是标注框是否完全在图片范围内。检查完这四样你对这份数据集能不能用于训练心里基本有数了。3. 把数据整理成YOLO能直接train的样子划分脚本和检查手段拿到手的18800张图目录结构大概率是原始标注工具导出的样子JPEGImages、Annotations、labels三个文件夹可能各自为政。YOLO训练要求图片和TXT放在同一级目录或者通过txt路径文件指定所以第一步是把数据整理成标准布局。3.1 统一类别编号把XML里的类名映射成TXT序号最稳妥的做法是直接用XML作为唯一数据源因为XML里有人能读的类名字符串TXT里只有数字ID。如果你拿着一份TXT训练根本不知道ID 1到底是烟雾还是火焰万一数据集的类别定义和你预期不一致模型训练出来就是个黑匣子。我从XML统一生成TXT映射表用配置文件管理保证全流程可追溯。import os import xml.etree.ElementTree as ET CLASS_MAPPING {fire: 0, smoke: 1} xml_dir Annotations label_dir labels os.makedirs(label_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) 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.strip() if name not in CLASS_MAPPING: print(f警告: {xml_file} 包含未知类别 {name}) continue cls_id CLASS_MAPPING[name] 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) 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}) out_path os.path.join(label_dir, xml_file.replace(.xml, .txt)) with open(out_path, w) as f: f.write(\n.join(lines))这段脚本的逻辑是遍历所有XML把类名字符串映射成数字ID再按归一化公式写TXT。CLASS_MAPPING是你唯一需要手动维护的地方建议在项目里放一个classes.yaml管理映射关系不要写死在脚本里。我额外加了一个未知名告警目的是暴露数据里的脏类别比如有些人把“smoke”写成了“smog”不告警的话这个类会被静默丢掉你还以为数据是全的。3.2 划分train/val并生成路径清单YOLO训练不看文件夹结构它只看你给的txt文件里写了哪些图片路径。常见做法是把18800张图按8:1:1或9:1划分成训练集和验证集划分时注意不要让同一场景的连续帧全落进训练集。数据集中如果有视频抽帧得到的序列图顺序划分会导致验证集和训练集过于相似评估指标虚高。用随机划分并配合文件名前缀做去重是一种可行策略。import os import random image_dir JPEGImages train_ratio 0.9 images os.listdir(image_dir) random.shuffle(images) train_files images[:int(len(images) * train_ratio)] val_files images[int(len(images) * train_ratio):] def write_path_file(file_list, output_path): with open(output_path, w) as f: for img in file_list: stem os.path.splitext(img)[0] img_path os.path.abspath(os.path.join(image_dir, img)) label_path os.path.abspath(os.path.join(labels, stem .txt)) if os.path.exists(label_path): f.write(img_path \n) else: print(f跳过无标注图片: {img}) write_path_file(train_files, train.txt) write_path_file(val_files, val.txt)train.txt和val.txt里存的是图片绝对路径YOLO训练时会自动根据图片路径找同名的TXT文件。划分比例是训练里最常调的参数数据量够大时9:1够用数据量紧巴巴时8:1甚至7:2:1更稳。划分后最好统计一下验证集里每个类别的框数量如果某个类别没进验证集那一轮验证指标就会失真。这一步很多开源项目不会提醒你但这是数据工程师的基本素养。3.3 可视化和合法性双重校验TXT生成完毕、数据划分完毕还不能直接开训。把标注框画到图片上用肉眼过几批图是最便宜也最有效的质检手段。我之前有一次就是没做这一步训了50个epoch才发现类别0和类别1的框全反了——因为XML里的name顺序和我的CLASS_MAPPING不一致。import cv2 def draw_yolo_labels(image_path, label_path): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path, r) as f: for line in f.readlines(): parts line.strip().split() cls_id int(parts[0]) cx, cy, bw, bh map(float, parts[1:]) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) color (0, 0, 255) if cls_id 0 else (0, 255, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, fcls{cls_id}, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow(check, img) cv2.waitKey(0) cv2.destroyAllWindows() draw_yolo_labels(JPEGImages/fire_001.jpg, labels/fire_001.txt)可视化校验看两样东西框的位置是否贴合目标边缘类别标注是否和图像内容匹配。框偏大或偏小会影响IoU计算和NMS后处理训练时模型会学到偏移的框中心推理时定位精度就差。合法性校验则偏数值层面检查TXT里的cx, cy是否在0到1之间w, h是否为正数以及有没有某一行数据只有4个字段。这类脚本建议整理成batch模式跑完整数据集而不是单张检查。4. 用这18800张图训一个能用的检测模型参数配置与验证数据准备好了接下来就是训练。行业里最常用的框架是YOLOv5和YOLOv8两者都吃同样的数据格式。下面给的是一套针对火焰烟雾场景的默认配置涵盖了数据yaml、训练命令和关键超参数,以及怎么看训练日志。4.1 数据yaml和训练参数的一次设置YOLO训练前需要写一个data.yaml文件里面是三组核心信息train路径、val路径、类别列表。路径可以是绝对路径也可以是相对于项目根的路径。类别列表必须和TXT里的类别编号顺序完全一致第一行对应ID 0第二行对应ID 1以此类推。train: /data/fire_smoke/train.txt val: /data/fire_smoke/val.txt nc: 2 names: [fire, smoke]这里有一个高频翻车点nc必须等于names列表的长度且与TXT里的最大类别编号加1一致。如果TXT里出现了类别ID 2但nc只写了2训练会直接报错或者静默忽略ID 2的框。训练命令的话一般这样起yolo train datadata.yaml modelyolov8s.pt epochs120 imgsz640 batch16 patience20 device0或者用YOLOv5的写法python train.py --data data.yaml --weights yolov5s.pt --batch-size 16 --img 640 --epochs 120 --patience 20参数意思很直白model指定预训练权重epochs是迭代轮次imgsz是训练分辨率batch-size是每批图片数patience是早停的耐心值device选GPU编号。火焰烟雾检测任务里imgsz建议至少640如果目标是小火苗或远距离烟雾直接上1280。显存紧张时优先降batch而不是降imgsz因为小目标在低分辨率下很容易丢。4.2 训练中看哪几个指标loss、P、R、混淆矩阵训练过程中不要只看loss曲线更要关注验证集上的mAP和混淆矩阵。YOLOv8训练时会在终端打印每个epoch的Box P、R、mAP50、mAP50-95训练结束后自动生成confusion_matrix.png。火焰烟雾这类任务P和R的取舍要看业务场景消防预警宁可误报也不漏报那就把confidence阈值调低、接受低P如果是巡检告警系统误报太多会让人疲劳那就要把阈值调高。混淆矩阵是必看的图。YOLO生成的混淆矩阵里对角线越亮越好非对角线的格子如果集中在背景类说明模型在把火焰或烟雾误判成背景这往往是负样本不足的表现。另外注意一个细节YOLO混淆矩阵的横纵轴包含background类别如果background那一行出现了明显的亮块说明模型的FP很高。如果训练时loss曲线在最后几个epoch反复震荡不下降通常是学习率没配合上可以试试cosine LR scheduler或者把初始lr调低一个量级。如果模型在训练集上mAP很高、验证集上mAP很低那不用怀疑过拟合了。火焰烟雾数据的纹理特征相对简单模型很容易背下训练集的背景模式解决方法是加数据增强、调高dropout或者直接换更小的模型。4.3 过拟合和欠拟合的判断epochs和patience怎么调训练轮次不是越多越好。18800张图不算少但火焰烟雾目标的相似度很高模型在40到60个epoch后通常就能收敛到稳定水平。我用patience20早停意思是20个epoch内验证集mAP没有提升就自动停止避免无脑跑满120个epoch浪费时间。看训练日志时如果发现val/box_loss在epoch 50后反而开始上升而train/box_loss还在下降这就是过拟合的经典信号应该立刻停掉回退到mAP最高的那个checkpoint。欠拟合的表现则是train和val的loss都高、P和R都低这时候优先检查数据而非模型比如标注是否有大量漏标、类别分布是否极端不平衡。5. 火焰烟雾数据集的避坑记录四个坑让我返工了三次这部分是拿真金白银换来的血泪经验。数据集从下载到训练出可用模型中间踩过的坑按频率排序基本就是下面这四个。5.1 坑一类别编号对不上训练全程在学错误映射现象训练日志里loss一直在降但验证集mAP始终在0.3左右徘徊怎么调参数都没用。把训练集里某张图的标注框可视化出来发现fire和smoke的框画反了。原因数据集提供的TXT里类别ID是1和2但我写的classes映射是0和1。YOLO读取TXT时按ID索引类别名ID错位后模型学到的语义全乱。解决不要直接用数据集给的TXT训练统一从XML重新生成TXT。XML里的类名是字符串人眼可读不容易产生歧义。训练前随机挑20张图做可视化检查确认类别ID和语义标签一一对应再开训。5.2 坑二XML里的bndbox越界训练直接崩现象训练过程中某个epoch突然报错提示index out of bounds或者有一条标注的box宽高为负数。有时候不报错但训练出来的模型在推理时框的位置明显偏移。原因原始XML标注是从别的工具导出的部分框的xmax大于图片宽度或ymin小于0。转换脚本没做边界检查把这些非法框原样写进了TXT。解决在XML转TXT的循环里加上边界裁剪或过滤。我习惯用max/min做裁剪而不是直接丢弃因为有些框只是右边界越了几个像素裁剪后仍是有效标注。xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) if xmax xmin or ymax ymin: continue加完这四行后再算归一化坐标能挡掉绝大多数脏标注。另外如果XML里出现cty这类标签名变体用root.iter(object)而不直接find容错性更好。5.3 坑三负样本不足白天误报率奇高现象模型在测试集上mAP不错但拿到监控视频里一跑晴天白天的误报率超过30%云朵、反光、白墙全被识别成烟雾。原因数据集的18800张图几乎全是正样本也就是每张图里都有火焰或烟雾目标没有纯背景负样本。模型没学过什么是“没有火”自然把一切亮色块当目标。解决从实际监控视频里抽几千张背景帧标注成空TXT文件混入训练集。空TXT就是没有任何标注行的文件模型会把它当作无目标图像参与训练。我一般按正负样本3:1的比例混入负样本。注意负样本来源要和部署场景贴近比如你的系统装在化工厂那负样本里就要有烟囱、蒸汽、灯光这些容易误报的东西。这一步没有标准答案全靠场景数据积累。5.4 坑四图像尺寸混乱导致小目标被Mosaic吞掉现象训练时loss正常但推理时小火焰目标检测率极低框的定位也偏。检查发现数据集的图有640x480和1920x1080两种尺寸而训练固定把图resize到640x640。原因Mosaic增强会把四张图拼成一张小目标在拼接后进一步缩小再经过resize直接缩到几个像素以内。火焰数据集里大量近景小火苗是重要的框被增强策略吞掉后就等于白标了。解决最直接的做法是把所有训练图统一resize到合适的短边比如1280x720再在训练时用imgsz1280。YOLO的letterbox会自动补边不会拉伸变形。如果显存不够跑1280那就把原图短边缩到640同时用mosaic0关掉拼接增强只保留随机仿射变换。另一个技巧是把数据集中所有宽高比大于2:1的图单独筛出来看是不是全景拼接图这种图的标注往往只覆盖局部区域需要人工复核。6. 把火焰烟雾模型做到能上线增强、剪枝和验证技巧训出mAP 0.8的模型不稀奇难的是在真实环境里稳定工作。最后一章写几个我平时最常用的进阶手段。火焰烟雾场景的专属数据增强不要迷信Albumentations全家桶。旋转和翻转是最安全的但色彩抖动和HSV增强要慎用因为火焰的橙色和烟雾的灰色是重要特征你把色调一调烟火特征就变了。一个有意思的增强是MixUp把火焰图片和一张背景图按比例融合相当于让模型学习目标被遮挡时的特征。光照增强只加亮度变化不做颜色反转。烟雾的形态变化剧烈水平翻转等于生成了一对新样本这对烟雾检测很有效。增强参数放在YOLO的hyp.yaml里调mosaic和mixup的开启比例建议各设0.3左右不要太高。推理端的验证技巧上推荐用滑动窗口跑长视频。把视频帧切成带重叠的patch分别推理再合并结果可以显著提升小火焰的召回率。窗口大小一般是训练分辨率的1.5倍重叠率30%。代码层面就是普通的numpy切片加循环推理没什么玄学但能实打实补上小目标漏检。验证时不要在标注过的测试集上反复调阈值那是自欺欺人。留一套完全没参与过训练和验证的现场视频按真实场景的帧率抽帧跑一遍推理统计误报数和漏报数才算真正验证了模型在部署环境中的可用性。剪枝推荐用模型自身的结构化剪枝去掉对输出影响最小的通道FLOPs能压掉30%以上。火焰烟雾检测部署在边缘设备上时剪枝后微调20到30个epoch精度可以回到剪枝前的95%。我一直保留一个习惯把每轮实验的数据yaml和超参数记在Git提交信息里这样模型出问题时知道自己做了什么改动。这个习惯救了我很多次模型效果变差时不用靠猜。希望帮到你。本文还有配套的精品资源点击获取