简介:本资源面向目标检测初学者与需要森林火灾烟雾识别方案的开发者,提供一套可直接用于YOLO系列训练的真实场景数据集。数据包含1000张高质量图片,场景丰富,经labelimg精细标注,并同步提供voc(xml)、coco(json)与yolo(txt)三种格式标签,分别存放于不同文件夹,便于直接接入不同框架。压缩包共2000个文件,以xml与txt标签为主,另含html教程、py划分脚本与yaml配置文件,整体约71.92MB。资源附赠YOLO环境搭建、训练案例教程及数据集划分脚本,可按需生成训练集、验证集与测试集,帮助读者快速跑通从数据准备到模型训练的全流程。目前已有925人学习下载,适合希望节省标注成本、快速验证烟雾检测效果的中级学习者参考使用。
1. 拿到一个森林火灾烟雾数据集,先别急着训练
森林火灾烟雾检测这件事,真正卡住大多数人的从来不是模型结构,而是数据。你手上如果有一个「1000 张图片 + voc/coco/yolo 三种格式标签 + 划分脚本 + 训练教程」的压缩包,那它解决的正是最费时间的那一环:把原始标注整理成 YOLO 能直接吃的目录结构。烟雾检测和常规目标检测不太一样,烟是半透明、边界模糊、形态随风向不断变化的目标,早期火点往往只有一小团灰白区域,和晨雾、云层、水汽高度相似,所以数据集的质量和标签格式的规范性,直接决定了后面模型是能收敛还是全程玄学震荡。
这个方案适合三类人:一是想快速跑通 YOLO 烟雾检测 baseline 的学生和初学者,二是需要把公开数据整理成自有训练流水线的工程师,三是做林火预警、电力巡检、园区监控这类落地项目、需要先验证数据可行性的从业者。1000 张的规模不算大,但足够跑通全流程、验证标签转换和划分脚本是否正确,也能作为后续扩充数据的模板。下面我按「先看懂数据长什么样 → 再动手转换和划分 → 然后配环境训练 → 最后排坑和调优」的顺序讲清楚,每一步都给到能直接抄的命令和参数。
2. 三种标签格式到底差在哪:voc、coco、yolo 的字段对照
2.1 为什么同一个数据集要同时给三种格式
很多人第一次看到 voc、coco、yolo 三套标签会懵:不是一份标注就够了吗?原因是这三种格式服务的工具链完全不同。VOC 的 XML 是标注工具(LabelImg 等)的原生输出,人可读、方便核对;COCO 的 JSON 是学术界和检测/分割通用评测的事实标准,pycocotools 只认它;YOLO 的 txt 是训练时 DataLoader 直接读取的格式,一行一个目标,最省解析开销。数据集同时提供三份,本质是让你不用自己写转换脚本,直接对接不同框架。
理解字段映射是后面排错的根基。VOC 用绝对像素坐标xmin, ymin, xmax, ymax;COCO 用[x, y, width, height]的绝对像素框加category_id;YOLO 用归一化后的x_center, y_center, width, height,取值 0~1,且类别从 0 开始编号。三者之间转换最容易翻车的地方就是坐标系和归一化,下面这张表建议存下来。
| 格式 | 文件形态 | 框表示 | 坐标基准 | 类别编号 |
|---|---|---|---|---|
| VOC | 每图一个 .xml | xmin,ymin,xmax,ymax | 绝对像素 | 名称字符串 |
| COCO | 单个 .json | x,y,w,h | 绝对像素 | 从 1 开始 |
| YOLO | 每图一个 .txt | cx,cy,w,h | 归一化 0~1 | 从 0 开始 |
2.2 用一段脚本核对三种格式是否自洽
拿到数据集第一件事不是训练,是验证三套标签描述的是同一批框。我一般写个小脚本,随机抽若干张图,把三种格式解析出来对比,误差超过 1 像素就说明转换有问题。
import os, json, random import xml.etree.ElementTree as ET def read_voc(xml_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') w, h = int(size.find('width').text), int(size.find('height').text) boxes = [] for obj in root.iter('object'): b = obj.find('bndbox') boxes.append(( float(b.find('xmin').text), float(b.find('ymin').text), float(b.find('xmax').text), float(b.find('ymax').text))) return w, h, boxes def read_yolo(txt_path, w, h): boxes = [] with open(txt_path) as f: for line in f: c, cx, cy, bw, bh = line.split() cx, cy, bw, bh = map(float, (cx, cy, bw, bh)) # 反归一化回绝对像素 boxes.append(( (cx - bw / 2) * w, (cy - bh / 2) * h, (cx + bw / 2) * w, (cy + bh / 2) * h)) return boxes # 随机抽 5 张做交叉验证 img_dir = 'images' names = random.sample(os.listdir(img_dir), 5) for n in names: stem = os.path.splitext(n)[0] w, h, voc_boxes = read_voc(f'Annotations/{stem}.xml') yolo_boxes = read_yolo(f'labels/{stem}.txt', w, h) for vb, yb in zip(voc_boxes, yolo_boxes): diff = max(abs(a - b) for a, b in zip(vb, yb)) print(stem, 'max diff =', round(diff, 2))这段脚本的关键在read_yolo里的反归一化:(cx - bw/2) * w把中心点加宽高还原成左上角,再乘图像宽高回到像素。参数上注意w, h必须来自 VOC 的<size>,不能想当然用固定值,因为数据集里图片尺寸可能不统一。如果打印出的max diff普遍大于 1,说明某套标签的坐标系被改过,先别训练,回去查转换环节。
2.3 类别映射:烟雾检测里最隐蔽的坑
烟雾检测数据集常见类别是smoke和fire两类,但 VOC 里写的是字符串,COCO 里是数字 id,YOLO 里是 0/1。如果转换时把fire和smoke的顺序搞反,模型照样能训练、loss 照样下降,但推理时把火判成烟,这种错误在指标上看不出来,只有可视化才暴露。我的习惯是固定一份classes.txt,顺序写死,所有转换脚本都读它,绝不手写类别号。
3. 划分脚本怎么用:从原始目录到 YOLO 训练目录
3.1 划分前先确认目录结构
划分脚本通常假设你已经把图片和标签放在固定位置。跑之前先tree一下,确认结构符合脚本预期,否则脚本会静默跳过找不到标签的图片,最后训练集少一半你还不知道。
# 典型的数据集原始结构 dataset/ ├── images/ # 1000 张 jpg/png ├── Annotations/ # VOC xml ├── labels/ # YOLO txt ├── annotations.json # COCO └── split.py # 划分脚本3.2 运行划分脚本并理解输出
划分脚本一般做三件事:按比例切分 train/val/test、把图片和标签成对复制到对应目录、生成data.yaml。常见比例是 8:1:1 或 7:2:1,1000 张的话验证集 100~200 张比较合理,太少指标抖动大。
python split.py \ --images ./images \ --labels ./labels \ --out ./dataset_split \ --train 0.8 --val 0.1 --test 0.1 \ --seed 42参数说明:--seed一定要固定,否则每次划分结果不同,实验无法复现;--train/--val/--test三者之和必须为 1,脚本一般会校验;--out输出目录里会生成images/train、labels/train这样的镜像结构。跑完后务必数一下文件数:
for d in train val test; do echo "$d images: $(ls dataset_split/images/$d | wc -l)" echo "$d labels: $(ls dataset_split/labels/$d | wc -l)" done图片数和标签数必须一一对应。如果 labels 比 images 少,说明有图片没有对应标注,训练时那批图会被当成负样本,直接污染结果。
3.3 data.yaml 里三个必须改对的字段
划分脚本生成的data.yaml是 YOLO 训练的入口配置,三个字段最容易错:
path: /abs/path/to/dataset_split # 必须是绝对路径 train: images/train val: images/val nc: 2 names: ['smoke', 'fire']path用相对路径时,YOLO 会相对它自己的运行目录解析,经常出现「找不到图片」却报的是标签错误。nc必须等于names长度,且names顺序要和标签里的类别号严格对应。我见过有人nc: 1但标签里有类别 1,训练直接崩在越界索引上。
4. 环境配置与训练:从 Anaconda 到第一个 baseline
4.1 Anaconda 环境配置要求与依赖安装
YOLO 训练对 CUDA 和 PyTorch 版本匹配很敏感,用 conda 隔离环境是标配。下面是我常用的配置流程,Python 3.9~3.10 兼容性最好。
conda create -n fire_yolo python=3.10 -y conda activate fire_yolo # 按显卡 CUDA 版本装对应 torch,这里以 cu118 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python pyyaml tqdm装完先验证 GPU 是否可用,这一步别省:
import torch print('cuda available:', torch.cuda.is_available()) print('device:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'cpu')如果cuda available是 False,后面训练会默默跑在 CPU 上,1000 张图也能跑,但一个 epoch 可能要十几分钟,你会以为模型不收敛,其实是没吃到 GPU。
4.2 用预训练模型跑通第一个 baseline
不要从零训练,烟雾数据只有 1000 张,从零训几乎不可能收敛。用 COCO 预训练权重做迁移学习是标准做法。
yolo detect train \ data=dataset_split/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/fire \ name=baseline参数逐个说:model=yolov8n.pt是最小的 nano 版本,1000 张图足够,显存占用低;imgsz=640是默认输入尺寸,烟雾目标偏大,640 通常够用;batch=16在 8G 显存上比较稳,爆显存就降到 8;lr0=0.01是初始学习率,迁移学习可以再小一点到 0.005;patience=20表示 20 个 epoch 指标不涨就早停,省时间。训练日志里重点看mAP50和mAP50-95,烟雾这种模糊目标 mAP50 能到 0.6 以上就算 baseline 可用。
4.3 训练中该盯哪些指标
box_loss和cls_loss持续下降是基本盘,但真正判断模型好坏要看验证集的混淆矩阵。烟雾检测最怕的是把云、雾判成 smoke(假阳),以及小火点漏检(假阴)。混淆矩阵里如果smoke和背景的误判特别高,说明数据里负样本(无烟的森林图)太少,需要补背景图。另外yolo混淆矩阵总合不唯一这个现象,通常是验证集里同一张图有多个目标、或者类别映射错位导致的,先回去核对data.yaml的names。
5. 避坑与排查:烟雾检测训练里最常见的 5 个翻车现场
5.1 现象:训练 loss 正常下降,但验证 mAP 一直是 0
原因:data.yaml里val路径指向的目录没有标签,或者标签文件名和图片名不一致(比如图片是IMG_001.jpg,标签是IMG_001.txt但放在错误子目录)。YOLO 找不到标签时不会报错,直接把该图当负样本。
解决:跑一遍文件名配对检查,图片 stem 和标签 stem 必须完全一致,且在同一相对路径下。
comm -3 \ <(ls dataset_split/images/val | sed 's/\..*//' | sort) \ <(ls dataset_split/labels/val | sed 's/\..*//' | sort)输出为空才说明配对完整。
5.2 现象:训练中途 BN 层崩溃,loss 变 NaN
原因:batch太小(比如 2 或 4)时,BatchNorm 统计量估计不准,加上学习率偏大,几个 batch 后梯度爆炸。热词里说的yolo训练中bn崩溃多半是这个。
解决:把batch提到 8 以上,或者改用yolov8n.pt这种带预训练权重的模型;同时把lr0降到 0.001,加warmup_epochs=3让前几个 epoch 学习率缓慢上升。
5.3 现象:推理时把晨雾、白云全框成 smoke
原因:训练集里负样本不足,模型没学会区分烟和自然水汽。1000 张图如果全是含烟图,模型会倾向于「看到灰白区域就报烟」。
解决:往训练集里加 10%~20% 的无烟森林、云雾、天空背景图,标签留空(空 txt 文件)。YOLO 支持空标签作为负样本,能显著压低假阳。
5.4 现象:调整置信度门限后指标忽高忽低
原因:conf门限和iou门限(NMS 用的)是两个独立参数,很多人只调conf忘了iou。烟雾目标重叠多,iou设太高会保留大量重复框。
解决:验证时固定iou=0.5先扫conf,从 0.1 到 0.5 每 0.05 测一次,画 P-R 曲线选最优点。命令里加conf=0.25 iou=0.5显式指定,别用默认值拍脑袋。
5.5 现象:换一台机器训练,结果完全不一样
原因:没固定随机种子,或者两台机器 CUDA/cuDNN 版本不同导致算子实现有差异。深度学习本身有随机性,但差异大到 mAP 掉 0.1 就不正常。
解决:训练命令加seed=42 deterministic=True,并记录torch.__version__和 CUDA 版本。跨机器复现时,环境版本对齐比调参更重要。
6. 把 1000 张用出 10000 张的效果:烟雾检测的进阶技巧
数据量小是这类数据集的天花板,但烟雾检测有几个特性可以利用。第一是烟雾形态不固定,水平翻转、随机缩放、色彩抖动这类增强对烟雾几乎无损,甚至有益,因为真实场景里烟本来就没有固定朝向。在 YOLO 配置里把hsv_h=0.015 hsv_s=0.7 hsv_v=0.4 fliplr=0.5打开,比默认增强更能提升泛化。第二是烟雾区域大,可以适当提高imgsz到 768 或 896,让小目标火点也能被检测到,代价是显存和训练时间上升,8G 卡上batch要相应降到 8。
第三点是我踩坑最多的地方:不要迷信 mAP。烟雾检测的落地价值在于「早发现」,也就是在火还小的时候报警。而 mAP 是在所有目标上平均的,大火点容易检、贡献大,小火点漏检对 mAP 影响被稀释。我现在的习惯是单独统计小目标(框面积小于图像 1%)的召回率,把它当成核心指标。做法是在验证后跑一段脚本,按框面积分桶算 recall:
from ultralytics import YOLO import numpy as np model = YOLO('runs/fire/baseline/weights/best.pt') results = model.val(data='dataset_split/data.yaml', conf=0.25, iou=0.5) # 按目标面积分桶统计召回,小目标单独看 areas = results.box.pred[:, 2] * results.box.pred[:, 3] # 预测框面积 small = areas < 0.01 * (640 * 640) print('小目标占比:', small.mean())如果小目标召回明显低于整体,说明模型在「早发现」这件事上不可靠,需要针对性补充小火点样本,或者用切片推理(把大图切成小块分别检测)来提升小目标分辨率。切片推理对森林这种大场景监控特别有效,代价是推理耗时成倍增加,实时性要求高的场景要权衡。
最后说个习惯:每次改完数据或参数,我都会把runs/下的results.csv和混淆矩阵图存一份带日期的副本,命名里写清改了什么。烟雾检测调参很容易陷入「感觉这次好一点」的错觉,只有把每次实验的指标和配置留档,回头看才知道哪一步真正有用。这套数据集和流程的价值不在于 1000 张本身,而在于它给了你一个能反复迭代、可复现的起点,把标签转换、划分、训练、排错这条链路走通一遍,后面换成 5000 张、10000 张,你只是换个目录而已。希望帮到你。
本文还有配套的精品资源,点击获取