
简介这份资源面向计算机视觉入门者与安全监控、火灾预警方向的开发者围绕烟火图像的识别与分类任务提供从图像预处理到模型部署的完整实践素材。压缩包共3627个文件以3617张bmp样本图片为核心数据辅以xml标注、jpg示例图、py训练脚本、ipynb预处理与实验笔记及iml工程配置整体约11.18MB目录结构便于按类别检索与批量读取。内容覆盖去噪、增强、灰度化与二值化等预处理流程SIFT、SURF及CNN特征提取思路SVM、随机森林与AlexNet、VGG、ResNet等模型的训练、验证与调优并涉及数据扩增、准确率与F1分数等评估指标。已有217人学习下载适合希望快速搭建烟火分类基线、复现实验流程并理解数据集标注与模型评估环节的读者参考。1. 烟火图像识别与分类从 BMP 样本到可复现的 CNN 流程手里拿到一批文件名像1-1004.BMP、0-00383.BMP、0-1209.BMP这样的烟火图像时第一反应往往不是我要训练一个多深的网络而是这批图到底能不能直接喂给模型。文件名前缀的0和1大概率就是二分类标签0代表非烟火、1代表烟火后面的数字是样本序号。这个命名规则本身就是一份轻量级标注省掉了单独维护 label 文件的麻烦但也埋了一个坑如果不同批次的编号有重叠光靠文件名切分训练集和测试集会串数据。烟火图像的识别与分类本质上是把看起来像烟或火的视觉模式转成可判别的数值特征再交给分类器做决策。它跟通用图像分类的区别在于烟火目标边界模糊、形态多变、背景干扰强灯光、晚霞、雾气都容易混淆所以预处理和特征工程阶段的选择比模型结构本身更影响最终效果。这套资源覆盖了从图像预处理、特征提取、模型训练到评估优化的完整链路适合做安全监控预警、烟花表演分析这类方向的从业者拿来当基线工程用。下面按实际动手顺序拆开讲。2. 图像预处理与数据组织BMP 批量读取和标签解析2.1 为什么 BMP 格式反而省事BMP 是无压缩位图格式每个像素的 RGB 值直接存储没有 JPEG 那种块效应和压缩伪影。对烟火图像来说火焰边缘的渐变和烟雾的纹理恰恰是判别关键JPEG 压缩会在高频区域引入噪声干扰边缘检测和纹理特征。所以这批.BMP样本在预处理阶段少了一步解压失真补偿的麻烦。但 BMP 的代价是体积大。一张 512×512 的 24 位 BMP 大约 768KB如果样本量上千内存和磁盘 IO 会成为瓶颈。常见做法是在预处理阶段统一转成 NumPy 数组缓存或者转存为 PNG 后再训练兼顾无损和体积。2.2 批量读取与标签解析脚本文件名格式是{label}-{index}.BMP用split(-)就能拆出标签和序号。下面这段代码把目录下所有 BMP 读进来统一 resize 到固定尺寸归一化后存成数组同时生成标签向量import os import cv2 import numpy as np IMG_SIZE 128 # 统一尺寸兼顾细节和显存 DATA_DIR ./fire_smoke_images images [] labels [] # 按文件名排序保证复现时顺序一致 fnames sorted([f for f in os.listdir(DATA_DIR) if f.upper().endswith(.BMP)]) for fname in fnames: # 文件名格式: 0-00383.BMP 或 1-1004.BMP label_str fname.split(-)[0] label int(label_str) # 0非烟火, 1烟火 path os.path.join(DATA_DIR, fname) img cv2.imread(path) # 默认读成 BGR if img is None: print(f跳过损坏文件: {fname}) continue img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转 RGB方便后续用 matplotlib 检查 img cv2.resize(img, (IMG_SIZE, IMG_SIZE), interpolationcv2.INTER_AREA) img img.astype(np.float32) / 255.0 # 归一化到 [0,1] images.append(img) labels.append(label) X np.array(images) y np.array(labels) print(f总样本: {X.shape[0]}, 烟火: {y.sum()}, 非烟火: {len(y)-y.sum()}) np.save(X.npy, X) np.save(y.npy, y)逻辑说明sorted保证每次运行文件顺序一致避免随机读取导致训练集划分漂移。cv2.INTER_AREA适合缩小图像比默认的双线性插值更少引入伪影。归一化放在 resize 之后避免插值计算时数值范围不一致。参数说明IMG_SIZE是最关键的超参数。128×128 对烟火这种大目标够用但如果你的场景里烟火在画面中占比很小比如远景监控建议提到 224 或 256否则 resize 后烟火区域只剩几十个像素CNN 也救不回来。2.3 训练集/测试集划分的坑按文件名排序后直接切前 80% 做训练、后 20% 做测试看起来没问题但如果原始数据是按时间顺序采集的后 20% 可能全是同一场景的图测试集分布和训练集不一致准确率虚高。更稳的做法是按标签分层抽样from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )stratifyy保证训练集和测试集里烟火/非烟火的比例一致。random_state42是给自己留的后悔药下次复现结果对不上时先检查这个值有没有改。3. 特征提取与 CNN 模型搭建从 SIFT 到卷积核的选型逻辑3.1 传统特征和深度特征的分界线SIFT 和 SURF 这类手工特征在烟火图像上有天然短板火焰没有固定形状关键点检测器容易把高光、反光误判为特征点烟雾更是纹理稀疏SIFT 描述子几乎抓不到稳定模式。所以这套资源里如果code.ipynb同时给了 SIFT 和 CNN 两条路实际落地时优先走 CNNSIFT 那部分当对照组理解就行。CNN 的优势在于卷积核自动学习边缘、角点、纹理这些低级特征再通过堆叠层组合成火焰的尖峰形态烟雾的扩散纹理这类高级语义。你不需要告诉它火焰边缘是高频的它自己会从数据里学出来。3.2 一个够用的 CNN 结构下面这个网络结构不追求 SOTA但胜在参数少、训练快、容易调import torch import torch.nn as nn class FireSmokeNet(nn.Module): def __init__(self, num_classes2): super().__init__() self.features nn.Sequential( # 输入 3×128×128 nn.Conv2d(3, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 64×64 nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 32×32 nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 16×16 nn.Conv2d(128, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool2d(1) # 全局平均池化输出 128×1×1 ) self.classifier nn.Sequential( nn.Dropout(0.5), nn.Linear(128, num_classes) ) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x)逻辑说明每个卷积后面跟BatchNorm和ReLU是标配BatchNorm让训练对学习率不那么敏感。AdaptiveAvgPool2d(1)替代了全连接层大幅减少参数量也降低了过拟合风险。Dropout(0.5)只在分类头用卷积层不加因为卷积层的参数共享本身就有正则效果。参数说明卷积核统一用 3×3这是经过验证的性价比选择。5×5 或 7×7 感受野更大但参数翻倍对烟火这种中等尺度目标没必要。通道数从 32 起步每经过一次池化翻倍这是经典的 VGG 式设计显存占用可控。3.3 训练循环里的关键参数from torch.utils.data import DataLoader, TensorDataset # 转成 Tensor train_ds TensorDataset( torch.tensor(X_train).permute(0, 3, 1, 2), # NHWC - NCHW torch.tensor(y_train, dtypetorch.long) ) test_ds TensorDataset( torch.tensor(X_test).permute(0, 3, 1, 2), torch.tensor(y_test, dtypetorch.long) ) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) test_loader DataLoader(test_ds, batch_size32, shuffleFalse) device torch.device(cuda if torch.cuda.is_available() else cpu) model FireSmokeNet(num_classes2).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) for epoch in range(30): model.train() total_loss 0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() out model(xb) loss criterion(out, yb) loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch1}, Loss: {total_loss/len(train_loader):.4f})逻辑说明permute(0, 3, 1, 2)把 NumPy 的 NHWC 格式转成 PyTorch 要求的 NCHW这一步忘了会直接报维度错误。weight_decay1e-4是 L2 正则配合 Dropout 一起压制过拟合。参数说明batch_size32是显存和梯度稳定性的折中。如果显存够可以提到 64训练更稳如果样本量少于 500降到 16 甚至 8避免一个 batch 里全是同一类。学习率1e-3是 Adam 的常用起点训练 loss 震荡就降到1e-4收敛太慢就升到3e-3但别超过1e-2。4. 数据扩增与类别不平衡烟火样本少的时候怎么救4.1 扩增不是越多越好烟火图像的数据扩增有个特殊约束水平翻转没问题火焰左右对称但垂直翻转要谨慎火焰的物理形态是向上燃烧的倒过来的火焰在现实中不存在模型学了反而困惑。旋转角度也别太大±15° 以内合理90° 旋转会让火焰方向完全错乱。import torchvision.transforms as T train_transform T.Compose([ T.ToPILImage(), T.RandomHorizontalFlip(p0.5), T.RandomRotation(degrees15), T.ColorJitter(brightness0.2, contrast0.2, saturation0.2), T.ToTensor() ])ColorJitter对烟火图像特别有用因为不同时段、不同天气下火焰的颜色差异很大亮度、对比度、饱和度的微调能模拟这种变化。但幅度别超过 0.3否则火焰可能被调成蓝色或灰色标签就错了。4.2 类别不平衡的处理顺序如果0类样本远多于1类非烟火图通常更容易采集直接训练会让模型倾向于预测多数类。处理顺序建议是先算类别权重再考虑过采样最后才动阈值。from sklearn.utils.class_weight import compute_class_weight class_weights compute_class_weight( class_weightbalanced, classesnp.unique(y_train), yy_train ) class_weights torch.tensor(class_weights, dtypetorch.float32).to(device) criterion nn.CrossEntropyLoss(weightclass_weights)compute_class_weight自动按总数/(类别数×该类样本数)算权重少数类权重更高loss 对它的惩罚更大。这比简单复制少数类样本更稳因为复制会加剧过拟合。提示如果用了WeightedRandomSampler做重采样就不要再叠加class_weight两个机制同时开会让少数类被过度强调模型反而崩。5. 模型评估与调优准确率之外的指标和排查清单5.1 为什么不能只看准确率烟火识别场景里漏报把烟火判成非烟火和误报把晚霞判成烟火的代价完全不同。漏报可能导致火灾预警失效误报会让监控人员疲于奔命。所以评估时必须看混淆矩阵和召回率from sklearn.metrics import classification_report, confusion_matrix model.eval() all_preds [] with torch.no_grad(): for xb, yb in test_loader: xb xb.to(device) out model(xb) preds out.argmax(dim1).cpu().numpy() all_preds.extend(preds) print(confusion_matrix(y_test, all_preds)) print(classification_report(y_test, all_preds, target_names[非烟火, 烟火]))classification_report会给出每个类的精确率、召回率和 F1。如果烟火类的召回率低于 0.85说明漏报严重优先调分类阈值或增加烟火样本。5.2 常见问题排查现象训练 loss 正常下降但验证集准确率卡在 50% 不动。原因标签解析错了比如0-00383.BMP被解析成标签 0但实际语义可能相反或者训练集和验证集的标签映射不一致。 解决打印前 10 个样本的文件名和对应标签人工核对几张图的真实类别。现象模型在测试集上准确率 95%但实际部署时误报率极高。原因测试集和实际场景的分布不一致。测试集可能来自同一批采集数据背景单一实际监控画面背景复杂。 解决从实际场景里抽 50100 张图做独立验证集不要混入训练数据。现象训练到第 5 个 epoch 后 loss 突然变成 NaN。原因学习率太大或者ColorJitter把像素值调到了非法范围。 解决先把学习率降到1e-4重跑如果还 NaN检查扩增后的图像有没有全黑或全白的异常样本。现象GPU 显存够但训练速度极慢。原因DataLoader的num_workers默认是 0数据加载在主进程里串行执行。 解决设num_workers4Windows 下设 0 或 2避免多进程报错pin_memoryTrue。现象同一份代码两次运行结果差很多。原因随机种子没固定。PyTorch、NumPy、Python 内置随机各有各的种子。 解决在脚本开头统一设torch.manual_seed(42)、np.random.seed(42)并设torch.backends.cudnn.deterministic True。6. 从二分类到多类烟火识别迁移学习和阈值调优的实战技巧二分类跑通之后下一步通常是区分烟和火或者进一步细分烟花类型。这时候样本量往往不够从头训练迁移学习是最实际的路径。用 ImageNet 预训练的 ResNet18 或 VGG16把最后一层换成你的类别数前几层冻结或小学习率微调import torchvision.models as models model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) # 冻结前面的卷积层 for param in model.parameters(): param.requires_grad False # 替换分类头 model.fc nn.Linear(model.fc.in_features, num_classes) model model.to(device) # 只优化分类头的参数 optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3)冻结策略看样本量少于 500 张就全冻结只训分类头5002000 张可以解冻最后两个卷积块一起微调学习率降到1e-4超过 2000 张再考虑全网络微调。微调时学习率一定要比从头训练小一个数量级否则预训练学到的通用特征会被冲掉。阈值调优是另一个容易被忽略的点。模型输出的是 softmax 概率默认取 0.5 作为分界。但在火灾预警场景里你可以把烟火类的判定阈值降到 0.3牺牲一点精确率换更高的召回率。具体操作是在推理阶段不取argmax而是手动比较概率probs torch.softmax(out, dim1) # 烟火类索引为 1阈值设为 0.3 preds (probs[:, 1] 0.3).long()这个阈值没有标准答案得拿验证集画 P-R 曲线找到召回率和精确率的平衡点。我一般会把阈值、对应的召回率和误报率列成一张表让业务方自己选。还有一个血泪经验模型上线后别关掉日志。把每次推理的输入图像、输出概率、最终判定都存下来每周抽一批人工复核。你会发现有些误报是模型的问题有些是摄像头脏了或者光线突变这些反馈数据攒够了再回流训练比任何调参都管用。从那以后我每次部署烟火识别模型都强制走一遍日志留存→周度复核→数据回流的闭环不然模型退化了你都不知道。希望帮到你。本文还有配套的精品资源点击获取