简介:本资源为面向目标检测学习者的YOLO烟雾检测数据集,适用于安防监控、工业安全等场景下的烟雾识别模型训练,兼顾入门与进阶需求。压缩包共2000个文件,约86.54MB,包含1000张真实场景高质量图片,以及voc(xml)、coco(json)和yolo(txt)三种格式标签,分别存放于不同文件夹,可直接用于YOLO系列模型训练。此外还提供数据集划分脚本、YOLO环境搭建与训练教程,方便按需划分训练集、验证集和测试集。目前已有362人学习下载。资源覆盖从数据准备到模型训练的完整流程,标注框质量高,场景丰富,能帮助读者快速搭建烟雾检测实验环境,减少数据整理与格式转换的重复工作,适合课程设计、科研实验及工程验证使用。
1. 烟雾目标检测为什么值得从一套 1000 张的数据集开始
工业现场、仓储园区、老旧楼宇里,烟雾往往比明火更早出现,但摄像头拍到的烟雾边界模糊、半透明、形态随风飘散,用传统阈值或颜色分割几乎必翻车。这也是为什么越来越多团队转向 YOLO 烟雾目标检测:它把「哪里像烟」交给卷积网络去学,而不是靠人去写规则。你手上如果只有算法没有数据,模型再新也训不出东西;反过来,一套标注规范、格式齐全、划分合理的 1000 张烟雾数据集,配合 YOLO 预训练模型微调,往往就能跑出一个能上边缘盒子做初筛的基线。
这套标题里的资源,核心价值不在「1000 张」这个数字,而在于它同时给了 VOC、COCO、YOLO 三种格式标签和划分脚本。做过检测的人都知道,公开烟雾数据要么只有图片没标注,要么只有单一格式,拿到手第一件事就是写转换脚本、重新划分、对齐类别名,光这些杂活就能耗掉一两天。它把这段脏活前置了,让你能把时间花在训练调参和部署验证上。适合谁?适合刚接触 YOLO 目标检测、想找一个完整闭环练手的工程师,也适合手里有业务场景、需要快速验证「烟雾检测这条路走不走得通」的团队。下面我按「先看懂数据 → 再跑通训练 → 再避开坑」的顺序,把整套流程拆开讲。
2. 拆开这套数据集:三种标签格式到底怎么选、怎么对齐
2.1 VOC、COCO、YOLO 三种格式的差异与适用场景
同一批图片,三种标签格式描述的是同一件事,但组织方式完全不同。VOC 用 XML,一张图一个文件,框用xmin/ymin/xmax/ymax绝对坐标;COCO 用一个大的 JSON,所有图片和标注集中管理,框是[x, y, width, height]绝对坐标加category_id;YOLO 用 txt,一张图一个文件,每行class_id cx cy w h,全部是相对图片宽高的归一化值。选哪个不取决于哪个「高级」,而取决于你下游用什么框架和工具链。
| 格式 | 存储方式 | 坐标类型 | 典型用途 |
|---|---|---|---|
| VOC | 每图一个 XML | 绝对像素 | 老牌检测框架、标注工具导入导出 |
| COCO | 单个 JSON | 绝对像素 | 评估指标、pycocotools、多任务 |
| YOLO | 每图一个 txt | 归一化相对值 | Ultralytics 系训练直接读取 |
我一般会以 YOLO 格式作为训练主格式,因为 Ultralytics 的 YOLO 系列直接吃这个;VOC 留着做标注复核和跨工具迁移;COCO 留着跑 mAP 评估和跟别的模型对比。三种格式并存的最大好处是:你换框架时不用重新标注,只换读取层。
2.2 用脚本把 VOC 转成 YOLO:坐标归一化与类别映射
拿到 VOC 的 XML 后,转 YOLO 的核心就两件事:把绝对坐标除以图片宽高变成相对值,把类别名映射成从 0 开始的整数 id。下面这段脚本我用了很多次,逻辑清晰,边界也处理了。
import os import xml.etree.ElementTree as ET from PIL import Image # 类别名到 id 的映射,顺序一旦定下就不要改,训练和推理必须一致 CLASSES = ["smoke"] CLASS_TO_ID = {name: i for i, name in enumerate(CLASSES)} def voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) 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() # 图片文件名在 XML 的 filename 节点里,用它去定位原图拿宽高 img_name = root.find("filename").text img_path = os.path.join(img_dir, img_name) with Image.open(img_path) as im: w, h = im.size lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text.strip() if cls_name not in CLASS_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) # 归一化并转成中心点加宽高 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h # 裁剪到 [0,1],防止标注越界导致训练报错 cx, cy = min(max(cx, 0), 1), min(max(cy, 0), 1) bw, bh = min(max(bw, 0), 1), min(max(bh, 0), 1) lines.append(f"{CLASS_TO_ID[cls_name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_file = os.path.join(out_dir, os.path.splitext(xml_file)[0] + ".txt") with open(out_file, "w") as f: f.write("\n".join(lines)) voc_to_yolo("Annotations", "JPEGImages", "labels")逻辑说明:先读 XML 拿框,再用对应图片的真实宽高做归一化,这一步必须用原图尺寸,不能用固定值,否则不同分辨率图片的框会整体偏移。参数说明:CLASSES是类别白名单,烟雾检测通常就一个类,如果你还区分「白烟/黑烟」就加进去,但 id 顺序定下后训练、推理、评估三处必须完全一致。:.6f保留六位小数是 YOLO 生态的常见精度,够用且不会让文件过大。跑完检查一下 labels 目录里 txt 数量和图片数量是否一一对应,少一个都说明有图没标注或文件名对不上。
2.3 划分脚本怎么写才不会有数据泄漏
划分训练集、验证集、测试集看着简单,坑却最多。最常见的错误是按图片随机分,但同一段视频抽出来的连续帧被分到训练和验证两边,验证集精度虚高,上线就露馅。正确做法是先按场景或视频源分组,再在组级别划分。
import os import random import shutil def split_dataset(img_dir, label_dir, out_dir, ratios=(0.8, 0.1, 0.1), seed=42): random.seed(seed) # 固定种子,保证每次划分可复现 imgs = [f for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".png"))] # 按文件名前缀分组,同一段视频的帧前缀相同,避免泄漏 groups = {} for f in imgs: key = f.split("_")[0] groups.setdefault(key, []).append(f) keys = list(groups.keys()) random.shuffle(keys) n = len(keys) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) splits = { "train": keys[:n_train], "val": keys[n_train:n_train + n_val], "test": keys[n_train + n_val:], } for split, gkeys in splits.items(): for sub in ("images", "labels"): os.makedirs(os.path.join(out_dir, split, sub), exist_ok=True) for k in gkeys: for f in groups[k]: shutil.copy(os.path.join(img_dir, f), os.path.join(out_dir, split, "images", f)) txt = os.path.splitext(f)[0] + ".txt" src = os.path.join(label_dir, txt) if os.path.exists(src): shutil.copy(src, os.path.join(out_dir, split, "labels", txt)) split_dataset("images", "labels", "dataset")逻辑说明:seed=42保证结果可复现,团队协作时大家划分一致才能对比实验。按文件名前缀分组是关键,如果你的图片命名没有规律,就退而求其次按拍摄时间段或来源目录分组。参数说明:ratios是训练/验证/测试比例,1000 张规模下 8:1:1 比较稳,验证集太小会让早停判断抖动。划分完务必统计三个子集的图片数和标注框数,如果某一类在验证集里一个框都没有,说明划分偏了,要重新调种子或分层抽样。
3. 用 YOLO 把 1000 张烟雾数据训到能用的最小流程
3.1 环境配置与预训练权重选择
环境这块,我一般用 Python 3.10 加 PyTorch,显卡从 RTX 3060 到 V100 都跑过,1000 张图用 yolov8n 或 yolov8s 这种小模型就够,别一上来就上大模型,数据量撑不住反而过拟合。安装直接走 pip:
pip install ultralytics # 验证环境和 GPU 是否可用 yolo checksyolo checks会打印 PyTorch 版本、CUDA 是否可用、显卡型号,这一步能提前发现驱动和 CUDA 不匹配的问题,比训练到一半报错强。预训练权重用官方发布的 yolov8n.pt 或 yolov8s.pt,加载预训练权重做微调,比从零训收敛快得多,1000 张图通常 100 到 150 轮就能看到稳定结果。注意权重版本和 ultralytics 版本要匹配,版本错配有时会报 key 不匹配,遇到就换对应版本的权重。
3.2 data.yaml 的字段含义与常见写错点
YOLO 训练靠一个 data.yaml 描述数据位置和类别,字段少但每个都关键。
path: /data/smoke_dataset # 数据集根目录,绝对路径最稳 train: train/images # 相对 path 的训练图片目录 val: val/images test: test/images nc: 1 # 类别数,烟雾检测通常为 1 names: ["smoke"] # 类别名,顺序必须和标签里的 id 对应逻辑说明:path用绝对路径能避免工作目录变化导致的找不到文件。train/val/test写的是图片目录,YOLO 会自动去找同级的 labels 目录,所以目录结构必须是train/images和train/labels并列。参数说明:nc和names长度必须一致,names的顺序就是标签里 class_id 的顺序,写反了模型会把烟学成背景。最常见的写错点是把train写成图片文件列表而不是目录,或者 labels 目录名拼错,训练一开始就报找不到标签。
3.3 启动训练与关键超参怎么设
命令行启动训练最直接:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=30 \ project=runs/smoke \ name=exp1逻辑说明:model=yolov8n.pt加载预训练权重做微调。imgsz=640是速度和精度的平衡点,烟雾目标通常占画面比例不小,640 够用;如果小目标烟雾多,可以提到 960 但显存和耗时都会涨。参数说明:batch=16在 8G 显存上比较稳,显存不够就降到 8 并配合accumulate做梯度累积。lr0=0.01是初始学习率,微调场景可以降到 0.001 更稳。patience=30表示验证指标 30 轮不提升就早停,省时间也防过拟合。训练过程中重点看mAP50和mAP50-95两条曲线,如果训练 loss 一直降但验证 mAP 早早走平甚至下降,就是过拟合信号,该加数据增强或减模型容量了。
3.4 训练完怎么验证模型真的学到了烟
训练结束别只看最后的 mAP 数字,要拿测试集实际跑一遍推理,把预测框画出来肉眼过一遍。用下面的脚本批量推理并保存可视化结果:
from ultralytics import YOLO import os model = YOLO("runs/smoke/exp1/weights/best.pt") test_dir = "dataset/test/images" for img in os.listdir(test_dir): results = model(os.path.join(test_dir, img), conf=0.25) # save=True 会把带框的图存到 runs/detect 下,方便逐张核对 results[0].save(filename=f"vis/{img}")逻辑说明:conf=0.25是置信度阈值,低于它的框不显示,调高会漏检、调低会误检,先用 0.25 看整体表现。参数说明:best.pt是验证集上表现最好的权重,别用last.pt,最后一轮往往已经过拟合。看可视化时重点盯三类:漏检的烟(尤其淡烟、远距离烟)、把云雾或蒸汽误检成烟、框位置偏移。这些现象直接告诉你下一步是补数据还是调增强。混淆矩阵也值得看,如果背景被大量误判为烟,说明负样本不够,得往训练集里加不含烟的相似场景图。
4. 烟雾检测训练里最容易翻车的几个地方
4.1 现象:训练 loss 正常但 mAP 一直上不去
原因:多半是标签和图片没对齐,或者类别 id 映射错了。YOLO 对错位标签不会报错,只会默默学歪。解决:随机抽 10 张图,用可视化脚本把标签框画到原图上,肉眼确认框的位置和类别对不对。再检查 data.yaml 的 names 顺序和标签里的 id 是否一致。
4.2 现象:验证集 mAP 很高,换一批现场图就崩
原因:数据泄漏或场景单一。同一段视频的帧被分到了训练和验证两边,验证集等于在考训练集见过的画面。解决:回到划分脚本,按视频源或拍摄场景分组再划分,确保验证集里的场景训练时没见过。同时补充不同光照、不同距离的烟雾图。
4.3 现象:训练中途 loss 突然变成 nan
原因:学习率太大、标注框越界、或者某张图尺寸异常。解决:先把lr0降到 0.001 重跑;再用脚本扫一遍所有标签,检查有没有 cx/cy/w/h 超出 [0,1] 的框,有就裁剪或剔除;最后确认没有 0 字节的损坏图片。
4.4 现象:模型把白云、蒸汽、雾气全当成烟
原因:负样本不足,模型没学会区分「烟」和「像烟但不是烟」的东西。解决:往训练集里加入不含烟但含云、雾、蒸汽的负样本图,标签文件留空即可。负样本比例一般控制在正样本的 10% 到 20%,太多会拉低召回。
4.5 现象:推理速度远低于预期
原因:输入尺寸设太大,或者用了大模型。解决:烟雾检测对实时性要求高时,把imgsz降到 416 或 320,换 yolov8n,再导出成 ONNX 或 TensorRT 推理,速度能提升明显。注意导出后要重新验证精度,量化有时会掉点。
5. 把 1000 张数据用出复利:增强策略与迭代习惯
1000 张图不算多,但用对了能顶几千张。我的习惯是先把基线跑通,拿到一个能看的 mAP,再针对性做增强。烟雾检测最有效的增强是 Mosaic 和随机透视,Mosaic 把四张图拼一张,能显著提升小目标和遮挡场景的表现;随机透视模拟不同拍摄角度。但要注意,Mosaic 在训练最后几十轮最好关掉,让模型在真实分布上收尾,否则框的分布和真实场景有偏差。颜色抖动(HSV)对烟雾也有用,因为现场光照差异大,但抖动幅度别太大,否则淡烟会被抖没。
迭代上我一般按「看错例 → 补数据 → 重训 → 对比」循环。每次训练完把误检和漏检的图单独挑出来,看是缺这类场景还是标注有问题。如果是缺场景,就想办法补几十张同类图;如果是标注漏标,就修标签重训。别一次改太多变量,否则不知道是哪个改动起了作用。评估时除了 mAP,还要看不同置信度下的召回和误报,工业场景往往宁可多报也不能漏报,阈值可以适当调低。
最后说个我踩过的坑:早期我图省事,把验证集和测试集混着调参,结果模型在测试集上表现虚高,上线后误报一堆。后来我强制自己测试集只在最终验收时用一次,调参全程只看验证集。这个习惯看着笨,但能让你对模型的真实水平心里有数。数据集的格式转换、划分、训练这条链路,跑通一次之后就是可复用的资产,换个场景换批数据就能再来一轮。希望帮到你。
本文还有配套的精品资源,点击获取