简介:面向无人机森林巡检与火灾监测场景的图像分类数据集,专门聚焦森林桩燃烧状态识别,包含燃烧、未燃烧两个类别。数据已按训练集与测试集划分并以文件夹形式保存,训练集约两万张图片、测试集约八千六百张图片,既适合直接接入图像分类网络训练,也能用于分类流程的数据准备,计算机视觉入门者和研究团队都可快速上手。资源共两千个文件,主体为样本图像,另含类别字典文件和可视化脚本,压缩包整体约八百零八兆。目录结构清晰,按训练集与测试集组织,运行可视化脚本即可快速浏览样本分布,便于核对标注与构建基准实验。已有八十六人学习,适合需要现成燃烧识别数据来完成分类模型验证、算法对比或课程设计的开发者。
1. 无人机视角森林桩燃烧识别数据集:为什么说它是分类任务里最不该跳过的一环
做森林防火图像识别的人大概率都经历过这种尴尬:网上能找到的公开数据集要么是卫星图、要么是地面监控拍的火点,真正贴着无人机俯拍视角、专门针对“树桩燃烧”这个小目标的分类数据集少得可怜。而这个项目给出的正是这样一个大型无人机视角下的森林桩燃烧图像识别数据集——2 分类(燃烧、未燃烧),训练集 2 万张,测试集 8600 张,目录按训练/测试划分好,附带类别字典 JSON 文件。换句话说,你下载下来不需要再花时间整理目录结构,直接就能喂给 YOLOv5 的 classify 分支或者任意一个 CNN 分类网络。适合谁用?三类人:一是做森林火灾预警但卡在数据清洗上的工程师,二是拿 YOLOv5 做分类训练想省掉自己爬图、对图、分目录时间的学生,三是想快速验证某个分类网络在无人机下视角下效果的算法岗。这份资源把数据准备、标签映射、训练验证的路径都铺好了,剩下的就是你自己的网络调参。
2. 数据集结构与类别字典:训练/测试划分和标签映射的两大前提
2.1 目录树与文件规模:先看清 2 万张训练图和 8600 张测试图怎么摆
下载解压之后,数据集根目录下的核心结构是这样的(以实际文件为准,我这里列出最常见的组织方式):
dataset/ ├── data/ │ ├── train/ │ │ ├── burning/ │ │ │ ├── resized_frame8368.jpg │ │ │ ├── resized_frame8364.jpg │ │ │ └── ... │ │ └── not_burning/ │ │ ├── resized_frame5418.jpg │ │ └── ... │ └── test/ │ ├── burning/ │ └── not_burning/ ├── classes.json # 类别字典文件 └── show.py # 可视化脚本这个目录结构有一个很关键的优点:文件夹名就是类别名。对 YOLOv5 分类训练来说,训练脚本正是按照data/train下每个子文件夹的名字来生成类别标签的,所以你不需要额外写标签文件,文件夹划分本身就是监督信号。
从规模上看,训练集 2 万张、测试集 8600 张,这个比例大约 7:3,对于二分类任务来说训练数据量已经足够支撑一个从零训练的轻量网络,更不用说做迁移学习了。每个类别在训练集和测试集中各自有独立的文件夹,避免了交叉混杂导致的数据泄漏(data leakage)。这一点容易被忽视——很多人下载到的数据集所有图片堆在一起,自己写脚本随机划分,如果按文件名排序前 80% 做训练,很可能把同一个视频帧序列里的连续帧全部切进训练集,测试集失去了独立性。这个数据集直接把划分做好了,就省掉了这一步。
2.2 类别字典 JSON:从标签名到训练代码的最小闭环
数据集里附带了一个类别字典 JSON 文件,内容一般长这样:
{ "0": "burning", "1": "not_burning" }别小看这个文件。它实际上是你在训练代码里做标签映射的“唯一事实来源”(source of truth)。我一般会在拿到数据集的第一时间就把这个 JSON 读进脚本,生成反向映射:
import json with open('classes.json', 'r') as f: classes = json.load(f) # 得到 {"0": "burning", "1": "not_burning"} # 反向映射得到 {"burning": 0, "not_burning": 1} id2label = {int(k): v for k, v in classes.items()} label2id = {v: k for k, v in id2label.items()} # 打印出来确认类别顺序 print(id2label) print(label2id)逻辑说明:上面的代码先把 JSON 里的字符串键转成整数键,得到id2label,再反转得到label2id。这样做的目的是保证训练时网络的输出索引和文件夹名之间的对应关系完全一致。参数说明:classes.json的键值结构决定了label2id的映射方向,如果你的类别字典文件字段名不同(比如键是name),把classes.items()换成对应的字段读取即可。
为什么强调类别字典?两个原因。第一,YOLOv5 分类训练时--data参数指向的是数据集根目录,脚本会按照os.listdir的顺序扫描训练目录下的子文件夹,这个顺序和 JSON 里的顺序在大部分情况下一致,但如果你删改过文件夹,顺序可能变化,导致类别索引错位。第二,模型推理阶段你拿到的只是[0, 1]的索引,要输出“燃烧”还是“未燃烧”的字符串,就得靠这个字典反查。没有它,你的模型输出就是一堆没人看得懂的数字。
这里有一个值得注意的细节:如果这份数据集是用于 YOLOv5 分类,那么classes.json更像是给你做代码对接用的辅助文件。因为 YOLOv5 分类模式会根据训练集目录下的文件夹名自动建立类别映射,并生成classification_classes.json。但如果你用的是自己写的 PyTorch 分类脚本,那就必须显式读入classes.json来固定类别顺序,避免每次训练时类别索引随机排列。
3. 把那 2 万张图喂进模型:YOLOv5 分类与通用 CNN 两条训练路线
3.1 YOLOv5 classify 训练:一条命令跑通,但有个参数容易错
YOLOv5 仓库本身自带分类训练支持,不需要额外装别的库。在yolov5目录下直接用classify/train.py就能启动训练。假设数据集解压后的路径是../dataset,训练命令如下:
python classify/train.py \ --model yolov5s-cls.pt \ --data ../dataset/data \ --epochs 50 \ --img 224 \ --batch-size 64 \ --name forest_burning_cls逻辑说明:--data指向的是包含train和test子目录的父目录,而不是指向train本身;--model指定预训练模型权重,yolov5s-cls.pt是在 ImageNet 上预训练过的分类模型,能显著加快收敛。参数说明:--img 224会把输入图片缩放到 224×224,--batch-size 64根据显存调整,如果你的显卡只有 6 GB,建议降到 32;--epochs 50对 2 万张训练图来说已经足够,二分类任务通常在 20 轮左右就开始收敛。
这里有一个高频翻车点:--data参数传错了层。很多人会把路径直接指到../dataset/data/train,结果 YOLOv5 把所有子文件夹的名字(burning 和 not_burning)当成了两条样本路径来读,直接报错。记住,YOLOv5 分类模式期望的目录结构是data/train/类别名/*.jpg,传参时给到data这一层就对了。
训练过程中会输出每个 epoch 的train_loss、val_loss和accuracy,我一般盯着val_loss看,如果连续 5 个 epoch 没有下降,说明学习率设置偏大或者模型容量不够。YOLOv5 默认初始学习率是 0.01,配合 cosine 退火,50 轮基本能跑出 92% 以上的准确率。
训练结束后,权重保存在runs/train-cls/forest_burning_cls/weights/best.pt。推理验证命令:
python classify/predict.py \ --weights runs/train-cls/forest_burning_cls/weights/best.pt \ --source ../dataset/data/test/burning/resized_frame8368.jpg逻辑说明:这条命令把测试集里的一张燃烧图片喂给模型,输出预测类别和置信度。参数说明:--source可以是单张图片路径,也可以是整个目录,YOLOv5 会自动遍历。
3.2 通用 CNN 分类路线:TIMM 库与自定义训练循环的两点取舍
如果你不想被 YOLOv5 框架绑死,或者想换一个更新的骨干网络(比如 ConvNeXt、EfficientNetV2),TIMM 是更灵活的选择。用 TIMM 加载预训练模型只需要几行代码:
import timm import torch import torch.nn as nn # 加载预训练模型,修改分类头 model = timm.create_model('efficientnet_b0', pretrained=True, num_classes=2) model = model.to('cuda') # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5)逻辑说明:timm.create_model中的num_classes=2会替换掉模型原本的 1000 类分类头,pretrained=True加载 ImageNet 权重作为初始化。参数说明:weight_decay设为1e-5防止过拟合;学习率从1e-4起步,配合CosineAnnealingLR调度器可以在 30 轮内达到不错的效果。
数据加载部分,直接用 PyTorch 的ImageFolder就能读取我们前面说的目录结构:
from torchvision import datasets, transforms train_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) test_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 = datasets.ImageFolder('../dataset/data/train', transform=train_transform) test_dataset = datasets.ImageFolder('../dataset/data/test', transform=test_transform)逻辑说明:ImageFolder会按照子文件夹名称自动生成类别标签,这正是前面提到的目录结构带来的便利。参数说明:RandomHorizontalFlip和RandomRotation是数据增强,对无人机俯拍图来说,水平翻转是合理的,因为树桩燃烧的方位不固定;Normalize的均值方差用的 ImageNet 统计值,因为预训练模型是在 ImageNet 上训练的,输入分布需要对齐。
这里需要做选择:如果追求快速复现,用 YOLOv5 的 classify 脚本最省事;如果要做网络结构对比实验(比如 EfficientNet vs ResNet vs ConvNeXt),就用 TIMM。两个方案在当前数据集上都能直接跑通。
3.3 评测指标:别只看 accuracy,还要看混淆矩阵和类别置信度分布
二分类任务里,整体 accuracy 很可能虚高。假设测试集 8600 张里 80% 是未燃烧,你一个把所有图片都判成未燃烧的“傻瓜模型”也能有 80% accuracy。所以我在评估时一般固定看三个东西:混淆矩阵、每一类的 precision/recall、以及错误样本的置信度分布。
from sklearn.metrics import confusion_matrix, classification_report import numpy as np y_true, y_pred = [], [] model.eval() with torch.no_grad(): for images, labels in test_loader: images = images.to('cuda') outputs = model(images) _, predicted = torch.max(outputs, 1) y_true.extend(labels.cpu().tolist()) y_pred.extend(predicted.cpu().tolist()) print(confusion_matrix(y_true, y_pred)) print(classification_report(y_true, y_pred, target_names=['burning', 'not_burning']))逻辑说明:confusion_matrix返回一个 2×2 矩阵,对角线是正确分类的数量,非对角线是错分情况。classification_report输出每一类的 precision、recall 和 F1-score。参数说明:torch.max(outputs, 1)取每个样本预测概率最大的类别索引;test_loader的 batch size 建议设置 128 以上,加快推理。
实际使用时,我还会额外统计“燃烧被误判为未燃烧”的数量,这是森林防火场景中最致命的错误——漏报比误报严重得多。如果发现漏报率高,优先考虑调整分类阈值,而不是盲目增加训练轮数。
4. 森林桩燃烧数据集的分类避坑指南:五个来回翻车换来的教训
4.1 数据集里混入了大量相似帧,测试集准确性被高估
现象:训练时 loss 下降很快,测试 accuracy 一度到了 95%,但部署到新视频上准确率掉到 80% 以下。
原因:无人机视频抽帧得到的连续帧高度相似,训练集和测试集如果来自同一段视频的不同帧,模型实际上是在“背题”,而不是在学习“燃烧”这个概念的泛化特征。相似帧导致的信息重叠让评估结果虚高。
解决:我拿到数据后,先按文件名排序,隔 N 帧抽样检查训练集和测试集之间是否有重复或极度相似的图片。如果发现相似帧跨集合存在,最粗暴但有效的办法是剔除测试集中与训练集高度相关的帧,或者按视频片段划分数据(一个视频的帧只能出现在一个集合里)。这个数据集的划分如果是按帧序列切割的,你自己训练前最好多留个心眼。
4.2 图片尺寸不统一,训练中途报错 batch 维度不匹配
现象:训练到一半,某个 batch 报Expected 3D (unbatched) but got 4D tensor之类的维度错误。
原因:数据集中图片经过resized_前缀处理,但仍有部分图片的通道数不是 3(比如灰度图被错误存放),或者 EXIF 旋转信息导致实际读入的宽高和预期不一致。
解决:在数据加载阶段强制统一格式,不要依赖数据集本身。在__getitem__里加上img = img.convert('RGB'),并把Resize放在ToTensor之前。我的习惯是写一个数据清洗脚本,先遍历全部图片,统计尺寸和通道数,把异常图片单独列出来检查。
4.3 类别字典里的顺序和文件夹扫描顺序不一致,训练标签错位
现象:训练完做推理,发现模型把not_burning的图片识别成burning,而且置信度很高,但训练准确率是正常的。
原因:classes.json里写的是"0": "burning", "1": "not_burning",但某些框架会按照os.listdir()的字母序扫描文件夹,burning字母序在not_burning前面,所以burning=0正好一致。但如果文件夹名改成中文或者加了数字前缀,字母序就完全变了。
解决:不要依赖文件夹名和字典的“巧合一致”。训练前写一段代码,读取classes.json后和os.listdir得到的类别列表做交叉验证,不一致就报错。这一点在 YOLOv5 的 classify 模式里尤其重要,因为它自动生成的classification_classes.json不一定和你手头的字典一致。
4.4 训练时显存 OOM,调整 batch size 后准确率反而下降
现象:用batch-size 128训练,显存溢出;改成batch-size 32后,同样 50 轮训练,准确率低了 3 个百分点。
原因:batch size 变小,梯度估计的噪声变大,同时学习率没有相应调整。YOLOv5 的优化器是 SGD,它对 batch size 变化很敏感。
解决:如果显存只能支持小 batch,可以保持 batch size 不变,启用梯度累积(gradient accumulation)。在 PyTorch 里就是每 N 个 batch 更新一次权重,效果等同大 batch。另外一个办法是降低输入尺寸--img 192,对二分类来说 224 缩到 192 精度损失可以接受。
4.5 测试集 8600 张评估结果可信,但部署到无人机实时视频上速度达不到要求
现象:离线评估 F1 达到 0.93,部署到边缘设备(比如 Jeston Nano)上推理速度只有 8 FPS,无法满足实时巡检。
原因:模型参数量大、输入分辨率高,边缘设备算力不足。分类准确率只是训练阶段的目标,部署阶段还要考虑吞吐量。
解决:先剪枝或换轻量网络,比如把 EfficientNet-B0 换成 MobileNetV3 或者 ShuffleNetV2,输入尺寸降到 192。然后用 TensorRT 做 FP16 量化,推理速度通常能翻 2 倍以上。一个 2 分类问题根本不需要大模型,模型小反而更稳。
5. 可视化与数据验证:show 脚本和人工抽查如何避免黑匣子训练
5.1 运行资源自带的 show.py,把图片和标签对应起来
数据集资源里提供了一个 show 脚本,用途是可视化数据集中的图片和标签。直接运行:
python show.py --data ../dataset/data --num_samples 10逻辑说明:这个脚本会从训练集和测试集中各抽样若干张图片,展示图片路径和它对应的类别标签。参数说明:--num_samples控制抽样数量,我一般先抽 10 张看格式,再抽 50 张细看类别边界。如果你是第一次接触这份数据,跑一下 show 脚本能直观感受到“燃烧”和“未燃烧”两个类别的视觉差异——树桩燃烧通常伴随火焰和烟雾,未燃烧则只是普通的枯木桩。
为什么强调这一步?因为分类任务里标签噪声是最难发现的。你永远不知道上一个制作数据集的人有没有把一张烟雾弥漫但没火苗的图标成“未燃烧”。人工抽查是发现这种问题的最直接方式。
5.2 训练完成后用测试集可视化预测结果,看错误样本长什么样
光看 accuracy 数字是不够的,我每次训练完都会把测试集里预测错误的图片单独挑出来保存成一张拼图,用肉眼看一遍:
import matplotlib.pyplot as plt import torchvision.utils as vutils misclassified_images = [] for images, labels in test_loader: images = images.to('cuda') outputs = model(images) _, predicted = torch.max(outputs, 1) for i in range(len(labels)): if predicted[i] != labels[i]: # 把错误样本和真实标签、预测标签一起保存 misclassified_images.append((images[i].cpu(), labels[i].item(), predicted[i].item())) # 取前 16 个错误样本画成网格 grid = vutils.make_grid([img for img, _, _ in misclassified_images[:16]], nrow=4) plt.imshow(grid.permute(1, 2, 0)) plt.show()逻辑说明:这段代码遍历测试集,把所有预测错误的图片、真实标签、预测标签收集起来,用make_grid拼成 4×4 网格可视化。参数说明:nrow=4控制每行放几张图,permute(1, 2, 0)把 PyTorch 的 C×H×W 通道顺序转成 matplotlib 需要的 H×W×C。如果你发现错误样本集中在某一类特定的光线条件下,比如傍晚逆光的树桩,就可能需要额外采集这个场景的数据,或者在数据增强里加强亮度扰动。
从我个人做项目的经验看,错误样本可视化是“低成本高回报”的步骤。它能让你看清模型的决策边界在哪里,是图像太暗、目标太小、还是类别本身存在歧义——这些信息是任何 loss 曲线都给不了你的。
6. 进阶用法:把这份数据集用在 YOLOv8 分类和半监督标签校验上
6.1 迁移到 YOLOv8 分类的目录结构与命令行参数
如果你已经在用 YOLOv8,而且不想在 YOLOv5 的 classify 分支上重复折腾,迁移成本其实很低。YOLOv8 同样支持图像分类,它对数据集目录结构的要求是train/类别名/*.jpg和val/类别名/*.jpg。由于这份资源已经把train和test分好了,你只需要把test重命名为val:
cd dataset/data mv test val然后安装 ultralytics 库,运行以下命令:
yolo classify train \ model=yolov8n-cls.pt \ data=../dataset/data \ epochs=50 \ imgsz=224 \ batch=64 \ name=forest_yolov8逻辑说明:yolov8n-cls.pt是 YOLOv8 的最小分类模型,参数量只有 3.2M,在 224×224 输入下推理速度非常快。如果你觉得精度不够,换成yolov8s-cls.pt或yolov8m-cls.pt即可。参数说明:data参数和 YOLOv5 一样,指向包含train和val的父目录;batch=64对 2 万张训练图来说,50 轮大约需要十几分钟(取决于显卡)。
yaml 文件方式也支持。如果你想显式指定类别,写一个forest.yaml:
path: ../dataset/data train: train val: val names: 0: burning 1: not_burning这样做的好处是,当数据集文件夹名需要统一替换时,用names字段可以保持类别顺序稳定,不会受目录扫描顺序影响。
6.2 半监督标签校验:用置信度阈值筛选出“可疑样本”
最后一个技巧,是我做过几个数据集项目后养成的习惯——用模型本身来给数据做“反向体检”。在这个数据集上,训练好的模型对每张测试图都会输出一个置信度分布。把置信度介于 0.4 到 0.6 之间的样本标出来,这些就是模型认为“拿不准”的图片,也是最可能标签有问题的图。
import numpy as np likely_mislabeled = [] model.eval() with torch.no_grad(): for images, labels in test_loader: images = images.to('cuda') probs = torch.softmax(model(images), dim=1) max_probs, predicted = torch.max(probs, dim=1) for i in range(len(labels)): if 0.4 < max_probs[i].item() < 0.6: likely_mislabeled.append({ 'label': labels[i].item(), 'pred': predicted[i].item(), 'confidence': max_probs[i].item() }) print(f"可疑样本数量: {len(likely_mislabeled)}") for item in likely_mislabeled[:10]: print(item)逻辑说明:softmax把输出的 logits 转成概率,max_probs取每个样本的最高概率值。如果最高概率都只有 0.4~0.6,说明模型在两个类别之间摇摆,这类样本要么是特征不明显,要么是标签错误。参数说明:阈值区间可以根据你的任务调整,如果整体置信度偏高,可以收紧到 0.5~0.65;如果偏低,就放宽到 0.3~0.7。
从那以后,我每次拿到新的图像分类数据集,都会强制走一遍这个流程:先跑 show 脚本人工看 50 张,再训练一个基线模型做错误样本可视化,最后用置信度阈值筛选可疑标签。这个过程看起来多花了半天时间,但帮我避开了很多次“准确率高但实际不可用”的坑。希望这份无人机视角的森林桩燃烧识别数据集,也能帮你少走几步弯路。
本文还有配套的精品资源,点击获取