简介这是一份面向烟火检测目标任务的已标注数据集包含1000张JPEG原始图像和1000个XML标注文件共2000个文件压缩包大小约89.87MB可直接用于YOLO v3、YOLO v5等主流目标检测模型的训练与评估。图像样本覆盖不同光照条件、背景环境和烟火形态XML标注文件记录每张图中烟火目标的边界框及类别位置信息方便接入TensorFlow、PyTorch等常见深度学习框架做数据加载与模型微调。该数据集既适合刚接触目标检测的初学者作为练习数据也适合有项目需求的研发者作为扩充样本、验证算法效果的基准数据。数据集结构简单标注信息完整可在烟火识别、安防监控等真实场景中帮助提升模型对细小目标和复杂背景的鲁棒性。目前已有3245人学习下载适合作为烟火检测项目起步或实验对比的现成数据基础。1. 烟火检测数据集到手先别急着训练先摸底做烟火检测落地十有八九的团队都卡在数据上——自己采集烟火图片一两周拍不到几个有效场景网上爬图版权和标注质量全靠运气找标注公司起步量小根本没人接。这份打包好的烟火检测数据集1000张已标注图片目标检测YOLO系列可直接使用恰好补上的是整个工作流里最脏最累的一环数据准备。1000张听起来不算多但烟火检测属于大目标场景火焰和烟雾通常占据画面显著区域背景相对固定这类任务对数据量的需求远低于车辆检测或行人检测。用YOLOv8或YOLOv5训练在1000张图上完全能收敛到可验证算法的水平。适合两类人刚接触目标检测、想快速跑通烟火检测全流程的开发者以及做智慧消防、安防预警DEMO、需要先证明方案可行性的工程师。数据集是死的但怎么查它的标注质量、怎么转成YOLO格式、怎么避掉训练里的暗坑才是这份资源真正值钱的地方。2. 数据集摸底目录结构、标注格式与图片质量检查2.1 解压后先看目录结构XML还是TXT一眼定生死拿到压缩包先别急着写训练脚本。我一般习惯先解压用一条命令扫一眼整体结构unzip 烟火检测数据集11000IMG已标注.zip -d fire_smoke_dataset cd fire_smoke_dataset find . -maxdepth 2 -type d | sort默认规划目录如下fire_smoke_dataset/ ├── images/ # 1000张原始图片 ├── annotations/ # 标注文件通常为VOC XML或YOLO TXT ├── classes.txt # 类别列表 └── README.md # 说明文档打开一个标注文件看它到底是哪种格式ls annotations | head -5 cat annotations/fire_001.xml如果输出里能看到object、bndbox、xmin这些标签那就是VOC格式的XML标注。VOC格式好在人类可读坏处是YOLO系列训练不能直接用——YOLOv5/v8要求标签是归一化的txt纯文本每行一段、四个归一化坐标加一个类别ID。这一步在动手训练前必须搞清楚因为数据集里夹杂的多半是VOC XML不是现成的YOLO txt。另外留意classes.txt内容正常是两行fire和smoke这个顺序就是后续YOLO的类别ID基准千万别改。2.2 核对标注框图像与标签是否对得上下载资源最怕标注和图像错位——图里是火焰XML里标的是烟囱。我拿到这种数据集的第一件事是从1000张里随机抽20张把标注框画回原图人工看一眼。用下面这个脚本import os import cv2 import xml.etree.ElementTree as ET image_dir images annot_dir annotations sample_names [fire_001, fire_002, fire_003] # 换成实际文件名前缀 for name in sample_names: img cv2.imread(os.path.join(image_dir, name .jpg)) xml_path os.path.join(annot_dir, name .xml) tree ET.parse(xml_path) root tree.getroot() for obj in root.findall(object): cls obj.find(name).text box obj.find(bndbox) x1 int(float(box.find(xmin).text)) y1 int(float(box.find(ymin).text)) x2 int(float(box.find(xmax).text)) y2 int(float(box.find(ymax).text)) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, cls, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.imwrite(fcheck_{name}.jpg, img)这段脚本的逻辑遍历指定的几张图读XML里每个目标的类别和边界框用OpenCV画在原图上输出成检查图。cv2.rectangle里的颜色是BGR顺序(0, 0, 255)是红色对应火焰标注烟雾可以改用(255, 0, 0)区分。参数上唯一要留意的是int(float(...))这层转换——有的XML导出工具会把坐标写成123.0直接int()会报错先转float再转int最稳。2.3 类别分布统计火和烟的比例可能出乎意料标注质量抽查通过后还要看类别分布。烟火检测数据集最常见的坑是类别比例失衡火焰目标明显、标注率高烟雾半透明、边界模糊标注员容易漏标。跑一下统计import os import xml.etree.ElementTree as ET from collections import Counter annot_dir annotations counter Counter() for xml_file in os.listdir(annot_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(annot_dir, xml_file)) root tree.getroot() for obj in root.findall(object): cls obj.find(name).text counter[cls] 1 total sum(counter.values()) for cls, cnt in counter.items(): print(f{cls}: {cnt} 张占比 {cnt / total:.1%})如果输出是fire: 780, smoke: 420这种悬殊比例不要慌这是烟火检测数据的常态。后面训练时处理手段很多可以用class_weight给少数类加权重也可以用mosaic增强时对smoke做过采样。最怕的是完全没意识到这个问题训练出来模型「只认火、不认烟」部署到现场一测全是漏报。另外顺手检查下图片尺寸是否统一——如果images目录里同时有1920x1080和1280x720的图YOLO训练时自动做letterbox问题不大但如果混着竖向构图720x1280就必须在训练配置里留意后面章节细说。3. 把VOC标注转成YOLO格式转换脚本与四个边界坑3.1 为什么要转成YOLO格式YOLOv5/v8训练时标签路径下每个txt文件与每张图片同名内容格式是固定的五元组class_id x_center y_center width height其中坐标全部归一化到01之间。VOC XML存的是绝对像素坐标xmin/ymin/xmax/ymax两者不能混用。很多人在这翻车直接把XML路径写进data.yaml训练报错一脸懵。另外YOLO训练时还会读取每张图对应的txt如果txt数量少于图片数量默认会跳过那些没有标注的图——这其实是YOLO处理背景图的方式但如果XML解析漏了一条等于白白丢掉一张训练样本。所以转换脚本不只是格式翻译还承担着数据完整性校验的功能。3.2 转换脚本VOC XML → YOLO TXT下面这版脚本是我在实际项目中反复改过的加了坐标边界检查和缺失文件回报能应对大多数标注导出工具的「脏数据」import os import cv2 import xml.etree.ElementTree as ET image_dir images annot_dir annotations label_dir labels os.makedirs(label_dir, exist_okTrue) # 类别顺序必须和 data.yaml 保持一致 class_names [fire, smoke] def convert_xml_to_yolo(xml_path, img_path, txt_path): tree ET.parse(xml_path) root tree.getroot() img cv2.imread(img_path) h, w img.shape[:2] lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in class_names: print(f警告未知类别 {cls_name} 在 {xml_path}已跳过) continue cls_id class_names.index(cls_name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 边界裁剪防止标注出界导致归一化后坐标大于1 x1 max(0, min(x1, w - 1)) y1 max(0, min(y1, h - 1)) x2 max(0, min(x2, w - 1)) y2 max(0, min(y2, h - 1)) if x2 x1 or y2 y1: print(f警告无效框 {xml_path}跳过) continue x_center (x1 x2) / 2 / w y_center (y1 y2) / 2 / h box_w (x2 - x1) / w box_h (y2 - y1) / 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)) # 批量转换 for xml_file in os.listdir(annot_dir): if not xml_file.endswith(.xml): continue stem xml_file.replace(.xml, ) xml_path os.path.join(annot_dir, xml_file) img_path os.path.join(image_dir, stem .jpg) txt_path os.path.join(label_dir, stem .txt) if not os.path.exists(img_path): print(f警告图片缺失 {img_path}) continue convert_xml_to_yolo(xml_path, img_path, txt_path) print(转换完成请检查 labels 目录下的 txt 数量是否与 xml 一致)这段代码的逻辑分三层。第一层是类别映射class_names的顺序就是最终训练时的类别IDfire是0smoke是1一旦确定就不要改动。第二层是坐标转换归一化的公式是(x1x2)/2/宽注意先算中心点再除以图像宽高除的顺序不要反。第三层是数据清洗clip操作把越界坐标拉回有效范围x2 x1的判断过滤掉标注员手滑生成的零宽零高框。参数上要说明几点。max(0, min(x, w-1))里的w-1是因为像素坐标从0开始w的位置实际上已经出界了。:.6f保留6位小数足够YOLO使用精度再高反而让txt文件变大没有实际收益。如果某个xml文件里所有框都被过滤掉生成的txt是空文件——这表示该图变成纯背景图YOLO照样会把它当作负样本参与训练不算浪费但你在统计数据时要心里有数。3.3 划分train/val别用random.shuffle裸跑转换完标签下一步是把1000张图划分成训练集和验证集。最省事的做法是random.shuffle后按比例切但烟火检测有个特殊问题相邻帧或同一场景拍摄的图片高度相似如果随机划分同一场景的图可能同时出现在训练集和验证集里验证集的mAP会虚高——模型记住场景而不是学会检测烟火。我一般按文件名前缀分组后再划分import os import random from collections import defaultdict image_dir images grouped defaultdict(list) for img_name in os.listdir(image_dir): if not img_name.endswith(.jpg): continue scene img_name.split(_)[0] # 按前缀分组如 fire_001.jpg - fire grouped[scene].append(img_name) train_files [] val_files [] # 同一个场景的图片要么全进训练集要么全进验证集 for scene, imgs in grouped.items(): random.seed(42) # 固定种子保证可复现 random.shuffle(imgs) split_point int(len(imgs) * 0.8) train_files.extend(imgs[:split_point]) val_files.extend(imgs[split_point:]) with open(train.txt, w) as f: f.write(\n.join(os.path.join(image_dir, n) for n in train_files)) with open(val.txt, w) as f: f.write(\n.join(os.path.join(image_dir, n) for n in val_files)) print(ftrain: {len(train_files)}, val: {len(val_files)})这里把分组键设为img_name.split(_)[0]——如果文件名是scene01_fire_001.jpg这种就改成split(_)[1]核心思路是让同一场景的图片不被拆散。random.seed(42)写进循环里保证每次运行划分结果一致这一步看似不起眼却能在你复现训练结果时省掉大量对账时间。4. 用YOLOv8训练烟火检测模型配置、命令与调参建议4.1 准备data.yaml路径与类别映射标签转换和数据集划分完成后下一步是写data.yaml。这是YOLOv8训练时读取数据集配置的唯一入口写在文件里的路径可以是相对路径也可以是绝对路径但类别列表的排列顺序必须和转换脚本里的class_names完全一致否则训练出来的模型类别全错位。一个标准的烟火检测data.yaml如下path: /path/to/fire_smoke_dataset # 数据集根目录 train: train.txt # 指向训练清单文件 val: val.txt # 指向验证清单文件 nc: 2 names: 0: fire 1: smoke这里path指向数据集根目录train和val可以直接写txt文件路径列表也可以写images/train这种目录路径两种写法YOLOv8都支持。我倾向于用txt清单因为第3节划分时已经生成了完整路径训练时少一层目录拼接排查问题也更直观。nc: 2是类别总数names下的编号必须从0开始连续递增如果中间跳号训练会直接报IndexError。4.2 训练命令与关键超参数YOLOv8的训练命令比较简洁控制台直接跑yolo train \ modelyolov8n.pt \ datafire_smoke.yaml \ epochs100 \ imgsz640 \ batch16 \ patience20 \ device0 \ workers4 \ projectfire_smoke_runs \ nameexp_fire_smoke参数含义逐个说明。modelyolov8n.pt表示用YOLOv8n的预训练权重初始化n是nano版本模型最小、速度最快1000张图的数据量用nano起步最合适精度不够再换s或m规模不要在第一次训练就直接上yolov8x——数据量喂不饱大模型效果反而更差。epochs100是上限实际训练会被patience20截断也就是验证集mAP连续20轮不提升就自动早停正常5070轮就能收敛。imgsz640是训练输入分辨率烟火目标大640完全够用没必要上1280显存消耗翻倍收益却很小。batch16取决于显存大小显存只有8G就降到8下文会细说。训练过程中主要看两个指标box_loss是不是稳定下降以及val/Recall有没有持续爬升。如果loss曲线在训练中期掉头向上多半是学习率过大或数据里有脏标签需要停下来看是不是标注边界出了错。另外workers4在Windows上如果数据读取报错改成workers0这是老生常谈的坑。4.3 显存不够怎么办图像大小与batch的取舍烟火检测的训练硬件往往没那么好很多人的笔记本只有8G显存。我给一个按照显存规模调整的参考组合显存imgszbatch建议模型6G6408yolov8n8G64016yolov8n12G64024yolov8n / yolov8s24G64032yolov8m显存不够时优先降batch而不是降imgsz。因为烟火检测的大目标特性对分辨率并不敏感imgsz416都能跑出可用的mAP但降batch影响的是batch normalization的统计稳定性——batch降到4以下BN层的均值和方差抖动会很厉害。我见过有人在6G卡上把batch2跑了一天一夜loss曲线抖得像心电图结果mAP还不如20轮的早停模型。正确姿势是能保持batch不低于8就保持实在不够就梯度累积accumulate4等效放大batch或者干脆换nano模型。5. 烟火检测训练避坑指南数据、标签与评估的5条真实教训5.1 现象验证集mAP很高视频实测全是误报这是烟火检测项目里最容易踩的坑验证集mAP能到0.85以上一拿到现场视频路边的红色卡车、晚霞、橙色路灯全被识别成fire。原因有两层一是验证集图片里烟火目标又大又明显正样本容易二是训练集里缺少「难负样本」——没有火焰但颜色接近火焰的物体。解决思路是主动往训练集里加负样本从实际场景中截取一批纯背景图不标注任何框放进训练集让模型学会「像火但不是火」的特征。另一个有效手段是时序确认视频检测时连续5帧里有3帧以上都命中同一个位置才算报警单帧误报直接丢弃这个逻辑在部署侧就能实现不需要重新训练。5.2 现象训练loss能降下去但验证集Recall极低烟雾检测的召回率上不去是烟火检测最常见的痛。现象是火焰几乎全检出来了烟雾漏掉大半。细看原因烟雾是半透明目标标注框内的像素对比度极低模型学到的不是形状而是纹理和颜色分布而训练数据里的烟雾往往只在天空背景的单一场景下出现泛化能力弱。我试过有效的两个办法一是数据增强里把hsv_h、hsv_s调大让YOLO的HSV颜色抖动更剧烈逼模型不能只靠颜色判断二是把标注策略调整一下只标浓烟核心区域不要为了框住飘散的淡烟把边界框拉得很大——半透明的淡烟区域反而干扰特征提取。这种做法虽然让标注框变小但模型学到的特征更集中实际召回率反而提升。5.3 现象标签坐标越界YOLO训练直接lossnan训练跑着跑着loss突然变成nan首先怀疑标签坐标越界。典型流程是XML里某个目标的xmax1920但图片实际宽度只有1280归一化后坐标变成1.5YOLO的坐标回归分支直接爆炸。这也是为什么第3节的转换脚本里必须写clip逻辑。如果已经训练到一半才发现止损方案是用脚本全量扫描labels目录下所有txt找出任何坐标大于1.000001或小于0的标签重新转换、重新训练。不要试图中途修改标签接续训练YOLO的缓存机制会让它继续读到脏数据。5.4 现象类别数量悬殊模型只认火不认烟fire: 780, smoke: 420这种比例下训练出的模型对fire的精确率很高、对smoke的召回率很低。原因是YOLO的默认分类损失对多数类天然友好。官方层面的调参手段有几种一是训练参数里加class_weights按类别数量的倒数设置权重二是做smoke的过采样在自定义数据加载器里对smoke样本重复读取。我个人的偏好是第二种——直接在训练集清单里把smoke的图片复制一份简单粗暴但有效。复制时注意图片文件名要改掉后缀加_dup避免训练时读到同名的标签文件覆盖写。5.5 现象训练完发现类别名错位预测时给烟火标成了person这是类别顺序错位的问题。data.yaml里的names顺序必须和转换脚本里class_names的顺序一致。常见翻车场景数据集的classes.txt写的是smoke, fire但转换脚本里class_names [fire, smoke]转换时smoke分到的ID是1、fire分到0而训练时data.yaml的names却用的是0: smoke, 1: fire两边一交错模型输出的ID对应完全错误的类别。防这个坑的办法极其简单训练前跑一个验证脚本读取前5张图的txt标签对比txt文件名对应的原图和XML里的类别名一份一份确认逻辑对上再开训练。6. 模型验证技巧用视频实测和混淆矩阵判断能不能上线训练结束拿到best.pt不要只看验证集指标就下结论把模型拉到真实视频上跑一遍用脚本压测yolo predict \ modelruns/exp_fire_smoke/weights/best.pt \ sourcedemo.mp4 \ conf0.25 \ iou0.45 \ saveTrue \ show_confTrueconf0.25是置信度阈值视频实测时建议从0.25往下探分别看0.15、0.2、0.3三档的误报情况调低conf会漏报变少但误报变多调高则反过来。烟火检测的报警场景宁可误报也不能漏报所以阈值设置通常比通用检测低一到两档。配合show_confTrue把置信度打印在画面上方便统计每个漏报框的置信度分布确定该不该调阈值。验证集指标里最有信息量的是混淆矩阵。训练结束后YOLO会在权重目录生成confusion_matrix.png这张图能把模型在验证集上的所有分类错误可视化。看混淆矩阵重点看两处一是背景列background有没有大量把背景预测成fire或smoke的样本如果有说明训练集负样本不足回到5.1的处理思路二是fire和smoke的互相混淆——烟包着火、火周围有烟这类标注本身就有重叠两者互相误判其实是数据标注问题不影响实际报警逻辑因为只要框内命中任一类别都会触发预警。最后一章我想提醒接线员和算法工程师之间最容易脱节的一点模型输出的是「类别框置信度」但现场报警逻辑会决定最终效果。帧间确认、置信度动态调整、误报白名单这些逻辑在模型之外但决定了烟火检测能不能真正用起来。从那以后我每次交付烟火检测模型都会强制自己走一遍「验证集指标 视频实测 混淆矩阵」三件套实测视频至少跑满10分钟把每一帧的误报都截图记录再回去调阈值或补数据。这套流程虽然费时间但能挡掉9成的线上翻车。希望帮到你。本文还有配套的精品资源点击获取