
简介本资源为面向YOLO目标检测算法学习者的烟雾识别数据集适用于火灾预警、工业安全监控等场景下的模型训练与验证适合具备一定深度学习基础、正在做烟雾检测课题或工程落地的开发者使用。压缩包共收录2000个文件全部为xml格式的标注文件对应21578张烟雾图像整体包体约268.9MBxml文件可直接用于YOLO系列框架的标签解析与数据加载省去自行标注的繁琐流程。目前已有482人学习下载具备一定的参考热度。数据集聚焦烟smoke100类别标注结构统一、命名规范便于快速划分训练集与验证集读者可据此搭建从数据预处理、模型训练到指标评估的完整流程也可用于对比不同YOLO版本在烟雾小目标上的检测表现是烟雾识别方向较为实用的数据支撑材料。1. 烟雾检测数据集落地21578 张带标签图像能撑起什么样的 YOLO 项目拿到一个 21578 张图像、带标签、命名里写着 smoke100-uwe4t 的烟雾数据集第一反应不该是「赶紧训一版 YOLO」而是先判断它到底能撑起什么级别的项目。烟雾检测和常规目标检测不一样烟是半透明、边界模糊、形态随气流不断变化的非刚性目标早期只有一小团淡雾晚期可能铺满半个画面同一套 anchor 很难同时吃下这两种尺度。21578 张这个量级放在 yolo数据集 里属于中等偏上足够从零训练一个轻量检测头也足够做迁移学习后的微调但前提是标签质量和类别定义得先摸清楚。这个数据集适合谁做园区/仓库/林区早期火情预警的算法工程师做 yolo边缘部署监控误检率高 这类课题的研究生以及想用真实烟雾数据跑通 yolo训练自己的数据集 全流程的从业者。它解决的核心问题是让你跳过最耗时的数据采集和标注直接把精力放在模型选型、损失函数调参和误检抑制上。但别指望拿来即用烟雾数据的坑比常规 COCO 类目标多得多。2. 先搞懂烟雾检测为什么不能照搬常规 YOLO 流程2.1 烟雾目标的三个物理特性决定了标注和训练策略烟雾和行人、车辆最大的区别在于它没有稳定纹理和清晰边缘。第一半透明性导致烟雾区域和背景像素混叠标注框内往往包含大量背景模型学到的特征被稀释。第二形态非刚性同一处烟雾在连续帧里形状差异极大如果数据集是抽帧得到的标注框的宽高比分布会非常散。第三尺度跨度大远距离早期烟雾可能只占 30×30 像素近距离浓烟能占满 640×640。这三点直接决定了标注时不能只框「最浓的那块」而要覆盖烟雾可见范围训练时数据增强不能无脑用随机裁剪否则容易把烟雾裁成无意义碎片anchor 或 anchor-free 策略要偏向小目标。我一般会先做一件事统计标注框的宽高分布。用下面这段脚本快速看数据集的尺度分布判断要不要调 anchor 或改输入分辨率。import os import glob import numpy as np # 假设标签是 YOLO 格式class x_center y_center width height归一化 label_dir labels/train widths, heights, areas [], [], [] for txt in glob.glob(os.path.join(label_dir, *.txt)): with open(txt) as f: for line in f: parts line.strip().split() if len(parts) 5: continue w, h float(parts[3]), float(parts[4]) widths.append(w) heights.append(h) areas.append(w * h) widths np.array(widths) heights np.array(heights) areas np.array(areas) print(f标注框总数: {len(widths)}) print(f宽高比 中位数: {np.median(widths / (heights 1e-6)):.2f}) print(f面积占比 25/50/75 分位: {np.percentile(areas, [25, 50, 75])}) print(f小目标(面积0.01)占比: {(areas 0.01).mean():.2%})这段脚本输出三个关键指标宽高比中位数告诉你烟雾偏横向还是纵向扩散面积分位数告诉你尺度集中在哪里小目标占比决定你是否需要开启多尺度训练或提高输入分辨率。如果小目标占比超过 30%输入 640 可能不够建议上 960 或 1280但要注意显存和推理速度的权衡。参数上areas 0.01这个阈值对应约 64×64 像素在 640 输入下你可以根据实际分辨率调整。2.2 从 21578 张里切出可信的验证集比调模型更重要烟雾数据集最容易翻车的地方是验证集泄漏。如果同一段视频抽出的帧被随机分到训练集和验证集验证指标会虚高因为相邻帧几乎一样。正确做法是按视频源或场景分组划分同一场景的帧只能进一个集合。常见做法是先按文件名前缀或目录聚类再按 8:1:1 划分 train/val/test。如果数据集没有提供视频源信息至少要用感知哈希去重把近似帧归到同一组。import os import imagehash from PIL import Image from collections import defaultdict # 用感知哈希对图像分组避免相邻帧泄漏 img_dir images/train hash_groups defaultdict(list) for img_name in os.listdir(img_dir): if not img_name.lower().endswith((.jpg, .png)): continue path os.path.join(img_dir, img_name) h imagehash.phash(Image.open(path).convert(RGB)) # 哈希前 8 位作为分组键近似帧会落到同一组 hash_groups[str(h)[:8]].append(img_name) print(f总图像: {sum(len(v) for v in hash_groups.values())}) print(f分组数: {len(hash_groups)}) print(f最大组帧数: {max(len(v) for v in hash_groups.values())})逻辑说明感知哈希对亮度、缩放不敏感相邻帧的哈希值高度接近用前 8 位分组能把同一段视频的帧聚到一起。参数上前 8 位是经验值分组太碎就减到 6 位分组太粗就加到 10 位。划分时以组为单位整体分配而不是以单张图分配。这一步做完你后面看到的 mAP 才是可信的否则训练集里见过的画面出现在验证集指标好看但上线就崩。3. 用这份数据集跑通 YOLO 训练环境、配置与关键参数3.1 环境搭建与数据目录组织yolo环境搭建 本身不复杂但烟雾项目有几个额外注意点。推荐 Python 3.9、PyTorch 2.0、CUDA 11.8 或 12.1。如果手头是 V100FP16 训练能明显提速如果是消费级卡注意 batch size 别撑爆显存。数据目录按 YOLO 标准结构组织smoke_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── smoke.yamlsmoke.yaml内容如下注意nc和names要和你的标签类别严格对应。如果数据集只有 smoke 一类就写 1如果还分了 light smoke / dense smoke就按实际类别数写。path: ./smoke_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: smoke这里有个容易忽略的点标签文件里的 class id 必须从 0 开始且连续。如果原始标签用了 1 表示 smoke训练时会被当成背景或报错。转换脚本如下import os import glob # 把 class id 从 1 基转成 0 基 label_dir labels/train for txt in glob.glob(os.path.join(label_dir, *.txt)): lines [] with open(txt) as f: for line in f: parts line.strip().split() if not parts: continue parts[0] str(int(parts[0]) - 1) # 1-0 lines.append( .join(parts)) with open(txt, w) as f: f.write(\n.join(lines))参数说明这段只做类别偏移不改坐标。执行前先备份原始标签跑完抽查几个文件确认坐标没被动过。如果数据集本身已经是 0 基跳过这步。3.2 训练命令与必调参数以 YOLOv8 为例一条可复现的训练命令yolo detect train \ datasmoke_dataset/smoke.yaml \ modelyolov8s.pt \ epochs150 \ imgsz960 \ batch16 \ lr00.01 \ lrf0.01 \ warmup_epochs3 \ mosaic0.5 \ mixup0.0 \ copy_paste0.0 \ degrees5.0 \ translate0.1 \ scale0.5 \ fliplr0.5 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ device0 \ workers8 \ projectruns/smoke \ nameexp01逐个说关键参数。imgsz960是因为烟雾小目标多640 容易漏检早期烟雾代价是显存和耗时上升V100 上 batch 16 基本能跑满。mosaic0.5而不是默认的 1.0因为烟雾拼接后容易出现不自然的边界模型可能学到拼接伪影而不是烟雾特征。mixup和copy_paste直接关掉烟雾半透明mixup 会让两层烟雾叠加成非物理的浓度反而干扰学习。degrees5.0限制旋转角度烟雾没有固定朝向但大角度旋转会让标注框包含过多背景。scale0.5保留多尺度增强对烟雾尺度跨度大的特点有帮助。lr00.01配合lrf0.01是余弦退火的常见设置如果训练中 loss 震荡明显先把 lr0 降到 0.005。训练过程中重点看三个曲线train/box_loss是否稳定下降、metrics/mAP50是否在 50 epoch 后还在涨、val/box_loss是否早于 train 反弹。如果 val loss 提前反弹而 train 还在降说明过拟合优先加数据增强或减模型容量而不是继续加 epoch。3.3 损失函数与 BN 崩溃的排查yolo损失函数 在检测头部分由分类损失和回归损失组成烟雾检测里回归损失更关键因为框的定位直接决定误检率。如果训练中出现 loss 突然变 NaN八成是 BN 崩溃。yolo训练中bn崩溃 的典型现象是某个 batch 的 feature map 方差趋近于零BN 层除零后梯度爆炸。诱因通常是 batch size 太小小于 8或学习率太大。解决办法把 batch 提到 16 以上或者改用rectTrue减少 padding 带来的无效像素如果显存不够用梯度累积模拟大 batch。# 梯度累积batch8累积 2 次等效 batch16 yolo detect train datasmoke_dataset/smoke.yaml modelyolov8s.pt batch8 accumulate2 ...另一个排查手段是冻结 BN 的 running stats 几个 epoch等训练稳定后再解冻。YOLOv8 里可以通过修改模型配置实现但更简单的做法是先降 lr 到 0.001 跑 5 个 epoch 热身再恢复到 0.01。BN 崩溃不是玄学本质是数值不稳定把 batch 和 lr 这对参数调平衡基本能压住。4. 烟雾检测的避坑与排查那些指标好看但上线就崩的情况4.1 坑一验证集 mAP 0.85实际监控画面全是误检现象训练完 mAP50 很漂亮部署到摄像头后云、雾、蒸汽、白色墙面全被框成 smoke。原因数据集里的负样本不足模型没见过「像烟但不是烟」的干扰物。解决从实际场景采集负样本尤其是清晨薄雾、工厂蒸汽、白色车辆按 1:3 的比例混入训练集重新微调 20 个 epoch。负样本标签为空文件即可YOLO 会把它们当作背景学习。4.2 坑二小目标烟雾漏检严重mAP 被大目标拉高现象整体 mAP 还行但早期小火情的小烟雾几乎检不到。原因大目标样本占多数损失被大目标主导小目标梯度被淹没。解决开启多尺度训练imgsz在 640 到 1280 之间随机或者在损失里给小目标更高权重。YOLOv8 没有直接的小目标权重参数可以通过复制小目标样本过采样来间接实现。4.3 坑三标签框只框了浓烟核心边缘烟雾被当背景现象模型对浓烟敏感对刚起的薄烟没反应。原因标注时只框了最明显的区域模型学到的「烟雾」定义偏窄。解决重新检查标注规范要求标注框覆盖烟雾可见范围包括半透明边缘。如果重标成本高至少对早期烟雾样本做一次抽查修正。4.4 坑四训练 loss 正常但推理速度极慢现象V100 上训练很快部署到边缘设备帧率只有个位数。原因输入分辨率 960 对边缘设备太重且模型没做量化。解决导出 ONNX 后用 TensorRT 做 FP16 或 INT8 量化输入降到 640通常能换回 3 到 5 倍速度。精度损失一般在 1 到 3 个 mAP 点烟雾检测里可以接受。4.5 坑五混淆矩阵类别对不上总合不唯一现象yolo混淆矩阵总合不唯一每行加起来不等于总数。原因多类别时预测框可能同时匹配多个 GT或者置信度阈值设置导致重复统计。解决先确认nc和标签类别数一致再把conf阈值固定为 0.25 重新生成混淆矩阵。如果只有一类混淆矩阵就是 2×2总合应该等于验证集图像数乘以每图平均目标数对不上通常是标签里有越界坐标或空标签混入。5. 把烟雾模型推到边缘量化、部署与一个验证技巧训练只是上半场烟雾检测真正的价值在边缘设备上 7×24 小时跑。以 RK3588 或树莓派这类平台为例直接跑 PyTorch 模型帧率不够标准路径是 PyTorch → ONNX → RKNN/TensorRT。导出 ONNX 时注意 opset 选 12 或 13动态轴只保留 batch宽高固定否则边缘推理库可能不支持。# 导出 ONNX yolo export modelruns/smoke/exp01/weights/best.pt formatonnx imgsz640 opset12 simplifyTrue # RKNN 转换示意需在 RKNN Toolkit 环境执行 python convert_rknn.py --onnx best.onnx --output smoke.rknn --quantize int8 --dataset quant_images/量化时最容易翻车的是校准集选择。如果校准集全是大白天清晰画面INT8 量化后傍晚和夜间的烟雾检测精度会掉得厉害。我一般会从验证集里按亮度分层抽 200 到 500 张覆盖白天、黄昏、夜间、逆光四种场景再做量化校准。这一步多花半小时能省掉上线后一周的误检投诉。验证量化模型是否可用的技巧不要只看 mAP要单独统计「早期小烟雾」子集的召回率。做法是把验证集里标注框面积小于 0.01 的样本挑出来单独跑一遍推理看召回率掉了多少。如果 INT8 相比 FP16 在这个子集上召回率下降超过 5 个百分点说明量化对弱小目标不友好要么换 FP16要么在训练时加入量化感知训练。# 统计小目标子集召回率 import json small_gt [] # 面积0.01 的 GT with open(val_gt.json) as f: for item in json.load(f): for box in item[boxes]: w box[2] - box[0] h box[3] - box[1] if w * h 0.01: small_gt.append(item[image_id]) print(f含小目标的图像数: {len(set(small_gt))}) # 再用预测结果和这些 image_id 做匹配算召回参数说明面积阈值 0.01 对应 640 输入下约 64×64 像素你可以按实际业务定义「早期烟雾」的像素范围调整。这个子集召回率比整体 mAP 更能反映模型能不能在火情初期报警。最后说个我自己的习惯每次训完烟雾模型我不会只看 mAP 就收工而是把验证集里所有漏检和误检的图导出来按场景分类看一遍。十次里有八次问题不在模型结构而在数据本身——要么标签框偏了要么某个场景的负样本压根没进训练集。烟雾检测这行数据上的功夫永远比改模型结构回报高。希望帮到你。本文还有配套的精品资源点击获取