简介:面向火灾烟雾检测与YOLO目标检测实践者,这份已标注数据集包含3600张真实场景图片,覆盖不同光照条件、烟雾密度、火灾阶段与监控视角,可支撑目标检测模型的训练、验证和调优。压缩包共2000个文件,其中1996个XML文件逐一记录目标的类别标签与边界框坐标,4个TXT文件提供训练集、验证集和测试集的路径清单,整体约244.77MB,结构清晰,能较方便地接入YOLO系列训练流程。已有209人学习,适合具备Python基础和YOLO概念的研究人员、算法工程师以及从事公共安全、工业安全、住宅安全监控的开发者。借助完整标注与现成数据划分,使用者可减少人工标注和整理时间,快速完成图片预处理、模型选择、训练评估与测试,并把模型部署到实时监控场景中,提升火灾烟雾识别的准确性与响应速度。
1. 这个数据集到底能干什么:先看清 3600 张标注图的真实价值
做火灾检测的第一道坎往往不是模型选型,而是数据。公开的烟雾火灾图不少,但大多散落在各个图片来源网站里,没有统一格式,没有类别标签,PNG、JPG、BMP 混在一起,光清洗就要耗掉两三天。这时候一份「已标注」的火灾烟雾数据集就变成了最稀缺的资源:干训练的人拿到手不是去标框,而是直接划分数据集、写配置文件、跑训练。yolov11火灾烟雾数据集(已标注)3600张图片这类资源,本质上是把数据处理环节压缩到了最短,让从业者把精力全部留给训练和调参。
这里要认清一个现实:3600 张对深度学习检测任务来说不大不小,单卡个人项目完全够用,场景简单时能训到不错的 mAP;但视角单一、光照多变时,这个数量容易在夜间和远距离小目标上露馅。所以它更适合验证算法流程、做原型系统、参加竞赛练手类场景,而不是不用任何数据增强就能直接商用的免检方案。读者拿到这份资源后,真正要做的事是三步:摸清标注格式和类别构成、整理目录结构、训练并查漏补缺。这篇笔记就是为了把这三步的可复现细节全部写透,包括坑在哪、参数怎么调、怎么判断模型是真学到火灾特征而不是过拟合了背景。
2. 先摸清数据集的底细:格式、类别与标注质量,决定你能不能直接开训
2.1 火灾烟雾数据集最常见的三种来源形态
市面上标注好的火灾烟雾数据集,最常见的交付形态是三种。第一种是纯 YOLO 格式:每张 JPG 配一个同名 txt,txt 里每行是 class_id、x_center、y_center、width、height,坐标基于图片宽高做了归一化。第二种是 VOC XML 格式:每张图配一个 XML,里面存 object 的 name 和 bounding box 的 xmin、ymin、xmax、ymax,单位是像素。第三种是 COCO JSON 格式:所有标注汇总到一个 JSON 文件里,适合直接用 pycocotools 做评估。如果你拿到的版本是第一种,那基本可以直接进入划分环节;拿到后两种,就要先做转换。
拿到资源后的第一个动作千万别是急着训练,而是进目录把图片和标注文件的命名规则对一对。常见命名不一致有大写和小写后缀混用、图片是 .jpg 但标注是 .JPG.txt、标注文件多了空格前缀,这些都会导致训练时标签全部丢失。我一般会先写一条 shell 命令扫一遍文件数量对不对得上:
ls images | wc -l ls labels | wc -l # 再检查是否有图片但无标注、或有标注但无图片,用 comm 命令做差集 comm -3 <(ls images | sed 's/\.[^.]*$//' | sort) <(ls labels | sed 's/\.[^.]*$//' | sort)这段命令的逻辑是先分别统计图片和标注文件数量,再把两侧文件名去掉扩展名后排序对比,第三个输出内容就是两边对不上的文件。对不上的文件如果超过个位数,后面训练出来的模型类别数都是虚的,mAP 会莫名偏低。
2.2 从 VOC XML 转 YOLO 标注:转换脚本与四个边界坑
如果这份数据集是以 VOC XML 形式交付的,你需要先转成 YOLO 格式。这个转换看起来是一道除法,实际上有四个边界坑:一是 XML 里的 width 和 height 有时没写,需要从图片头读取;二是坐标存在 xmin 大于 xmax 或 ymin 大于 ymax 的脏数据;三是 bounding box 超出图像边界;四是类别字符串大小写不统一,比如 Fire 和 fire 被当成两个类别。下面这个脚本是常见做法的浓缩版:
import os import xml.etree.ElementTree as ET from PIL import Image def xml_to_yolo(xml_path, img_path, class_map, out_txt): tree = ET.parse(xml_path) root = tree.getroot() with Image.open(img_path) as img: img_w, img_h = img.size lines = [] for obj in root.iter('object'): name = obj.find('name').text.strip() if name not in class_map: continue # 未知类别直接跳过,避免污染 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) # 边界修正:把越界坐标裁剪回图像范围内 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.0 / img_w cy = (ymin + ymax) / 2.0 / img_h bw = (xmax - xmin) / img_w bh = (ymax - ymin) / img_h lines.append(f"{class_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(out_txt, 'w') as f: f.write('\n'.join(lines))这段代码的关键点在于先读图片真实宽高,而不是信任 XML 里可能缺失或错误的尺寸信息;同时对 xmax、ymax 做了裁剪修正。class_map 是你自己维护的类别编号映射,比如 {"fire": 0, "smoke": 1},后续训练出的类别顺序就由它决定。转换完成后,用前面那条 shell 命令再核对一次文件数量,别急着删 XML。
2.3 标注质量体检:类别分布与疑似无效标注的快速统计
数据集的名称虽然写着已标注,但标注质量仍需自己验证。最常见的问题是类别极端不均衡:烟雾类别占了 85%,火焰只有 15%,这种分布会让模型对火焰类别的召回率很低。另一个问题是小目标标注框过小,width 或 height 归一化后小于 0.01,这类框在 YOLO 训练里几乎学不到特征。用一条 Python 脚本可以快速统计出这两个指标:
import os from collections import Counter label_dir = 'labels' class_counter = Counter() tiny_boxes = 0 total_boxes = 0 for txt in os.listdir(label_dir): with open(os.path.join(label_dir, txt), 'r') as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls = int(parts[0]) w = float(parts[3]) h = float(parts[4]) class_counter[cls] += 1 total_boxes += 1 if w < 0.01 or h < 0.01: tiny_boxes += 1 print('类别数量分布:', class_counter) print('总框数:', total_boxes, '疑似极小框占比:', round(tiny_boxes / total_boxes, 4))这个统计脚本应该是你拿到任何标注数据集之后要做的第一件事。如果极小框占比超过 5%,后续训练时要格外关注小目标召回率,或者考虑去掉这些垃圾标注;如果类别严重不均衡,则要在 loss 权重或采样策略上做文章,而不是直接开训。先把画像摸清楚,后面才能判断指标异常是模型问题还是数据问题。
3. 训练前准备:数据划分为三类、目录组织方式与 data.yaml 的写法
3.1 目录结构:YOLOv11 默认按 images 和 labels 同级读取
Ultralytics YOLOv11 的默认数据加载逻辑是 images 目录和 labels 目录同级,且文件名保持一致的相对关系。一个可靠的标准结构是这样:
datasets/fire_smoke/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/很多人习惯把整个数据集放在 train 里,只划 5% 做 val,这种做法在 3600 张的小规模数据上很危险:验证集太小,mAP 波动大,一个批次里的偶然误差就让你误判模型好坏。我一般建议训练集、验证集、测试集按 8:1:1 划分。测试集单独留出来的目的是做最终模型行为确认,验证集用于每个 epoch 之后的早停判断,两个集合的用途不能混,否则后期的调参全都是在测试集上作弊。
3.2 划分脚本:按文件粒度打散,避免同一场景的连续帧串组
划分工作的核心不是数量比例,而是把数据按照文件粒度均匀打散。如果原始图片里同一场景连续几十帧,切割时用了顺序切片,就会导致训练集和验证集出现同一场景的相近画面,验证指标虚高。我常用的划分脚本如下:
import os import random from shutil import copyfile random.seed(42) img_dir = 'images_all' label_dir = 'labels_all' train_ratio, val_ratio = 0.8, 0.1 imgs = [f for f in os.listdir(img_dir) if f.endswith('.jpg')] random.shuffle(imgs) n_train = int(len(imgs) * train_ratio) n_val = int(len(imgs) * val_ratio) for i, img in enumerate(imgs): name = img[:-4] lbl = os.path.join(label_dir, name + '.txt') if not os.path.exists(lbl): continue if i < n_train: sub = 'train' elif i < n_train + n_val: sub = 'val' else: sub = 'test' copyfile(os.path.join(img_dir, img), f'images/{sub}/{img}') copyfile(lbl, f'labels/{sub}/{name}.txt')这段代码设置了随机种子,保证每次运行得到同样的划分结果;循环里跳过没有标注文件的图片,这对脏数据是一个有效过滤。需要说明的是,最好的做法其实是按场景或视频片段分组后再划分,但多数开源数据集不提供分组元数据,退而求其次就是加大随机打散的力度,让同类场景尽量均匀分布在三个子集里。
3.3 data.yaml 的写法:路径用绝对还是相对?类别顺序不能乱
YOLOv11 通过一个 YAML 文件告诉训练器数据在哪、类别有哪些。常见写法如下:
path: /home/user/datasets/fire_smoke train: images/train val: images/val test: images/test nc: 2 names: 0: fire 1: smoke这里有个容易被忽略的细节:path 字段如果是相对路径,它是相对于 YAML 文件所在位置解析的,不是相对于当前终端的目录。为避免各种玄学路径错误,我一般直接写绝对路径。另一个细节是 names 的索引顺序必须和之前转换 txt 标注时使用的 class_id 保持一致,一旦顺序错位,训练不会报错,但 mAP 统计里的类别名称张冠李戴,最后部署时才发现,返工成本很高。
4. 跑通 YOLOv11 火灾烟雾训练:命令、关键参数与日志指标的判读
4.1 最小训练命令:从预训练权重开始,别从零初始化
一切准备就绪后,训练本身反而最简单。用 Ultralytics 的命令行工具,最小可跑命令如下:
yolo detect train \ data=datasets/fire_smoke/data.yaml \ model=yolo11s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20 \ project=runs \ name=fire_smoke_exp这里的 model 填的是预训练权重文件 yolo11s.pt,而不是模型结构 YAML。这么做的意义是借助 COCO 上预训练过的特征提取能力,让模型在火灾烟雾这种小数据集上快速收敛,而不是从零开始学习底层纹理和边缘特征。对于 3600 张的数据规模,s 这个体积级别性价比最高;n 更快但精度天花板低一点,m 在单卡上训练速度就明显慢下来。
4.2 关键参数调整:imgsz、batch 与数据增强的默认值陷阱
imgsz 是第一个要动的参数。640 是默认值,但火灾烟雾数据里如果小目标居多,建议上调到 768 或 1024,小目标检测的召回率会有可见提升。代价是显存占用增大,batch 就得相应调小。这里给出两个常见组合的参考方向:如果单卡 24G 显存,1024 输入时 batch 8 稳妥;640 输入时 batch 16 到 32 都可以试。batch 太小会出现 BatchNorm 统计不稳定,精度震荡明显;我一般不会让 batch 低于 8。
数据增强方面,Ultralytics 默认开启的增强套件对白天室外场景友好,但火灾烟雾样本里如果大量是夜间监控画面,默认的色域增强 hsv_h、hsv_s 会把本来就暗的画面变得更难分辨。遇到这类情况,我会在训练命令里追加显式参数压低增强强度:hsv_h=0.01、hsv_s=0.2、degrees=5。另外一个实际经验是开启 mosaic 增强在小数据集上收益明显,但最后 20 个 epoch 建议关闭,让模型在接近真实分布的单图数据上做最后微调。关闭方式是把 mosaic=0.0 加到命令里,用单独的 last_epoch 阶段重新训练。
4.3 训练过程中重点盯哪些指标:不要只盯着 mAP50 看
终端里的训练日志每行会输出 GPU 显存占用、box_loss、cls_loss、dfl_loss、mAP50、mAP50-95 等参数。多数人只关心 mAP50,但这容易造成误判。真正反应训练是否健康的是前三个 loss 是否同步下降、不再出现周期性反弹;然后才是 mAP 曲线平不平稳。当 mAP50 上升但 mAP50-95 停滞,说明模型只学到了粗定位,框的精度不够;当 loss 持续下降但 mAP 不涨,要怀疑是否过拟合或标签噪声过大。
操作上,训练完成后不要只靠系统给的最后结果,而是去 project 目录下看 confusion_matrix.png 和 results.png。混淆矩阵能清晰看出 fire 与 smoke 之间的互相误检情况,这两个类别在大场景下本来就容易被交叉。后面我会专门写一节验证方法,把这一步展开讲。
5. 避坑:训练火灾烟雾模型最常遇到的 5 个问题,现象与根源一并说清
5.1 现象:训练启动即报错 “All labels are empty” 或训练 loss 为 NaN
背景原因:目录结构里 images 和 labels 没有保持同名同级关系,或者 data.yaml 里的 path 写错指向了空目录。排除方法:先确认 labels/train 下 txt 文件数量与 images/train 下图片数量接近,再随机打开一个 txt 看看里面是不是真的有坐标数据。还有一个容易被忽视的细节是某些数据集打包时把标注写成了 UTF-8 BOM 编码,txt 首行带不可见字符,解析时 line.split() 后第一个元素不是纯数字,程序直接抛异常。解决方式是先批量转码,统一去掉 BOM 头:
find labels -name '*.txt' -exec sed -i '1s/^\xef\xbb\xbf//' {} \;5.2 现象:训练正常但 val 的 mAP 在某个 epoch 后突然大幅掉点
背景原因:这种断崖式下降通常不是模型问题,而是验证集划分不当。如果 val 集合里恰好包含某个拍摄场景的连续帧,训练集里也出现了同一场景的相似画面,模型在训练时已经见过这些画面的变体,初始验证指标虚高。等训练到后期开始学习更细节的纹理模式时,验证集与训练集的场景重叠优势消失,指标就掉下去。排查思路:去 val 里随机抽几张图,看看它们在原始数据目录里的相邻文件是不是也出现在 train 里。如果是,重新用随机种子做一次全局划分,建议把同一目录或同一命名前缀的样本归为同一场景组,整组进入同一子集。
5.3 现象:训练不报错,但模型对烟雾的检测效果远差于火灾
背景原因:烟雾本身就是半透明、无规则边界的目标,标注框的边界比其他物体更模糊,人工标注一致性差。加上 3600 张里如果烟雾样本远少于火灾样本,模型天然偏向学习形状更清晰的火焰特征。两条解决路径可以并行:一是在 data.yaml 里通过设置每个类别的损失权重提升烟雾类别对 loss 的贡献;二是在预处理阶段对烟雾样本做随机平移、旋转、缩放增强,人为扩充该类别的形态多样性。如果烟雾类别数量不足火灾类别的三分之一,先考虑补充数据,靠权重硬拉的效果有限。
5.4 现象:小目标烟雾完全漏检,但大目标检测效果极好
背景原因:YOLO 各检测头负责不同尺度的目标,浅层特征图负责小目标,深层特征图负责大目标。当输入分辨率过低、或者训练时被自动缩放过狠时,小目标的特征在浅层特征图里只占几个像素,无法有效激活锚点。解决办法有三个方向:把 imgsz 从 640 提到 896 或 1024;训练命令里减少 scale 数据增强的幅度,避免目标被缩得太小;如果数据集中确实存在大量极小目标,则要重点观察小目标类别在 val 集上的分尺寸 AP 曲线,确定是样本量不足还是分辨率不足,再对症处理。
5.5 现象:训练和验证指标都正常,但部署到实际视频流中误检满天飞
背景原因:数据集里的图片大多是清晰、正视角、环境相对干净的样本,而实际监控摄像头是俯视角且存在大量树叶晃动、汽车灯光、工地扬尘。模型在训练集上没见过这些负样本,自然会对这类背景产生误检。解决思路不是反复调参,而是收集目标部署环境里 1 到 2 小时的负样本视频帧,以背景图的方式加入训练集;如果不想改标签体系,可以把这些图片复制出来放到 images/train 下,labels 目录里放空 txt,模型就会学会把这些区域当作背景。这一步是模型能否落地的分水岭,调参救不了数据缺口,只能靠喂背景。
6. 模型验证与落地:从 mAP 数据到可视化预测,再到导出部署的完整闭环
训练结束后,先别急着导出模型。用测试集做一次独立验证,你才知道模型的真实水平:因为验证集已经参与过早停判断和调参过程,测试集的指标才有统计意义。用下面的命令做一次干净的 valid 评估:
yolo detect val \ model=runs/fire_smoke_exp/weights/best.pt \ data=datasets/fire_smoke/data.yaml \ split=test \ imgsz=896 \ conf=0.25 \ save_json=True这里的关键参数是 split=test,强制使用 data.yaml 里 test 字段指定的数据。如果想精确看 fire 和 smoke 各自的 AP,加上 save_json=True 后生成的结果文件里会有每个类别的详细 AP 数据,不要再只看平均值。另一个实用操作是生成测试集的可视化预测结果,把模型输出的框画在图上,你需要看的是那些置信度在 0.25 到 0.5 之间的预测框:如果这个区段里大量是错误框,说明阈值不能设太低;反过来,如果这个区段的正确框被滤掉了,就需要调低阈值,同时负样本处理要做好。
模型进入到部署环节时,我记得有一次把训练好的权重直接搬到另一台没有 GPU 的机器上做推理,性能下降明显。后来换成导出 ONNX 格式并在推理机上依赖 OpenVINO 或 ONNX Runtime 执行,延迟才回到可接受范围。导出命令很简单:
yolo export model=runs/fire_smoke_exp/weights/best.pt format=onnx imgsz=896导出的 ONNX 模型可以脱离原有训练框架运行,适合嵌入到监控服务或边缘设备中。这里给一个实用建议:导出前把 imgsz 固定成训练时一致的分辨率,不要在导出后再随意改,否则模型的精度会下降。我在这个数据集上血泪经验就是:模型验证时 mAP 看着不错,但因为导出的分辨率不一致,实际部署效果打了七折。所以固定好输入尺寸、把阈值校准到可视化预测结果可接受的水平,再进部署流程,这套模型才算真正落地。3600 张数据集的上限就这样逐步被验证清楚,如果最终精度仍不满足业务需要,下一步就该定向补充夜间、远距离、多视角样本,而不是继续调参。希望这些排查路径和参数经验能帮到你少走几趟弯路。
本文还有配套的精品资源,点击获取