尧图精选

CNN火灾识别实战:数据集整理与模型训练全流程解析

🕒 发布时间:2026/10/1 4:16:20 📁 来源:尧图网络
简介这份基于CNN深度学习的火灾识别项目包适合入门深度学习的开发者配合PyTorch环境使用。资源内含数据集、Python脚本和运行说明整体共250个文件其中图片约244张png/jpg另有3个Python脚本与3个txt文本压缩包大小173.55MB。数据集已做预处理通过在较短边增加灰边将图片变为正方形并通过旋转角度进行增强充分照顾了后续训练需要。01数据集文本生成制作.py负责读取各类别图片路径与标签02深度学习模型训练.py完成训练集与验证集的读取训练并保存模型和记录每个epoch的验证集损失与准确率03pyqt_ui界面.py则提供一个可视化界面可加载本地图片进行火灾识别。对于希望快速体验完整CNN训练流程、并借助界面验证效果的读者来说这份项目提供了从数据整理到模型训练再到交互识别的闭环参考。目前已有170人学习下载适合作为基于PyTorch的图像分类入门练习。1. 火灾识别为什么非 CNN 不可先搞清这套方案解决的是哪类问题晚上八点值班室的告警系统因为窗外路灯的黄色光斑误报了十七次而真正厨房冒烟的时候它却一声不吭。这是火灾识别项目最常见的翻车现场——传统基于颜色阈值、亮度跳变和烟雾浓度传感器的方案在复杂背景里几乎没办法把“光”和“火”分开、把“雾霾”和“烟”分开。基于 CNN 的火灾识别要解决的就是这个它不再靠人去设计“火是什么颜色”“烟是什么纹理”这类规则而是用一堆带标签的图像训练卷积神经网络让模型自己从像素里学出“火、烟、正常环境”的判别边界。这个项目标题里直接带了“含数据集.zip”意味着你不需要从零收集图像数据已经打包好了。你真正要做的是把数据集整理干净、把 CNN 管线跑通、把准确率和误报率调到能用的程度。这套方案适合正在做消防告警网关、厂区/楼道智能摄像头改造、毕业设计或竞赛的人。它的门槛不在深度学习理论而在数据怎么处理、训练怎么收敛、推理怎么落地这几段工程衔接上。2. 拆解火灾识别数据集先把 zip 里的图像变成能喂给 CNN 的规范结构2.1 火灾识别数据集最常见的三种组织方式拿到“含数据集.zip”后很多人第一件事是直接解压然后开始写模型这是最容易翻车的操作。深度学习项目里数据集的目录结构决定了后面数据加载、标签读取和评估方式的复杂度。常见做法是先解压再看清图片的分布。第一种是已经按类分好目录的例如train/fire、train/normal、val/fire、val/normal这种对 PyTorch 的ImageFolder和 Keras 的flow_from_directory都很友好。第二种是只有一个大目录里面放了若干子文件夹每个子文件夹代表一个采集场景图片散落在里面需要自己按场景或内容分配标签。第三种最麻烦是图片和一个 CSV/Excel 标签文件配套图片文件名和标签一一对应比如监控截图按帧号命名。如果你拿到的是第二种或第三种别急着训练先花半小时把数据整理成第一种结构。火灾识别的标签体系一般就两类fire和normal。有些项目会把smoke单独拆出来但这会明显增加类间混淆——烟雾样本如果数量不够模型很容易把雾霾、水汽、灰尘都预测成smoke。我一般建议起步阶段只用“有火/无火”二分类等数据量超过五千张再考虑三分类。2.2 把散乱图像整理成ImageFolder格式转换脚本与参数说明下面这段脚本能把任意散落目录下的火灾图像按文件名前缀或子目录名归类并按比例随机划分训练集和验证集。它的核心思路是先收集所有图片路径再根据每个图片所属文件夹名判断标签最后用随机数划分到train和val下。import os import shutil import random from collections import defaultdict src_dir raw_fire_dataset # 原始解压路径 dst_dir organized_fire_dataset # 整理后输出路径 train_ratio 0.8 random.seed(42) # step1: 扫描所有图片文件 image_exts {.jpg, .jpeg, .png, .bmp} file_map defaultdict(list) for root, _, files in os.walk(src_dir): for f in files: if os.path.splitext(f)[1].lower() in image_exts: label os.path.basename(root) # 用所在文件夹名作为标签 file_map[label].append(os.path.join(root, f)) # step2: 按比例划分并复制到新目录 for label, paths in file_map.items(): random.shuffle(paths) split_idx int(len(paths) * train_ratio) for phase, subset in [(train, paths[:split_idx]), (val, paths[split_idx:])]: out_dir os.path.join(dst_dir, phase, label) os.makedirs(out_dir, exist_okTrue) for src_path in subset: shutil.copy2(src_path, out_dir)逻辑说明defaultdict(list)用来按标签聚合所有图片路径os.walk遍历目录时os.path.basename(root)拿到的是图片所在文件夹名——如果原始数据是“火灾场景1”“火灾场景2”“办公室正常”这种目录名你需要先手动建一个标签映射字典比如把“火灾场景”开头映射成fire。这里直接用文件夹名当标签是一种简化实际项目中最好在脚本里再加一层字典label_map {fire_scene: fire, office: normal}。参数说明train_ratio 0.8表示训练集占 80%验证集占 20%。火灾识别领域我一般不推荐把比例拉得太高因为数据通常来自连续监控画面相邻帧高度相似验证集需要保留足够的多样性。random.seed(42)保证每次运行划分结果一致方便复现实验。如果你后续要做交叉验证把random.shuffle换成KFold的方式即可。2.3 火灾图像的通道、尺寸与增强策略整理完目录后运行下面代码检查每张图片的尺寸分布和通道数from PIL import Image import glob img_paths glob.glob(organized_fire_dataset/train/*/*.jpg) size_set set() mode_set set() for p in img_paths[:500]: with Image.open(p) as im: size_set.add(im.size) mode_set.add(im.mode) print(尺寸种类数:, len(size_set), 示例:, list(size_set)[:5]) print(颜色模式:, mode_set)这一步通常能发现两类问题一是图片尺寸差异极大有的监控截图是 1920x1080有的网络爬图是 224x224如果不统一直接进模型数据加载会报batch维度冲突二是存在 RGBA 四通道图片直接转 RGB 时透明通道会被丢弃但有些项目中透明区域会变成黑色块相当于引入了噪声。火灾识别数据处理上需要做三件事。第一是统一输入尺寸我见过 128x128 到 512x512 都有项目用多数 CNN 骨干网络默认输入是 224x224。第二是合并且归一化把图像从 0-255 缩放到 0-1 区间。第三是数据增强对火灾识别特别有用的是随机水平翻转、随机裁剪和颜色抖动——注意不能用随机旋转太大的角度因为烟雾和火焰的朝向本身是有物理意义的倒过来的“火”在真实场景中很少出现。2.4 火灾数据集的统计维度与质量检查整理完数据后建议画一个表格去核对数据质量。我常用的核对清单包括以下维度检查项目标值出现问题时的处理方式总图片数量至少 2000 张二分类数量不足时先用预训练模型迁移学习正负样本比例fire:normal 尽量接近 1:1通过欠采样或加权损失函数修正图片最短边尺寸不低于 128 像素过小的图直接删除或做超分辨率增强场景覆盖包含室内、夜间、户外、烟雾早期缺场景时补充公开火灾图像或录制模拟视频重复/相似帧占比连续帧相似度不宜过高用感知哈希去重按时间间隔抽样这里特别提醒一点很多人拿到 zip 后只看图片数量不看相似度。如果是监控视频按帧导出同一段燃烧过程会连续出现几十张几乎一样的画面这些画面同时进训练集和验证集会导致验证准确率虚高但现场换了角度立刻失效。所以我在整理环节会额外跑一个去重脚本——计算每张图的phash值互相同一汉明距离小于 5 的就删掉一张。3. 搭一个能收敛的 CNN 火灾分类模型结构选型与训练管线3.1 自己搭小 CNN 还是用预训练模型迁移学习这个选择直接决定你做这个项目要花多少天。我的判断标准很粗暴如果整理完的数据小于一万张优先使用在 ImageNet 上预训练过的 ResNet18 或 MobileNetV3把最后一层全连接换成 2 类输出冻结前几层只训练尾部如果你的数据集超过两万张且包含大量夜间红外场景那再考虑从零训练一个轻量 CNN。为什么这样选因为火灾图像里的“火”和“烟”在特征上并不像 ImageNet 里的物体那样有稳定的纹理模式火焰的高频边缘和颜色渐变在预训练模型里其实已经学到过一部分——比如“烛台”“篝火”这些类别的特征。迁移学习可以把这部分通用视觉能力直接搬过来你只需要在尾部微调让模型聚焦在“火苗形状”和“烟雾扩散方向”上。从零训练小 CNN 的坑在于收敛速度慢而且很容易过拟合到背景纹理上比如把白墙、白天花板识别成烟。如果你非要自己搭小 CNN结构上有几个硬经验。第一层卷积核不要小于 7x7火灾识别输入图里火焰的光晕是低频信号小卷积核第一层感受野太小会丢失全局亮度信息中间层用 3x3 卷积堆叠步长 1 配合 BatchNorm下采样交给 MaxPool最后用全局平均池化代替 Flatten能大幅减少参数量。3.2 最小可跑通的 CNN 火灾分类模型PyTorch 实现与逐层说明下面这段代码是一个可以端到端跑通的二分类 CNN输入 224x224 的 RGB 图像输出火灾/正常两类概率。建议直接复制到 Jupyter Notebook 里试跑确认管线通后再换预训练模型。import torch.nn as nn class FireCNN(nn.Module): def __init__(self, num_classes2): super().__init__() self.features nn.Sequential( # 第一层: 大卷积核捕获火焰光晕的低频信息 nn.Conv2d(3, 32, kernel_size7, stride2, padding3), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), # 第二层: 提取烟雾边缘的局部纹理 nn.Conv2d(32, 64, kernel_size3, stride1, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d((1, 1)), # 输出形状固定为 1x1 nn.Flatten(), nn.Dropout(0.5), nn.Linear(64, num_classes), ) def forward(self, x): return self.classifier(self.features(x))逻辑说明features部分做了两次下采样输入从 224x224 降到约 28x28通道数从 3 升到 64。BatchNorm2d放在卷积和 ReLU 之间能缓解梯度消失并让训练对学习率不那么敏感。最后用AdaptiveAvgPool2d((1,1))把特征图压成一个向量避免全连接层对输入尺寸的死板要求。Dropout(0.5)在数据量不大时非常关键它强制模型不要只依赖某几个神经元去记住训练集里的具体场景。参数说明num_classes是输出类别数二分类时填 2 即可。kernel_size7和stride2的组合是一对如果你把卷积核改成 5感受野会变小对大面积火焰的响应会变差。padding3是为了保持输出尺寸 输入尺寸 / 2这个等式关系不要在改卷积核时忘记否则维度会报错。3.3 训练超参数学习率、批大小与损失函数的三组有效组合训练火灾识别模型时超参数组合比模型结构更容易决定成败。我踩过太多坑给出三组验证过比较稳的组合按你的数据量去选。第一组适合小数据量2000-5000 张批量大小 16优化器 Adam初始学习率 3e-4损失函数交叉熵训练 30 轮。这个组合的玄学在于小批量加小学习率让梯度更新更平稳避免模型在早期就过拟合到某几个高亮烟雾样本上。第二组适合中等数据量5000-20000 张批量大小 32优化器 SGD Momentum动量 0.9初始学习率 1e-2配合余弦退火调度器训练 40 轮。SGD 的泛化性能在图像分类里通常优于 Adam代价是收敛慢。建议前 5 轮做 warmup学习率从 0 线性升到 1e-2后面再余弦下降。第三组适合迁移学习微调预训练模型主干学习率 1e-5新加分类头学习率 1e-3用 AdamW 优化器权重衰减 0.01。这种不对称学习率设置是因为主干已经收敛过了太大的学习率会把学到的通用特征冲掉。损失函数除了交叉熵火灾识别里有一个值得试的变体叫LabelSmoothingCrossEntropy把真实标签从硬 0/1 变成 0.9/0.1 的软标签。这个 trick 对火/烟这类视觉边界模糊的问题很有效等于告诉模型“不要把有火无火分的太绝对”能显著降低误报率。3.4 训练循环的骨架代码验证集评估与早停训练循环本身没什么奇技淫巧但必须在每个 epoch 末尾同时输出训练损失和验证集上的 F1 分数只盯着 loss 会让你错过很多问题。for epoch in range(init_epoch, total_epoch): model.train() train_loss 0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) opt.zero_grad() logits model(imgs) loss criterion(logits, labels) loss.backward() opt.step() train_loss loss.item() * imgs.size(0) model.eval() correct 0 total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) outputs torch.softmax(model(imgs), dim1) preds outputs.argmax(dim1) correct (preds labels).sum().item() total labels.size(0) val_acc correct / total print(fEpoch {epoch} | train_loss: {train_loss/len(train_loader):.4f} | val_acc: {val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_fire_model.pt) patience 0 else: patience 1 if patience 8: print(验证集连续 8 轮未提升早停) break逻辑说明model.train()和model.eval()切换了 BatchNorm 和 Dropout 的行为如果不切回评估模式验证时 BatchNorm 仍然用当前 batch 的统计量验证结果会漂移。早停的 patience 参数设 8是因为火灾数据的类别差异大但场景噪声也多验证准确率会有 1-2 个百分点的正常抖动patience 太低会误触发早停。参数说明val_acc不是最稳妥的评估指标当 fire 样本很少时模型把所有图都预测成 normal 也能拿到很高的准确率。你在实际训练时把val_acc顺便换成 F1-base 的best_f1或者至少打印出混淆矩阵。4. 训练与验证的 4 个必查项火灾识别避坑与常见问题排查4.1 正负样本比例失衡模型收敛后把所有图都判成“无火”现象训练损失逐渐下降验证准确率高达 93%但打开测试视频发现真正起火的画面模型完全没有报警输出概率始终贴在“normal”一侧。原因数据集里火灾图像只有总样本的 12%模型发现只要全部预测成 majority 类损失就能维持在很低水平。CNN 并没有“学会”火灾特征它只是拟合了类别先验分布。解决最直接的办法是重采样对fire类做随机过采样让每个 batch 里的正负样本保持接近 1:1。如果做不了重采样就在损失函数里给少数类加权重torch.nn.CrossEntropyLoss(weighttorch.tensor([1.0, 5.0]))前一个权重给 majority 类后一个给 fire。注意这个权重策略在训练集和验证集分布不一致时不适用过采样通常更稳。4.2 数据增强过度深夜真实场景里的“暗光小火苗”全部漏报现象白天测试效果很好一旦换成夜间厂区场景误报率低但漏报率极高小火苗完全识别不出来。模型对测试集里那种昏暗背景下的小面积火焰没有响应。原因训练时用了强度过高的随机亮度扰动和随机灰度化把很多真实火焰样本的亮度特征破坏掉了。模型看到的“火”全是调亮或调暗的极端图像反而丢失了火焰“由暗到亮的渐变”这一关键区分特征。解决把颜色抖动参数降低一半尤其不要把brightness范围设成 0.5-1.5 这种跨度。白天和黑夜的差异应该靠加入夜间真实场景图像来覆盖而不是靠调亮度模拟。我后来在训练集里专门保留了一组夜间红外图像的原始亮度不动增强只做翻转和轻微裁剪夜间漏报率立刻降下来。4.3 连续帧数据划分不严格验证集泄露导致结果虚高现象训练 5 个 epoch 后验证准确率就超过 98%但把这个模型接到另一段监控视频上表现完全不是那么回事对相似场景的泛化能力很差。原因数据来自视频抽帧同一场景的相邻帧被随机划分到了训练集和验证集。模型相当于“见过”验证集中同一时刻的画面记忆住了场景背景而不是学会泛化的火焰特征。解决划分数据集前先按视频片段分组把一段视频的所有帧放进同一个 partition。具体做法给每张图片记录视频 ID然后按视频 ID 进行分组再对组做随机划分。严格来说验证集的泄漏问题在火灾识别里非常普遍因为火焰出现本身就是时间连续事件。4.4 只用 Loss 和 Accuracy 判断收敛忽略了误报率的业务成本现象训练损失降到 0.02验证准确率 97.2%看起来万事大吉。部署一周后发现虽然真实火情都被抓住了但值班室平均每天被无关的光照变化吵醒三次所有人开始手动关闭告警系统。原因在消防告警场景里误报是有实际成本的——每次误报都会消耗应急响应资源同时降低系统可信度。准确率这个指标没有把“火灾被漏掉”和“正常被误报”区分开。模型在验证集上 97% 准确率可能对应 3% 的误报率换算到每天数千次检测就是几十次误报。解决用混淆矩阵做调试专门关注两个数字漏报率fire被判成normal的比例和误报率normal被判成fire的比例。我通常把训练目标定成“漏报率低于 1%误报率低于 5%”。如果模型偏向一边就去调分类阈值——不用默认 0.5而是找验证集上 F1 最高的阈值例如torch.softmax(logits, dim1)[:, 1] 0.7才判为 fire能凭空把误报率降一半。5. 落地进阶把训练好的 CNN 接到真实告警流里的 3 个实战技巧5.1 用时间窗口平滑预测消灭单帧抖动造成的误报CNN 在单帧图像上的输出是不稳定的同一场景在相邻几帧里预测概率可能在 0.49 和 0.83 之间跳动。直接按单帧阈值 0.5 触发告警会让系统变成“神经质”。常见做法是维护一个大小 5-10 的滑动窗口只有窗口内超过 3 帧判定为 fire才触发告警。这个滞后机制在业务上叫确认周期牺牲最多 3 秒的响应时间换取 90% 以上的误报削减。class AlarmFilter: def __init__(self, fire_threshold0.5, confirm_frames3): self.history [] self.fire_threshold fire_threshold self.confirm_frames confirm_frames def update(self, prob): self.history.append(prob) self.history self.history[-self.confirm_frames * 2:] # 只保留近几帧 if len(self.history) self.confirm_frames: return False fire_count sum(1 for p in self.history if p self.fire_threshold) return fire_count self.confirm_frames逻辑说明confirm_frames3意味着连续三帧超过阈值才能上报。这里的“连续”不是严格意义上的逐帧而是任意三帧的快速确认允许中间偶发一帧丢失。参数调整上分辨率高的场景可以缩短窗口因为火焰稳定摄像头有频繁遮挡的场所适当放大窗口。5.2 把 PyTorch 模型导出为 ONNX摆脱训练框架的部署限制训练完成后模型要进告警程序或摄像头边缘盒子总不能再让生产环境里安装 PyTorch。导出 ONNX 是最省事的中间环节它可以被 OpenCV DNN、ONNX Runtime 和多数硬件加速 SDK 直接加载。注意要在model.eval()状态下导出。import torch import torch.nn as nn model FireCNN(num_classes2) model.load_state_dict(torch.load(best_fire_model.pt, map_locationcpu)) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, fire_model.onnx, input_names[input_image], output_names[fire_probability], dynamic_axes{input_image: {0: batch}, fire_probability: {0: batch}}, opset_version12, )参数说明input_names和output_names是给调用的程序看的建议把输出名直接叫fire_probability这样部署端代码里不用靠记忆索引取值。dynamic_axes打开 batch 维度的动态传入允许实际推理时一次只传一帧。opset_version设 12 是为了兼容 OpenCV 旧版本太高的话某些部署环境会报不支持的算子。5.3 保存完整的预处理参数否则换台机器推理结果直接漂移最后一个坑最容易在交接时爆发模型在 A 机器的 Windows 上跑得好换到 B 机器的 Linux 容器里同一张图的预测结果完全变了。原因不在模型权重在于预处理参数丢了——训练时用的mean、std、输入尺寸、通道顺序推理代码里没带上。我的习惯是把这些参数统一放进一个 JSON 文件跟 ONNX 模型放在同一目录。项目跑完至少保留三个文件model.onnx、preprocess.json、class_names.txt。preprocess.json里面写清楚归一化均值方差和 resize 尺寸推理端加载这个 JSON 后无需人工确认参数从源头避免跨平台预测不一致。做这个项目到现在我最深的感受是火灾识别真正难的从来不是模型结构而是对业务场景的理解——你要知道白天和黑夜的火焰长什么样要知道监控画面的脏水渍和烟雾的视觉差异要接受模型不可能 100% 准确然后设计后处理逻辑兜底。把这些环节想清楚了CNN 只是你手里的一把螺丝刀。希望这些经验帮你少走几段弯路。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →