简介:这份数据集面向需要训练火焰、烟雾、正常三类图像分类模型的开发者和学生,内置约两百四十张已标注真实场景图片,可直接作为CNN或YOLOv5分类网络的实验数据,解决火灾预警、安全监控等场景下公开样本少与标注缺失的问题。资源文件总数两百四十三,包含约两百四十张jpg样本、一个json标注文件、一个py可视化脚本以及一张png预览图,其中json记录三个类别的划分标签,py脚本用于快速查看图片与标注效果,压缩包整体约五百零四KB,轻量便于快速下载与解压。数据已严格按训练集与测试集划分,同一类图片集中存放,便于直接训练和验证,运行其中的show脚本即可快速预览标注效果。目前已有112人学习下载,适合火焰烟雾识别入门练习、模型对比以及算法验证,也可作为教学实验的数据基础。
1. 火焰烟雾识别数据集:240张已标注图片够不够训练一个分类器
做火灾预警或者工地安全监测的同行,大概率都卡在同一个环节:找不到能直接用的火焰烟雾图片数据。网上公开数据集要么是视频帧得自己抽,要么标注得乱七八糟,跑通一个分类网络半天时间全耗在整理数据上。这份火焰、烟雾、正常三类图像识别数据集,约240张已标注图片,虽然量不大,但胜在类别干净、划分好了训练集和测试集,还带可视化脚本,适合用来验证CNN分类思路或者跑yolov5分类分支的流程。
数据集的核心价值在于:它是按三类分好的——火焰、烟雾、正常,标注文件用JSON存着,训练集测试集已分开,省掉了最烦的整理环节。适合两类人:一是刚接触图像分类、想把模型训练全流程跑通的新手;二是需要在火焰烟雾检测上快速验证算法思路的工程师。下面把数据内部结构、训练参数设置、踩坑经验按实际使用顺序拆开讲,照着走就能复现。
2. 数据目录与标签结构:先搞清这240张图是怎么组织的
拿到的数据集解压后,第一件事不是急着训练,而是摸清目录组织方式和标签存储格式。这个数据集的划分逻辑是按类别分文件夹存放,每个类别下再分训练集和测试集,标注信息统一放在JSON文件里。下面把目录结构和JSON字段拆开说明。
2.1 训练集测试集目录划分逻辑
按摘要描述,数据集划分了训练集和测试集,存放各自同一类数据的图片。常见的目录组织方式是:
dataset/ ├── train/ │ ├── fire/ │ │ ├── fire01.jpg │ │ ├── fire02.jpg │ │ └── ... │ ├── smoke/ │ │ └── ... │ └── normal/ │ └── ... ├── test/ │ ├── fire/ │ ├── smoke/ │ └── normal/ ├── labels.json └── show.py这种按类别套训练测试的双层目录,对PyTorch的torchvision.datasets.ImageFolder和TensorFlow的image_dataset_from_directory都天然友好,不用自己写数据加载逻辑。ImageFolder会自动把一级子目录名当作类别标签,按文件名排序后映射到整数索引。
2.2 JSON标签文件字段说明
标注文件的命名在摘要里提到是“查看json文件”,实际打开后字段通常包含图片路径、类别索引、类别名称、数据集划分标记。以常见格式为例,关键字段如下表:
| 字段名 | 类型 | 含义 |
|---|---|---|
| image_path | string | 图片相对路径,如 train/fire/fire01.jpg |
| label | int | 类别索引,0火焰、1烟雾、2正常 |
| class_name | string | 类别名称,fire/smoke/normal |
| split | string | train或test,标记划分归属 |
| bbox(可选) | list | 目标框坐标,分类数据集一般没有 |
注意:这里约240张是总量,不是每类240张。实际每类大概80张左右,训练集测试集按比例分,常见是8:2或9:1。用JSON而不是直接读文件夹名的好处是——类别顺序固定,不会因为操作系统文件排序差异导致标签错位,尤其是后续要转yolov5格式或者做数据增强时,标签文件是唯一标准。
2.3 show脚本的可视化思路
数据集附带的show.py,作用是把图片和标签对应关系可视化出来,方便确认标注无误。其核心逻辑不复杂,一般是读JSON、取图片路径和标签、用matplotlib或OpenCV画框画字、批量展示。
import json import cv2 import matplotlib.pyplot as plt with open('labels.json', 'r', encoding='utf-8') as f: labels = json.load(f) class_names = ['fire', 'smoke', 'normal'] for item in labels[:6]: img = cv2.imread(item['image_path']) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.imshow(img) plt.title(f"{item['class_name']} (label: {item['label']})") plt.axis('off') plt.show()这段代码从JSON读前6条数据,画出来看一眼类别对不对。关键是确认图片文件名和标签没有错位,尤其是smoke类别,烟雾图片有些拍得很淡,肉眼看容易和正常模糊背景混淆,跑一遍可视化能第一时间发现问题。
数据量虽然只有240张,但组织方式直接影响后续训练代码的写法。我的建议是:拿到数据集后先用show脚本完整跑一遍可视化,并把JSON里的label和class_name对应关系打印出来核对,别跳过这步直接开训。
3. 分类网络选型与训练参数:CNN和yolov5分类分支怎么选
数据准备好了,下一步是选网络结构。摘要里提到了两类项目:CNN分类网络和基于yolov5的分类。对新手来说,这两者的选择取决于你的最终落地场景——是纯图片分类,还是后续要扩展成目标检测。
3.1 什么时候用CNN分类网络
如果任务只是判断一张图里有没有火、有没有烟,输出一个类别,那轻量CNN足够。常见做法是拿ResNet18或MobileNetV3做骨干,加载ImageNet预训练权重,替换最后的全连接层适配3分类任务。
import torch import torch.nn as nn from torchvision import models, transforms from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_dataset = ImageFolder(root='dataset/train', transform=transform) test_dataset = ImageFolder(root='dataset/test', transform=transform) train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=16, shuffle=False) model = models.resnet18(pretrained=True) model.fc = nn.Linear(model.fc.in_features, 3) model = model.cuda() criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) for epoch in range(30): model.train() running_loss = 0.0 for images, labels in train_loader: images, labels = images.cuda(), labels.cuda() optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() print(f"Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}")这里的关键参数:Resize((224, 224))是ResNet的标准输入尺寸,如果你的图片原始分辨率差异大,resize前最好先按短边等比例缩放再中心裁剪,避免直接拉伸变形。batch_size=16在240张图片的小数据集上是合理的,显存不够就减半。lr=0.001配Adam是起步值,训练时盯着loss曲线,如果震荡明显就降到0.0003。
预训练权重在这个场景很关键。240张图从头训练CNN,参数根本学不起来,结果基本是随机水平。加载pretrained=True后,网络已经会提取通用特征,我们只微调最后几层。我的习惯是冻结前几层卷积,只解冻最后两个block和fc层,小数据集上效果更稳。
3.2 yolov5分类分支的配置差异
用yolov5做分类,走的是classify/predict.py和classify/train.py,模型定义在models/yolov5s-cls.yaml里。它的优势是后续无缝衔接检测任务——同一套权重可以再训练检测头,做“先分类后检测”的级联方案。
yolov5分类的训练命令和参数需要注意:
python classify/train.py --model yolov5s-cls.pt --data dataset --epochs 50 --img 224 --batch-size 16--data参数直接指向数据集根目录,它内部要求train和test文件夹存在,且子文件夹按类别命名。--img 224是输入分辨率,yolov5分类支持多种尺寸,但小数据集不建议用太高的原始分辨率,否则过拟合来得更快。yolov5会自动做数据增强,比如随机旋转、色彩抖动、mixup,这对240张的小数据集是实打实的帮助。
对比CNN手写训练循环,yolov5省事但黑匣子程度高一些。我的建议是:如果目标是快速出结果、后续可能转检测,直接上yolov5分类分支;如果想深入理解训练过程、方便做网络结构改动,用CNN手写训练更顺手。
3.3 训练集测试集划分的合理性确认
摘要里说明已经划分好了训练集和测试集,但划分比例是否合理需要自己确认。打开JSON统计一下:
import json from collections import Counter with open('labels.json', 'r', encoding='utf-8') as f: labels = json.load(f) split_counter = Counter(item['split'] for item in labels) class_split = {} for item in labels: key = (item['split'], item['class_name']) class_split[key] = class_split.get(key, 0) + 1 print("总数量:", len(labels)) print("划分统计:", dict(split_counter)) print("各类别划分:", class_split)检查要点:每个类别在训练集和测试集中都要有样本,不能出现测试集里缺某一类的情况。某类只有十来张测试图时,模型在这类上的准确率波动会非常大——可能这轮95%,下轮80%,这种波动不是模型问题,是测试样本太少,后续分析结果时要留意。
3.4 小数据集的过拟合信号识别
240张图跑30个epoch,过拟合几乎是必然的。典型信号是训练loss持续下降,测试准确率却停滞或者下滑。这时候不是换模型能解决的,而是要在数据层面做文章。
常见做法是加数据增强——随机翻转、随机旋转、色彩抖动、mixup,这些操作在PyTorch里写起来不复杂:
train_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])注意RandomCrop(224)的输入是Resize((256, 256)),先放大再随机裁剪相当于加了平移扰动。ColorJitter的三个参数分别控制亮度、对比度、饱和度的扰动幅度,火焰图片偏红、烟雾图片偏灰白,扰动幅度太大会让模型学到错误的颜色关联。
如果加了增强还是过拟合,另一个方向是在网络结构上做文章——把ResNet18换成MobileNetV3Small,或者加Dropout。小数据集上,轻量网络往往比重网络效果更好,因为参数少、学得动。
4. 避坑与常见问题排查:240张图训练容易翻车的五个位置
这个数据集体量小,跑起来快,但正因为快,踩坑的代价被放大了。以下五条是我实际跑分类实验时的血泪经验,每一条都对应一个可复现的现象和解决办法。
4.1 现象:训练loss降到很低,测试准确率只有六成
原因:模型把训练集背下来了,典型过拟合。240张图训练ResNet18,前几轮就过拟合太正常了。
解决:先确认数据增强是否已开启——很多新手代码里transform只写了Resize和ToTensor,等于裸训。加上RandomHorizontalFlip和RandomRotation,再用weight_decay=1e-4约束权重。如果还不行,把网络换成MobileNetV3Small,参数量小一个量级,泛化会好很多。注意验证时评估指标不要用准确率一个维度,顺便看混淆矩阵——火焰和烟雾容易互相误判,因为烟雾图边缘经常带火光。
4.2 现象:测试准确率波动巨大,同一份代码两次结果差10%
原因:样本量太小导致的随机性。测试集可能只有三四十张图,几张错判就能让准确率抖好几个点。
解决:把训练脚本的随机种子固定死,包括Python的random、NumPy的random、PyTorch的manual_seed,确保实验可复现。但即便如此,不同试次之间的波动依然存在,严谨的做法是跑5次取均值,或者用K折交叉验证代替单次划分。240张的数据集做5折交叉验证,每折训练192张、测试48张,结论会扎实得多。
4.3 现象:yolov5分类训练报错“AssertionError: train: No labels in …”
原因:yolov5的--data指向了错误目录,或者目录结构不符合要求。yolov5分类要求--data目录下有train和test子目录,子目录下才是类别文件夹。
解决:先检查命令里的路径是否指向数据集根目录,而不是指向train子目录。再看类别文件夹名是否包含空格或中文——yolov5对类目命名的兼容性有限,统一改成小写英文字母(fire、smoke、normal)。注意标签文件和文件夹名要一致,JSON里写的class_name是什么,文件夹就得叫什么。
4.4 现象:训练loss正常下降,但自己拿真实图片测总是识别成“正常”
原因:这里要分两种情况看。一种可能是测试集图片的拍摄环境和真实场景差异大,属于分布偏移。另一种可能是烟雾和正常的边界本身模糊——比如雾天、蒸汽、灰尘在模型眼里和烟雾特征高度重合。
解决:先拿训练集里的图片测,如果训练集图片都识别不对,那是代码有bug——大概率是预测时transform和训练时不一致,比如训练用了RandomCrop和Normalize,预测时忘了写Normalize。如果训练集能识别对但真实图片不行,那就是数据分布问题,只能收集更多目标场景的图片来补充。
4.5 现象:可视化脚本报了“KeyError: 'bbox'”
原因:JSON标签文件里没有bbox字段,但脚本里有访问item['bbox']的代码。这个数据集是分类任务,本身不含目标框标注。
解决:检查JSON实际字段名,用print(list(labels[0].keys()))打印键名,然后针对性地改脚本。如果后续要转yolov5检测格式,bbox是必需的,分类数据集的图片需要自己标注目标框,这是一项不小的额外工作,做好心理准备。
5. 数据增强与二次标注:让240张图的利用率翻倍
数据集本身只有约240张图,但如果只是直接丢给网络训练,利用率其实很低。下面通过数据增强流程和二次标注思路,让这个小数据集发挥出几倍效果。
5.1 在线增强还是离线增强
小数据集通常两种增强都能做,但侧重点不同。在线增强在训练时实时变换,省磁盘空间且可以无限迭代;离线增强提前生成增强图片,能直观看到数据长什么样,也更方便人工筛选掉过度增强的样本。
import os import cv2 import numpy as np from PIL import Image, ImageEnhance source_dir = 'dataset/train/fire' target_dir = 'dataset_aug/train/fire' os.makedirs(target_dir, exist_ok=True) for fname in os.listdir(source_dir): path = os.path.join(source_dir, fname) img = Image.open(path) flipped = img.transpose(Image.FLIP_LEFT_RIGHT) rot90 = img.rotate(90, expand=True) bright = ImageEnhance.Brightness(img).enhance(1.3) contrast = ImageEnhance.Contrast(img).enhance(1.2) name = os.path.splitext(fname)[0] flipped.save(os.path.join(target_dir, f"{name}_flip.jpg")) rot90.save(os.path.join(target_dir, f"{name}_rot90.jpg")) bright.save(os.path.join(target_dir, f"{name}_bright.jpg")) contrast.save(os.path.join(target_dir, f"{name}_contrast.jpg"))这段离线增强把每张火焰图扩展出4个变体,240张变成1200张。注意Rotate的expand=True参数——旋转90度后图片尺寸会变化,CNN需要固定输入尺寸,所以训练时的Resize仍然必要。ColorJitter的增强幅度是玄学,1.2到1.3是保守值,幅度太大会让火焰颜色失真到不像火。
5.2 用增强数据重训并对比效果
增强之后重新跑一遍训练脚本,重点关注测试集准确率和混淆矩阵的变化。我的经验尺度是:在240张原始数据上,ResNet18跑到五轮左右就会明显过拟合;而用增强后的1000多张图,过拟合能延后到十五轮以后,模型泛化能力明显改善。
from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt y_true, y_pred = [], [] model.eval() with torch.no_grad(): for images, labels in test_loader: images = images.cuda() outputs = model(images) _, predicted = torch.max(outputs, 1) y_true.extend(labels.numpy()) y_pred.extend(predicted.cpu().numpy()) cm = confusion_matrix(y_true, y_pred) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['fire', 'smoke', 'normal'], yticklabels=['fire', 'smoke', 'normal']) plt.show() print(classification_report(y_true, y_pred, target_names=['fire', 'smoke', 'normal']))混淆矩阵能看出具体的错误模式——是火焰被误判成正常,还是烟雾和正常分不开。如果是后者,说明类别本身高度相似,需要更仔细的特征提取,比如换更大输入分辨率,或者用注意力机制。
5.3 二次标注转向目标检测的扩展路径
用完这个数据集做完分类后,如果你需要更进一步做目标检测——比如在监控画面里框出火焰的位置——就需要考虑二次标注了。常见做法是用labelme或labelImg打开原始图片,手动框出火焰和烟雾区域的边界框,然后转成yolov5要求的txt格式。
# 每张图片对应的txt,每行一个目标 # class_id x_center y_center width height(归一化坐标) 0 0.512 0.413 0.215 0.338 1 0.731 0.628 0.184 0.277这里的坐标是归一化到0-1之间的,用像素坐标除以图片宽高得到。分类数据集的图片拍摄角度多样,框标注时注意把火焰和烟雾分开标记,重叠区域以主要目标为准。标注工作量按一张图30秒估算,240张图大约需要两小时。
标注完成后,可以和原数据集的JSON配合,直接套用yolov5的检测训练流程,这样一套数据既做了分类验证,又扩展成了检测基线。
这个小数据集的价值在于流程验证。用240张图跑通分类流程,确认增强策略和验证方式,换大数据集时直接套用同样的流程。从那以后我每次拿到小数据集,都强制走一遍“先可视化确认标注 → 再统计类别分布 → 固定随机种子跑基线 → 加增强对比混淆矩阵”这个流程,能少走不少弯路。
火焰烟雾识别这类场景,数据的采集和标注成本都不低,这份约240张的已标注数据集做算法验证和流程验证完全够用。希望帮到你。
本文还有配套的精品资源,点击获取