简介:这份资源是面向目标检测开发者与计算机视觉学习者的YOLOv11火灾烟雾数据集,共3600张已标注图片,可用于训练和测试火灾烟雾识别模型,适用于公共安全、工业监控与住宅安防等实时检测场景。压缩包为zip格式,内含2000个文件,其中1996个xml标注文件与4个txt划分文件,整体约244.77MB,xml对应每张图片的边界框与类别信息,txt则用于train、val、trainval、test等数据集的索引划分,方便直接接入YOLO训练流程。数据集覆盖不同场景、光照条件、烟雾密度与火灾阶段,标注质量较高,能帮助使用者减少数据清洗成本,快速完成图片预处理、模型选择、训练、评估与测试等环节。目前已有209人学习下载,适合具备一定Python基础、希望深入YOLO目标检测实践的研究人员与工程师参考使用。
1. 拿到3600张已标注火灾烟雾数据集,先别急着喂给YOLOv11
你从某个渠道搞到一份“yolov11火灾烟雾数据集(已标注)3600张图片”,解压一看,images和labels两个文件夹整整齐齐,data.yaml也躺在根目录。这时候最容易犯的错,就是直接yolo train data=data.yaml一把梭。我见过太多人这么干,然后mAP卡在0.4上不去,跑来问是不是模型不行。问题往往不在YOLOv11,而在你根本没检查这批标注到底能不能用。
火灾烟雾检测这个场景有它的特殊性:烟雾是半透明、无固定边界的,火焰在不同光照下颜色差异极大,早期火点可能只有十几个像素。3600张这个量级,说多不多,说少不少,刚好卡在“够用但经不起浪费”的区间。如果标注质量差、类别定义混乱、训练集里混入了大量近似重复帧,你后面调再多超参都是白费。这篇笔记就按我实际处理这类数据集的流程走一遍:先验数据、再转格式、然后配环境跑通baseline、最后针对烟雾小目标做优化。目标很明确——让你拿到这份数据集后,能在半天内跑出一个能看的检测结果,并且知道后面往哪个方向改。
2. 数据集验收:3600张已标注图片到底能不能直接用
2.1 先搞清楚标注格式和目录结构
“已标注”这三个字信息量太少了。可能是YOLO txt、COCO json、VOC xml,甚至可能是labelme的json。不同格式决定了你后面要不要写转换脚本。我一般拿到数据集先跑一条命令看目录:
find . -maxdepth 2 -type d | head -30 find . -name "*.txt" | head -5 find . -name "*.json" | head -5 find . -name "*.xml" | head -5如果labels目录下是txt,每个txt里每行是class x_center y_center width height,且值都在0到1之间,那就是标准YOLO格式,最省事。如果发现坐标值大于1,说明是像素坐标,需要除以图像宽高做归一化。如果压根没有labels目录,只有和图片同名的json,那大概率是labelme格式,得转。
常见做法是写一个快速统计脚本,把类别分布、每张图的标注框数量、框的宽高分布都打出来:
import os, glob from collections import Counter label_dir = "labels/train" cls_counter = Counter() box_per_img = [] wh_list = [] for txt in glob.glob(os.path.join(label_dir, "*.txt")): with open(txt) as f: lines = [l.strip() for l in f if l.strip()] box_per_img.append(len(lines)) for line in lines: parts = line.split() cls_counter[int(parts[0])] += 1 w, h = float(parts[3]), float(parts[4]) wh_list.append((w, h)) print("类别分布:", cls_counter) print("平均每图框数:", sum(box_per_img)/len(box_per_img)) print("空标注图片数:", box_per_img.count(0)) small = [1 for w,h in wh_list if w*h < 0.01] print("小目标占比:", len(small)/len(wh_list))这段代码的逻辑很直接:遍历所有标签文件,统计每个类别的框数量、每张图平均有多少个框、有多少张图是空标注、以及面积小于整图1%的小目标比例。参数上,0.01这个阈值是我习惯用的,对应大约32x32像素(在640输入下),烟雾早期目标往往就这个量级。如果空标注图片超过5%,要么是负样本故意放的,要么是漏标,得区分对待。如果小目标占比超过30%,后面训练时imgsz就不能用默认的640,得往上提。
2.2 类别定义混乱是火灾烟雾数据集最大的坑
我处理过好几个火灾数据集,类别定义五花八门。有的只有fire和smoke两类,有的把fire拆成flame、spark、ember,还有的把smoke分成white_smoke、black_smoke。3600张这个量级,如果类别超过3类,每类分到的样本可能就不够YOLOv11收敛了。
你需要先确认data.yaml里的names列表和标注文件里的class id对得上。我遇到过标注里class id从1开始,但data.yaml里names从0开始,结果训练时所有标签都偏移了一位,模型把火焰学成了背景。检查方法很简单:
import yaml with open("data.yaml") as f: cfg = yaml.safe_load(f) print("names:", cfg["names"]) print("nc:", cfg.get("nc", len(cfg["names"]))) # 再对比cls_counter里的最大id print("标注中最大class id:", max(cls_counter.keys()))如果max(cls_counter.keys()) >= len(cfg["names"]),说明有类别没在names里定义,或者id越界了。这种情况YOLOv11训练时不会报错,但会把越界的类别当成背景忽略,你最后发现某一类永远检测不出来,查半天查不到原因。
注意:如果发现类别定义不合理,比如
fire和flame混用,建议合并成统一类别重新生成标签。3600张的规模,重新映射class id比后面调参划算得多。
2.3 训练集/验证集划分不能随机切
很多人习惯train:val = 8:2随机切。但火灾烟雾数据集如果是从视频抽帧来的,相邻帧高度相似,随机切会导致验证集里出现和训练集几乎一样的图片,mAP虚高。我一般先按视频来源或时间戳分组,再在组间切分。如果数据集没有来源信息,至少用感知哈希去重:
import imagehash from PIL import Image import glob hashes = {} for img_path in glob.glob("images/train/*.jpg"): h = imagehash.phash(Image.open(img_path)) hashes[img_path] = h # 找出相似度极高的图片对 keys = list(hashes.keys()) dup_pairs = [] for i in range(len(keys)): for j in range(i+1, len(keys)): if hashes[keys[i]] - hashes[keys[j]] <= 5: dup_pairs.append((keys[i], keys[j])) print("近似重复对数量:", len(dup_pairs))phash的汉明距离小于等于5,基本可以认为是同一场景的近似重复。如果重复对超过10%,建议把重复的只保留一张,或者确保它们落在同一个split里。这一步不做,后面验证集指标好看但实际部署翻车,属于典型的“数据泄漏”。
3. 用YOLOv11跑通第一个baseline:环境配置与训练命令
3.1 环境配置:ultralytics安装与版本确认
YOLOv11目前是通过ultralytics包来调用的。我一般用conda建一个干净环境,避免和之前的YOLOv5/v8冲突:
conda create -n yolo11 python=3.10 -y conda activate yolo11 pip install ultralytics==8.3.0 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121这里指定ultralytics==8.3.0是因为这个版本对YOLOv11的支持比较稳定,再新的版本有时候API会微调。torch的cu121对应CUDA 12.1,你根据自己显卡驱动选。装完后验证:
from ultralytics import YOLO model = YOLO("yolo11n.pt") print(model.model.yaml["nc"]) # 默认80类如果这一步能跑通,说明环境没问题。yolo11n.pt是nano版本,参数量小,适合先跑通流程。后面再换s/m/l。
3.2 data.yaml的正确写法与路径陷阱
data.yaml看着简单,但路径写错是新手最常翻车的地方。我建议用绝对路径,避免相对路径在不同工作目录下解析不一致:
path: /home/user/fire_smoke_dataset train: images/train val: images/val test: images/test names: 0: fire 1: smokepath是数据集根目录,train和val是相对于path的子路径。YOLOv11会自动在path下找labels目录,且要求labels的目录结构和images一致。比如images/train/abc.jpg对应labels/train/abc.txt。如果你把labels放在别的地方,需要在yaml里显式指定train_labels,但我不建议这么干,容易乱。
提示:如果训练时报
No labels found,先检查path是否写对,再检查labels目录名是不是labels而不是label或annotations。
3.3 第一条训练命令:参数怎么设才不浪费3600张图
baseline训练命令我一般这么写:
yolo detect train \ data=/home/user/fire_smoke_dataset/data.yaml \ model=yolo11s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ workers=8 \ device=0 \ project=fire_smoke_runs \ name=baseline_s \ patience=20 \ save=True \ plots=True逐项说:model=yolo11s.pt比nano大,3600张图用s版本比较匹配,n版本可能欠拟合,l版本容易过拟合。epochs=100配合patience=20,如果20轮验证mAP不涨就早停,省时间。imgsz=640是默认值,但如果前面统计小目标占比高,这里要改成960或1280,代价是显存和训练时间增加。batch=16在12G显存下跑s版本640输入基本能稳住,如果OOM就降到8。workers=8取决于你CPU核数,太多反而拖慢数据加载。
训练开始后重点看几个输出:box_loss和cls_loss是否稳定下降,mAP50在前10轮有没有明显上升。如果前10轮mAP50一直低于0.1,大概率是数据或标签有问题,别继续跑,回去查第2章的内容。
3.4 训练过程监控与中断恢复
YOLOv11训练时会在project/name下生成results.csv,里面记录了每轮的loss和mAP。我习惯用一条命令实时看:
tail -f fire_smoke_runs/baseline_s/results.csv如果训练中断了,比如断电或手动停了,可以用resume继续:
yolo detect train resume model=fire_smoke_runs/baseline_s/weights/last.ptresume会从last.pt恢复优化器状态和epoch计数。注意只有last.pt能resume,best.pt只保存了权重,没有优化器状态,用它resume会从头开始。
4. 烟雾小目标优化:从640到1280的取舍与增强策略
4.1 为什么烟雾检测必须关注小目标
火灾早期烟雾在监控画面里可能只占几十个像素。YOLOv11默认的P3特征图 stride是8,对应640输入下每个格子8x8像素。如果烟雾目标小于16x16,在P3上只有2x2个格子响应,很容易被NMS滤掉。我实测过,同一份数据在640下mAP50-95是0.38,提到1280后能到0.47,提升主要来自小目标召回。
但imgsz不是越大越好。1280下显存占用大约是640的4倍,训练时间也接近4倍。3600张图,640跑100轮大概2小时(单卡3090),1280要7-8小时。你得权衡。我的建议是:先640跑一个baseline,看验证集里漏检的是不是都是小目标。如果是,再上1280。
4.2 多尺度训练与Mosaic增强的参数调整
YOLOv11默认开启Mosaic增强,把4张图拼成一张。这对小目标其实有利,因为拼完后小目标相对变大。但火灾烟雾场景有个问题:Mosaic会把不同场景的火焰拼在一起,可能产生不真实的颜色组合。我一般把mosaic从默认1.0降到0.5,保留一部分增强但不过度。
yolo detect train \ data=data.yaml \ model=yolo11s.pt \ imgsz=1280 \ batch=8 \ mosaic=0.5 \ scale=0.3 \ degrees=5.0 \ fliplr=0.5 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4scale=0.3允许图片随机缩放30%,增加尺度多样性。degrees=5.0小幅旋转,火灾场景一般不会有大角度旋转,设太大反而引入噪声。hsv_s=0.7和hsv_v=0.4是颜色增强,对火焰颜色变化有好处,但烟雾是灰白色,饱和度增强对它影响不大。
4.3 用验证集混淆矩阵定位漏检和误检
训练完别只看mAP数字,跑一下验证看混淆矩阵:
yolo detect val \ model=fire_smoke_runs/baseline_s/weights/best.pt \ data=data.yaml \ imgsz=1280 \ conf=0.25 \ iou=0.5 \ plots=True生成的confusion_matrix.png能告诉你:fire被误判成smoke有多少,smoke被当成背景漏掉有多少。如果smoke漏检多,说明小目标问题没解决,继续加imgsz或改anchor。如果fire和smoke互相混淆多,说明两类特征区分度不够,可能需要加更多难例。
注意:
conf=0.25是验证时的置信度阈值,实际部署时这个值要重新调。验证时用0.25是为了看模型在全阈值下的表现,部署时可能用0.4或0.5来降误报。
5. 避坑与排查:火灾烟雾数据集训练中最容易翻车的5件事
5.1 现象:mAP50卡在0.3不上不下,loss也不降
原因:最常见的是标签格式不对。YOLO要求归一化坐标,如果标注是像素坐标且没除宽高,模型学到的框全是错的。另一种可能是class id从1开始,但names从0开始,导致所有标签偏移。
解决:用第2章的统计脚本检查坐标范围,如果w或h大于1,写个脚本批量除以图像宽高。class id偏移的话,把所有txt里的id减1重新保存。
5.2 现象:训练时显存够,但验证时OOM
原因:验证时YOLOv11默认用batch=32,比训练batch大。如果训练batch=16,验证时可能爆显存。
解决:在val命令里显式指定batch=8,或者训练时就把batch设小一点。这个坑很隐蔽,因为训练能跑,一到验证就崩。
5.3 现象:模型把火焰检测成烟雾,或者反过来
原因:两类样本在颜色和纹理上重叠。比如阴天场景的火焰偏暗,和黑烟颜色接近。另外如果标注时边界框画得太松,把火焰周围的烟雾也框进fire类,模型就学混了。
解决:检查标注框是否紧贴目标。如果松框多,重新标注或收紧。训练时加label_smoothing=0.1,让模型不要对单类过度自信。如果还是混,考虑合并成fire_smoke一类,先做有无检测,再二级分类。
5.4 现象:验证集mAP很高,但实际视频推理漏检严重
原因:数据泄漏。训练集和验证集有近似重复帧,验证集指标虚高。或者训练集都是白天场景,实际视频有夜间红外画面,域偏移。
解决:用第2章的phash去重,确保验证集和训练集无重叠。如果域偏移,在训练集里加入目标域的无标注图片做半监督,或者至少做一次直方图均衡化预处理。
5.5 现象:训练到一半loss突然变NaN
原因:学习率太大,或者某批数据里有异常框(宽高为0或负值)。YOLOv11默认用SGD,lr0=0.01,如果数据集小,这个值可能偏大。
解决:先把lr0降到0.001试。同时检查标签里有没有w=0或h=0的行,这些会导致除零。用脚本过滤掉:
with open(txt) as f: lines = [l for l in f if l.strip()] valid = [] for line in lines: p = line.split() if float(p[3]) > 0 and float(p[4]) > 0: valid.append(line) with open(txt, "w") as f: f.writelines(valid)6. 从baseline到可用模型:导出ONNX与Jetson Nano部署的量化取舍
训练出best.pt只是第一步。如果你打算部署到Jetson Nano这类边缘设备,直接跑PyTorch权重帧率可能只有个位数。我一般先导出ONNX,再用TensorRT做FP16或INT8量化。导出命令:
yolo export \ model=fire_smoke_runs/baseline_s/weights/best.pt \ format=onnx \ imgsz=640 \ simplify=True \ opset=12simplify=True会调用onnx-simplifier去掉冗余节点,opset=12兼容性较好。导出后在Jetson Nano上用TensorRT加载:
import tensorrt as trt import pycuda.driver as cuda logger = trt.Logger(trt.Logger.WARNING) builder = trt.Builder(logger) network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser = trt.OnnxParser(network, logger) with open("best.onnx", "rb") as f: parser.parse(f.read()) config = builder.create_builder_config() config.set_flag(trt.BuilderFlag.FP16) config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) engine = builder.build_engine(network, config)FP16量化在Jetson Nano上大概能到15-20 FPS(640输入),INT8能到30 FPS但需要校准集,精度会掉2-3个点。火灾检测场景我建议用FP16,因为漏报的代价比误报高,精度优先。
验证量化后模型有没有掉点,用同一批验证集跑一次:
yolo detect val \ model=best_fp16.engine \ data=data.yaml \ imgsz=640 \ conf=0.25对比best.pt的mAP50,如果掉超过3个点,检查校准集是否覆盖了所有场景。我自己的习惯是:每次导出量化模型后,一定抽10张验证集图片肉眼过一遍,看火焰和烟雾的框有没有明显偏移。这个习惯帮我省过好几次“指标正常但实际不能用”的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取