
简介森林火灾图像分类数据集面向计算机视觉研究者、深度学习初学者及消防预警系统开发人员包含约13,000张经专业标注的图像按“有火”“无火”两类构建可服务于火灾检测模型训练、图像分类算法验证及森林火情实时监控等场景。压缩包内共2000个文件以1998张JPG图片为主要数据另有1个Python脚本用于数据集可视化1个JSON文件记录类别信息整体大小约338.73MB目录按训练集和测试集存放加载便捷便于直接进行模型训练与泛化评估。通过运行附带脚本可快速预览各分类图片辅助核查数据分布和标注质量。目前已有439人学习/下载。资源同时配套图像分类网络改进与计算机视觉完整项目的讲解链接可帮助读者理解数据增强、网络结构优化等技巧并将分类模型应用于实际火灾预警系统兼顾学术研究与工程实践价值。1. 森林火灾二分类数据集13,000张标注图先看数据再谈模型拿森林火灾图像分类当练手项目的人多半吃过数据不干净的亏要么图片是爬虫抓的没清洗要么标注是猜的训练的时候 Loss 一路下降验证集准确率却像心电图一样来回跳。这个资源解决的正是这个问题——约13,000张已经标注好的图片只分两个类有火和无火训练集和测试集都划分好了连可视化脚本都给你备好。我拿到手先做的事不是急着跑模型而是把它的标注文件打开看了一遍再随机抽了几张图确认标签和内容对得上。整个过程下来感受就一句话这个数据集把「图像分类」最基础但最关键的环节——数据质量验证——替你省掉了大半。适合三类人刚接触图像分类、想拿一份干净数据跑通 ResNet 或 EfficientNet 流程的新手做课程设计需要一套带标签的二分类数据的学生以及想把火灾检测做成演示 Demo但没时间收集清洗数据的开发者。2. 数据格式与目录结构json 标注、编码规律和图片命名2.1 下载解压后先做的三件事拿到资源压缩包别急着解压后直接扔给训练脚本。我习惯先做三件事看顶层目录结构、读 json 标注文件、随机抽几张图人工核对。这个数据集的目录结构在描述里只提了「划分了训练集、测试集存放各自的同一类数据图片」但实际使用中你需要确认它到底是「分目录存放」train/fire、train/nofire还是「用一个 json 记录图片路径和标签」。这两种组织方式对应的加载代码完全不同。常见做法是解压后第一件事执行tree -L 2看目录树然后打开标注文件确认类别字段。标注文件的格式也需要留意。有的数据集用 json 存一个字典key 是图片文件名value 是类别字符串有的则是嵌套字典先按 train/test 分组再按 fire/nofire 分组最后才是文件名。这个资源的摘要写的是「分类个数【2】有火、无火【具体查看json文件】」说明类别定义、划分逻辑都在 json 里。所以正确姿势是先把 json 读进来打印它的 keys看清楚层级再写加载逻辑。直接盲写os.listdir()然后硬编码路径很容易翻车。第三件事是人工核对。这一步很多人跳过但我的一条血泪经验是标注数据集里永远可能存在少量错标或边界情况比如火光很弱的图被标成无火或者夕阳图被标成有火。抽 30 张图出来每张图 2 秒钟扫一眼浪费的时间不到一分钟但能帮你提前预判模型训练时准确率为什么卡在某个值上不去。import json from collections import Counter with open(annotation.json, r, encodingutf-8) as f: data json.load(f) print(type(data)) # 判断是 dict 还是 list print(list(data.keys())[:5]) # 看顶层字段 print(data[train].keys()) # 看 train 下的结构 # 常见结构1: data[train][fire] [011131.jpg, ...] # 常见结构2: data[train] [{img: 011131.jpg, label: fire}, ...]这段代码的核心目的是「探结构」不是「加载数据」。很多新手一上来就写完整的 Dataset 类结果 json 结构与预期不符跑起来报 KeyError又回头改类——不如先花三十秒把结构摸清楚。参数上要注意encodingutf-8Windows 环境如果缺这行读含特殊字符的 json 会直接抛 UnicodeDecodeError。2.2 图片文件名的编码规律能当成一个低成本数据增强观察资源正文里列出的样例文件名011131.jpg、011134.jpg、011124.jpg、011122.jpg、011156.jpg、011405.jpg、011350.jpg、011366.jpg、011401.jpg、011425.jpg。这些编号不是乱序的细看有明显的时间或采集序列特征——11000 段和 11400 段数值波动不大说明相邻编号大概率来自同一拍摄场景的连续帧或邻近地点。这个规律对训练有实际意义如果你不做处理直接把前后相邻的帧同时分进训练集模型可能学到的是「同场景背景」而不是「火」本身。我一般会做一步轻量的处理把文件名的数值部分解析出来设定一个间隔阈值比如同一序列内间隔小于 50 的图片只保留一张进训练集其余放进验证集。这种做法叫「按采集序列切分」它比随机划分更能真实反映模型在未见场景上的表现。代码实现也很简单import os img_dir train/fire all_files sorted(os.listdir(img_dir)) seqs {} for f in all_files: seq int(f.split(.)[0][:3]) # 取编号前三位作为序列号 seqs.setdefault(seq, []).append(f) keep, drop [], [] for seq, files in seqs.items(): files sorted(files) keep.extend(files[::15]) # 每个序列内步长15采样 drop.extend([f for f in files if f not in keep]) print(f保留 {len(keep)} 张剔除 {len(drop)} 张可能强相关的相邻帧)这段逻辑的原理是序列号跳变意味着场景切换而序列号内连续意味着同一场景使用步长采样能把场景相关性打散。参数15是经验值如果单序列图片特别多可以调到 30如果每个序列只有十几张就调到 5。这一步做完你的训练集不会因为「同场景前后帧都被模型见过」而虚高准确率。这也是这个数据集比很多爬虫数据集强的地方——它从源头做了标注你只需要在训练前把序列相关性处理掉模型泛化能力会明显提升。3. 从零跑通一个二分类训练ResNet 基线、json 加载和训练验证3.1 数据划分的合理性再验证训练集和测试集各占比多少这个资源已经划分了训练集和测试集但划分比例不会写在标题里得自己从目录或 json 里数。以二分类 13000 张的总量估算常见划分是 8:2 或 9:1那么训练集大约 10400 张到 11700 张测试集大约 1300 张到 2600 张。需要确认两件事第一训练集和测试集的类别分布是否接近 1:1如果「有火」类占了 80% 而「无火」类只有 20%那么模型直接全部预测「有火」就能拿到 80% 准确率这个指标没有参考价值第二测试集和训练集的图片是否来自不同时间段或不同区域如果划分是随机的那么同场景的后续帧可能同时出现在两边评估结果会偏乐观。from collections import Counter train_counts Counter([data[train][img] for img in data[train]]) test_counts Counter([data[test][img] for img in data[test]]) total_train sum(train_counts.values()) total_test sum(test_counts.values()) print(f训练集总数: {total_train}, 类别分布: {dict(train_counts)}) print(f测试集总数: {total_test}, 类别分布: {dict(test_counts)}) print(f训练集占比: {total_train / (total_train total_test):.2%})这个统计结果直接决定你怎么设定评估指标。如果类别分布不均衡那么只看准确率是不够的得加上 Precision、Recall、F1-Score并且按类别分别看召回率——火灾检测场景里「有火被漏报」False Negative的代价远高于「无火被误报」False Positive所以召回率比精确率更重要。参数上有一个容易忽略的点data[train][img]这种取值方式只适用于 json 是「文件名 → 标签」平铺结构的场景嵌套结构需要改成data[img][label]到时候按实际结构调整即可。3.2 用 YOLOv8 的分类模块还是独立分类网络最近关于「yolov8训练自己的数据集」的搜索热度很高这里给一个明确建议如果只是想做一个「有火/无火」二分类判断YOLOv8 的yolov8n-cls.pt和原版 ResNet18 都够用。区别在于YOLOv8 的分类模式提供了完整的训练管线命令行直接跑不需要自己写训练循环非常省事而 ResNet18 需要自己写 DataLoader、损失函数、优化器但可控性更强方便往里面塞各种改进模块。我的建议是第一次跑通流程用 YOLOv8 的cls train它自带数据增强、学习率调度和验证逻辑等换到自己网络的消融实验时再切到 ResNet18 手动编码。拿 YOLOv8 举例数据准备好之后命令非常简单yolo classify train \ modelyolov8n-cls.pt \ data./dataset \ epochs30 \ imgsz224 \ batch32 \ lr00.001 \ patience10几个参数的含义需要说清楚。modelyolov8n-cls.pt表示用 ImageNet 预训练的 nano 版本分类模型它的参数量最小训练速度最快适合 13000 张这种规模的数据集data./dataset指向的目录结构必须是train/fire、train/nofire、val/fire、val/nofire这种形式YOLOv8 会自动识别imgsz224是输入分辨率224 是分类任务的标准配置暴力上到 512 不一定会提升精度反而会让训练时间变长patience10表示验证集指标连续 10 轮不提升就早停——这个参数很重要二分类数据集通常 20 轮以内就收敛了不设定早停机制容易过拟合。3.3 训练时最先看的三个指标Loss、Top1 Acc、Confusion Matrix训练日志里输出很多字段但不要眉毛胡子一把抓。先看三样东西训练 Loss、验证 Top1 Acc、混淆矩阵。训练 Loss 持续下降是模型在学的信号验证 Acc 如果停滞或下降说明过拟合了混淆矩阵则是后期分析用的先不用每轮都看。调试代码示例里可以自己加载训练好的权重做一次测试集推理。from ultralytics import YOLO model YOLO(runs/classify/train/weights/best.pt) results model.val(data./dataset, splittest, imgsz224) print(fTop1 Acc: {results.top1:.4f}, Top5 Acc: {results.top5:.4f})splittest是关键参数因为 YOLOv8 默认只验证 val 目录而./dataset根目录下如果没有 val 文件夹就得显式指定用测试集做最终评估从复用角度讲这像一个后悔药——训练完再评估一次测试集判断这个模型能不能真正落地。我自己有个习惯训练过程中确认验证集 Top1 Acc 超过 0.95才把测试集结果打印出来避免反复用测试集调参把它变成第二个验证集。4. 图像分类避坑指南13,000 张数据里最容易踩的五个坑4.1 坑一json 里的类别名和文件夹名不一致现象YOLOv8 训练报错AssertionError: Class names not matching或自己写的 Dataset 类加载时 KeyError。原因json 里记录的标签是fire/nofire而文件夹名可能是with_fire/without_fire两边字符不一致框架的类别映射表对不上。解决训练前写一段脚本读 json 后把类别名做一个映射统一找一个基准。我一般把 json 作为事实来源重命名文件夹或改数据集加载代码来匹配它而不是反过来。在 YOLOv8 的data.yaml中显式指定names避免自动读取时猜错。4.2 坑二天气和光照的变化被模型当成了「火」现象训练时验证集准确率 0.98上线后拿到黄昏时段的图片大量误报为「有火」。原因数据集里「有火」图片很多来自火灾现场背景是浓烟和暗色环境「无火」图片多为晴天绿色森林。模型可能学到了「暗色调 烟雾感 → 有火」这种捷径。解决把测试集按场景简单分组比如按照片文件名的序列号区分不同采集时段分别计算准确率。如果黄昏时段准确率明显偏低就得考虑在训练时加入色彩扰动ColorJitter或者引入外部负样本做补充。这一步不是调参能救的得回到数据层面补漏。4.3 坑三相邻帧泄漏测试集指标虚高现象训练跑 15 轮验证集准确率就 0.97看起来完美但部署到新视频流上识别率掉到 0.85。原因随机划分数据集时同一监控机位连续拍摄的帧被同时分到训练集和测试集模型在训练时「见过」测试集图片的相似场景理论上泛化能力虚高了。解决用文件名里的序列号做组划分。拿编号里的「一段」当作一个组比如011131.jpg中011是拍摄段131是帧序号那么按011、114这种前缀划分训练/测试同段帧只能进其中一边。具体按 8:2 的比例随机挑段分配即可。4.4 坑四有火和无火的边界情况没有定义清楚现象标注人员把「远处烟囱冒白烟」和「火灾初期小范围明火」都标成了有火模型既学不清楚火的特征又把大量白烟误报为火。原因二分类标注缺少明确的边界规则比如火焰像素面积占比、烟雾形态、光照条件是否纳入判断。资源里的 json 只给了类别标签没有给标注规范。解决训练前自己定一条边界规则明火可见像素超过整张图的 0.5% 才标为有火低于则标为无火。把这条规则写进数据清洗脚本抽 100 张人工复核一遍标注一致性。如果发现大量边缘样本建议单独抽出来当「难例集」训练时做重采样。4.5 坑五硬生生用 224×224 输入喂原始大图现象Loss 不下降或者训练每轮耗时特别长验证准确率一直卡在 0.5 附近。原因原始图片可能是 1920×1080 甚至更高分辨率直接 resize 到 224×224 会把火焰细节全抹掉相反如果原图本身只有 480×320放大到 224×224 会模糊。解决先统计数据集中图片的尺寸分布把训练集和测试集的尺寸统一到一个合理值。显示统计信息的代码很简单from PIL import Image import os img_dir train/fire sizes set() for f in os.listdir(img_dir)[:500]: with Image.open(os.path.join(img_dir, f)) as im: sizes.add(im.size) print(sizes)如果尺寸集中在 640×480 附近imgsz224没问题如果原图都是 1920×1080建议imgsz320起步。分类任务不是越大越好但也不要因为 resize 抹掉了关键特征这一条血泪经验换个说法就是模型没学好很多时候不赖网络赖输入数据。5. 把模型从「能跑」变成「可靠」混淆矩阵、召回率分析与单类调优5.1 不只看 Acc把混淆矩阵打印出来二分类问题最常出现的情况是 Acc 在 0.9 以上但实际使用起来很别扭因为「无火误报为有火」和「有火漏报为无火」这两类错误的影响完全不同。火灾检测场景中漏报一次可能直接导致火势蔓延误报最多是让值班人员多跑一趟。所以训练完成后第一件事是打印混淆矩阵看看四类计数分别是多少。from sklearn.metrics import confusion_matrix, classification_report import numpy as np y_true np.load(test_labels.npy) # 真实标签0无火 1有火 y_pred np.load(test_preds.npy) # 模型预测标签 cm confusion_matrix(y_true, y_pred) print(cm) print(classification_report(y_true, y_pred, target_names[nofire, fire]))逻辑说明混淆矩阵的行是真实标签列是预测标签右上角那个数字是有火被漏报的数量左下角是无火被误报的数量。看classification_report的recall列如果fire的召回率低于nofire说明模型偏保守漏掉了不少真实火情。参数说明target_names传入可读标签名方便直接看报告如果两个类的样本数不均衡额外关注support列样本少的类别指标波动大是正常现象。5.2 样本不均衡时的单类调优假设统计发现fire类只有 3500 张而nofire类有 9500 张那么模型很自然会偏向学无火的特征。解决办法有三个第一class_weight参数设置balanced让少数类样本的 loss 贡献更大第二对少数类做数据增强比如对火灾图片做随机旋转 15 度、水平翻转、亮度抖动第三如果增强后效果还不行就去资源附带的博客链接里找网络改进思路比如给 ResNet 加注意力模块或改用 EfficientNet 的更强预训练权重。一个比较实用的参数参考是当测试集上 fire 召回率低于 0.9 时建议优先调整class_weight而不是网络结构。数据显示约 13000 张的二分类规模加注意力模块带来的提升通常只有 1~2%而类别加权能一次性提 5%。理由很简单模型首先需要一个像素分配的大致平衡然后才有精力去学细微特征。5.3 手动复验随机抽 20 张测试图做目检指标最终要靠目检兜底。写一个小脚本从测试集里随机抽 20 张图把每张图的真实标签和预测标签打印出来手动看一遍错误的图是什么形态。这个习惯能把上面所有坑都兜住如果抽到的错误样本是黄昏图被误判说明光照域问题如果错误样本是近距离特写火焰但颜色偏暗说明特征尺度问题。从那以后每次训练完我都会强制走一遍「统计分布 → 打混淆矩阵 → 抽图目检」的流程哪怕数据集已经跑过好几遍也不会省毕竟对做火灾识别的人来说任何一个漏报的代价都可能是真实的火情。希望这个流程能帮你在类似任务上少走几步弯路把精力留在模型优化本身。本文还有配套的精品资源点击获取