简介:本资源为面向YOLO目标检测学习者的烟雾识别数据集,适用于安防监控、工业消防、森林防火等场景下的烟雾检测模型训练与算法验证,适合具备一定深度学习基础、正在做目标检测课程设计或科研实验的开发者使用。压缩包共收录2000个文件,全部为XML格式的标注文件,对应21578张烟雾图像,整体约268.9MB,标注文件可直接配合YOLO系列框架完成数据加载与训练流程。目前已有482人学习下载,具备一定的参考与复用价值。读者可借助该数据集快速搭建烟雾检测训练环境,用于模型微调、精度对比与消融实验,省去自行采集与标注图像的时间成本,同时便于验证数据增强、锚框调整等策略对烟雾小目标检测效果的影响,为后续工程落地提供可靠的数据支撑。
1. 烟雾检测数据集拿到手:21578 张带标签图像到底怎么用
工业现场做烟雾检测,最头疼的从来不是模型结构,而是数据。你手里如果正好有一份 21578 张图像、带标签、按 smoke100 类别组织的烟雾数据集,那已经比大多数人起步快了。但快不等于顺——我见过太多人把 zip 解压完,直接train一跑,结果 mAP 卡在 0.3 上不去,回头怪 YOLO 不行。问题往往出在标签格式、类别映射和划分策略上,而不是网络本身。
这份数据集的核心价值在于“带标签”三个字。烟雾检测和普通目标检测不一样:烟没有固定形状,边缘弥散,早期烟雾和云雾、水汽、光照反射极易混淆。21578 张的量级,配合 YOLO 系列做迁移学习,是能落地的。它适合谁?适合做工业烟雾预警、火灾早期监控、边缘部署烟感视觉方案的工程师,也适合想用真实场景数据跑通 YOLO 训练全流程的人。接下来我不讲空理论,直接按“数据检查 → 格式转换 → 训练配置 → 排错 → 进阶技巧”这条线走一遍。
2. 烟雾数据集的结构检查与 YOLO 格式转换
2.1 先别急着训练:解压后必须确认的三件事
拿到smoke100-uwe4t.zip这类压缩包,第一步不是写训练脚本,而是把目录结构摸清楚。常见做法是先看顶层目录,再抽样看标签文件。烟雾数据集通常有两种组织方式:一种是 VOC 风格(Annotations放 XML,JPEGImages放图),一种是 YOLO 风格(images和labels平行,标签是.txt)。你得先确认是哪一种,因为后面的转换脚本完全不同。
我一般会跑下面这段检查脚本,把图像数量、标签数量、类别名、标签格式一次性看清楚:
import os import glob from collections import Counter root = "./smoke100-uwe4t" # 解压后的根目录,按实际改 # 1. 统计图像文件 img_exts = ("*.jpg", "*.jpeg", "*.png", "*.bmp") imgs = [] for ext in img_exts: imgs.extend(glob.glob(os.path.join(root, "**", ext), recursive=True)) print("图像总数:", len(imgs)) # 2. 统计标签文件 txts = glob.glob(os.path.join(root, "**", "*.txt"), recursive=True) xmls = glob.glob(os.path.join(root, "**", "*.xml"), recursive=True) print("txt 标签数:", len(txts), " xml 标签数:", len(xmls)) # 3. 抽样看一个 txt 标签内容(YOLO 格式应为: class x_center y_center w h) if txts: with open(txts[0], "r") as f: lines = f.read().strip().splitlines() print("样例标签文件:", txts[0]) print("前 3 行:", lines[:3]) # 4. 统计类别分布(仅 YOLO txt) cls_counter = Counter() for t in txts: with open(t, "r") as f: for line in f: line = line.strip() if line: cls_counter[line.split()[0]] += 1 print("类别分布:", cls_counter.most_common(10))这段脚本的逻辑很直白:先确认图像和标签是否一一对应,再看标签是 YOLO 的归一化格式还是 VOC 的绝对坐标格式,最后统计类别分布。参数上,root指向解压目录,recursive=True是为了兼容多层子目录。如果txt标签数远小于图像数,说明有大量图片没有标注,训练前必须处理,否则 YOLO 会把无标签图当背景,拉低召回。
提示:如果发现标签是 XML,不要手动转,用现成脚本批量处理,下面就给。
2.2 VOC 转 YOLO:转换脚本与四个边界坑
如果数据集是 VOC 格式,需要把 XML 里的xmin, ymin, xmax, ymax转成 YOLO 的class x_center y_center w h,并且全部归一化到 0~1。转换本身不难,坑在边界处理。我常用的转换脚本如下:
import os import glob import xml.etree.ElementTree as ET # 类别映射:根据你的数据集实际类别改,smoke100 通常只有 smoke 一类 classes = ["smoke"] cls_to_id = {c: i for i, c in enumerate(classes)} xml_dir = "./smoke100-uwe4t/Annotations" img_dir = "./smoke100-uwe4t/JPEGImages" out_dir = "./smoke100-uwe4t/labels" os.makedirs(out_dir, exist_ok=True) def convert(xml_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in cls_to_id: continue 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) # 边界裁剪,防止越界 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)) w = xmax - xmin h = ymax - ymin if w <= 1 or h <= 1: # 过滤无效框 continue x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h nw = w / img_w nh = h / img_h lines.append(f"{cls_to_id[name]} {x_center:.6f} {y_center:.6f} {nw:.6f} {nh:.6f}") return lines # 需要先读取图像尺寸,这里用 PIL from PIL import Image for xml_path in glob.glob(os.path.join(xml_dir, "*.xml")): base = os.path.splitext(os.path.basename(xml_path))[0] img_path = None for ext in (".jpg", ".jpeg", ".png", ".bmp"): p = os.path.join(img_dir, base + ext) if os.path.exists(p): img_path = p break if img_path is None: continue with Image.open(img_path) as im: img_w, img_h = im.size lines = convert(xml_path, img_w, img_h) with open(os.path.join(out_dir, base + ".txt"), "w") as f: f.write("\n".join(lines))逻辑说明:先建立类别到 id 的映射,遍历 XML 中每个 object,取出框坐标,做边界裁剪,再归一化。参数上,classes必须和你的数据集类别一致,烟雾数据集常见只有smoke一类,但有些会分smoke和fire,要按实际改。四个边界坑分别是:坐标越界、宽高为负、宽高过小、类别名不匹配。前三个用max/min和阈值过滤解决,第四个靠打印未匹配类别来排查。
2.3 训练集/验证集/测试集划分:别用随机划分
烟雾检测有个特点:同一场景的连续帧高度相似。如果你用纯随机划分,训练集和验证集里会出现几乎一样的图,验证指标虚高,上线就翻车。我一般按场景或按视频片段划分,如果没有场景信息,至少按文件名前缀分组。常见做法是 8:1:1,但分组划分比随机划分更可信。
import os import glob import random from sklearn.model_selection import GroupShuffleSplit all_imgs = glob.glob("./smoke100-uwe4t/images/*.jpg") # 用文件名前缀作为分组依据,按实际数据调整 groups = [os.path.basename(p).split("_")[0] for p in all_imgs] gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42) train_idx, val_idx = next(gss.split(all_imgs, groups=groups)) train_imgs = [all_imgs[i] for i in train_idx] val_imgs = [all_imgs[i] for i in val_idx] print("训练集:", len(train_imgs), "验证集:", len(val_imgs))这里用GroupShuffleSplit保证同一组的图不会同时出现在训练和验证里。参数test_size=0.2表示验证集占 20%,random_state固定后结果可复现。如果数据没有明显分组,至少也要固定随机种子,别每次跑都不一样。
3. YOLO 训练烟雾检测模型的参数配置与显存控制
3.1 选 YOLOv8 还是 YOLOv5:烟雾场景下的取舍
烟雾检测选版本,不是越新越好。YOLOv8 的 anchor-free 头对小目标更友好,烟雾早期往往是小区域,所以 v8 通常比 v5 略好。但 v5 的生态和部署资料更成熟,如果你要上 RK3588 或树莓派这类边缘设备,v5 的量化工具链更顺。我的建议是:训练阶段用 YOLOv8 拿精度,部署阶段如果边缘工具链只支持 v5,再转。别一上来就纠结,先跑通一个。
数据配置文件smoke.yaml长这样:
path: ./smoke100-uwe4t train: images/train val: images/val test: images/test names: 0: smokepath是数据集根目录,train/val/test是相对路径,names是类别映射。注意names的 id 必须和标签里的 class id 一致,否则训练时类别全错。
3.2 关键训练参数:batch、imgsz、学习率怎么定
烟雾数据集 21578 张,不算小。用单卡 V100 或 3090,batch=16或32都能跑。imgsz我一般设 640,如果烟雾目标很小,可以上 1280,但显存翻倍。学习率用默认的0.01配合余弦退火通常够用,如果 loss 震荡,降到0.001。
yolo detect train \ data=smoke.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=20 \ device=0 \ project=smoke_runs \ name=exp1参数说明:model=yolov8s.pt是预训练权重,烟雾数据量够,用 s 或 m 都行;epochs=100配合patience=20早停;lr0是初始学习率,lrf是最终学习率比例;device=0指定第一块 GPU。如果显存不够,把batch降到 8,或者用yolov8n.pt。
注意:训练中 BN 崩溃是常见问题,表现是 loss 突然变 NaN。原因通常是 batch 太小或学习率太大。解决方法是增大 batch 或降低 lr0,也可以在配置里加
warmup_epochs=3。
3.3 训练过程监控:看哪些曲线,什么时候该停
训练启动后,重点看三个东西:box_loss、cls_loss、mAP50。烟雾检测里cls_loss通常降得比box_loss快,因为类别少。如果box_loss不降,说明框回归有问题,检查标签归一化是否正确。如果mAP50在 0.5 附近卡住,可能是数据里负样本太多,或者烟雾和背景太像。
我一般会在runs/detect/smoke_runs/exp1下看results.csv,用 pandas 画一下:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/smoke_runs/exp1/results.csv") df.columns = df.columns.str.strip() plt.plot(df["epoch"], df["metrics/mAP50(B)"], label="mAP50") plt.plot(df["epoch"], df["train/box_loss"], label="box_loss") plt.legend() plt.show()如果mAP50连续 20 轮不升,早停会触发。别硬跑 300 轮,浪费时间。
4. 烟雾检测训练避坑:从标签到部署的 5 个翻车现场
4.1 现象:mAP 虚高但实际漏检严重
原因:验证集和训练集同场景,模型记住了背景而不是烟雾。解决:按场景分组划分,或者用不同时间段的数据做验证。
4.2 现象:训练 loss 正常,推理时框全偏
原因:标签归一化时用了错误的图像尺寸,比如 XML 里尺寸和实际图不一致。解决:转换时用 PIL 重新读图尺寸,别信 XML 里的size。
4.3 现象:BN 崩溃,loss 变 NaN
原因:batch 太小或学习率太大。解决:batch 至少 8,lr0 降到 0.001,加 warmup。
4.4 现象:边缘部署误检率高
原因:训练时 imgsz 640,部署时输入 320,尺度不一致。解决:导出模型时固定 imgsz,或者用相同尺寸做量化校准。
4.5 现象:混淆矩阵总和不唯一
原因:多类别时某些类被漏统计,或者标签里有非法 class id。解决:检查names和标签 id 是否一一对应,过滤非法标签。
5. 烟雾检测进阶:用测试时增强和切片推理榨干小目标
训练完模型,别急着收工。烟雾早期目标小,常规推理容易漏。我一般会加两个技巧:测试时增强(TTA)和切片推理(SAHI)。TTA 是在推理时对图像做翻转、缩放,综合多个结果,能提 1~3 个点 mAP。切片推理是把大图切成小块分别检测,再合并,对小目标特别有效。
from ultralytics import YOLO model = YOLO("runs/detect/smoke_runs/exp1/weights/best.pt") # TTA 推理 results = model.predict( source="./test_images", imgsz=640, augment=True, # 开启 TTA conf=0.25, iou=0.45, save=True )augment=True就是 TTA,conf和iou按验证集调。切片推理可以用 SAHI 库,但要注意切片重叠率,太低会漏,太高会重复框。我一般设overlap=0.2。
验证方法上,别只看 mAP,要拿实际视频跑一遍,看漏检和误检的帧。我习惯把误检的图单独存下来,回头补进训练集,迭代两三轮,效果比换模型明显。最后说个血泪经验:烟雾检测的后悔药就是数据,模型再改,不如多标 500 张难例。希望帮到你。
本文还有配套的精品资源,点击获取