ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

烟火图像识别与分类:从BMP预处理到CNN模型训练全流程

烟火图像识别与分类:从BMP预处理到CNN模型训练全流程 简介这份资源面向计算机视觉初学者与安全监控、火灾预警方向的开发者围绕烟火图像的识别与分类任务提供从图像预处理、特征提取到模型训练与评估的完整实践素材。压缩包共3627个文件以3617张bmp烟火样本图片为主另含xml标注、jpg示例图、py训练脚本、ipynb预处理与实验笔记及项目配置整体约11.18MB目录结构便于按类别检索。已有217人学习下载。读者可借助现成样本与代码动手完成去噪、灰度化、二值化等预处理对比SIFT、SURF与CNN特征提取效果并训练SVM、随机森林或ResNet等模型结合准确率、精确率、召回率与F1分数评估优化快速搭建可复用的烟火识别流程。1. 烟火图像识别与分类从一堆 BMP 文件名到能跑通的分类器手里拿到一批烟火图像文件名长这样1-1004.BMP、0-00383.BMP、0-00382.BMP、1-1186.BMP、0-001002.BMP。前缀的 0 和 1 就是类别标签后面是序号。这种命名方式在烟火识别与分类项目里非常典型——正样本是烟花绽放或火焰燃烧的画面负样本是普通夜景、灯光、云霞等容易混淆的场景。整个资源包围绕这条数据主线展开code.ipynb负责图像预处理和特征可视化main.py负责模型训练与验证temp.jpg是单张推理的样本入口.idea是开发环境配置。它解决的核心问题是给定一张烟火图像判断它属于哪一类并给出可复现的训练和评估流程。适合做安全监控预警、烟花表演分析、火灾早期识别的从业者也适合想拿一个完整图像分类 pipeline 练手的人。2. 图像预处理与数据管道把 BMP 喂进模型前要做的四件事2.1 为什么预处理决定了模型上限烟火图像有个很麻烦的特点火焰和烟花本身是高亮、高饱和区域但背景可能是深夜、烟雾、城市灯光。如果直接把原始 BMP 丢进网络模型很容易学到“亮的就是烟火”这种捷径特征换一个场景就翻车。所以预处理不是走形式它直接决定模型能不能学到真正的形状和纹理。常见做法是四步走去噪、增强、灰度化、二值化。去噪用高斯滤波或中值滤波高斯适合高斯噪声中值适合椒盐噪声烟火图像里传感器热噪声偏多我一般先用高斯。增强用直方图均衡化或 CLAHE把火焰边缘的对比度拉出来。灰度化看后续模型如果用传统特征如 SIFT、SURF灰度图足够如果用 CNN保留 RGB 三通道更好因为颜色分布本身就是烟火的重要判别信息。二值化只适合做形状分析或连通域统计不要拿二值图去训练深度模型会丢掉太多信息。code.ipynb里通常会把这几步串成一个可视化流程方便你逐张检查预处理效果。下面这段代码是我按这个资源场景补全的常见写法你可以直接对照 notebook 里的单元格改。import cv2 import numpy as np import os def preprocess_firework(img_path, use_grayFalse, use_binaryFalse): # 读取 BMPOpenCV 默认 BGR img cv2.imread(img_path) if img is None: raise FileNotFoundError(f读不到图像: {img_path}) # 高斯滤波去噪kernel 取 5x5sigma 自动 denoised cv2.GaussianBlur(img, (5, 5), 0) # 转 LAB 空间对 L 通道做 CLAHE避免 RGB 直接均衡化偏色 lab cv2.cvtColor(denoised, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) l_eq clahe.apply(l) enhanced cv2.merge((l_eq, a, b)) enhanced cv2.cvtColor(enhanced, cv2.COLOR_LAB2BGR) if use_gray: enhanced cv2.cvtColor(enhanced, cv2.COLOR_BGR2GRAY) if use_binary: # 自适应阈值适合光照不均的烟火场景 enhanced cv2.adaptiveThreshold( enhanced, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 ) return enhanced # 批量处理示例 data_dir ./firework_data save_dir ./preprocessed os.makedirs(save_dir, exist_okTrue) for fname in os.listdir(data_dir): if fname.lower().endswith(.bmp): out preprocess_firework(os.path.join(data_dir, fname)) cv2.imwrite(os.path.join(save_dir, fname), out)逻辑说明先读图再做高斯去噪然后在 LAB 空间对亮度通道做 CLAHE这样不会把颜色通道一起拉爆。clipLimit2.0是经验值调高到 4.0 会让火焰边缘更锐但噪声也会放大。tileGridSize(8,8)适合 512×512 左右的图图更大就改成 16×16。二值化那一步只建议在传统特征流程里用CNN 训练时把use_binary关掉。2.2 数据集划分与标签解析文件名前缀就是标签0-开头是一类1-开头是另一类。解析的时候不要用split(-)[0]就完事因为有的文件名可能是0-001002.BMP序号部分有前导零但类别位是稳定的。我一般写一个正则把类别和序号都提出来顺便检查有没有漏标或错标。import re import pandas as pd from sklearn.model_selection import train_test_split pattern re.compile(r^(\d)-(\d)\.BMP$, re.IGNORECASE) records [] for fname in os.listdir(data_dir): m pattern.match(fname) if m: label int(m.group(1)) idx int(m.group(2)) records.append({filename: fname, label: label, index: idx}) df pd.DataFrame(records) print(df[label].value_counts()) # 先看类别是否均衡 # 按 7:2:1 划分训练、验证、测试 train_df, temp_df train_test_split( df, test_size0.3, stratifydf[label], random_state42 ) val_df, test_df train_test_split( temp_df, test_size1/3, stratifytemp_df[label], random_state42 )参数说明stratifydf[label]保证划分后各类比例一致烟火数据里如果 0 类远多于 1 类不加这个参数会导致验证集里某一类几乎消失。random_state42固定随机种子方便复现。如果value_counts()出来差距超过 5:1就要考虑过采样或类别权重不然后面召回率会很难看。提示BMP 文件体积大批量读取时不要一次性全部加载到内存。用tf.data或DataLoader做流式读取或者先统一转成 JPEG 再训练能省不少显存和时间。3. 特征提取与模型选型SIFT、SURF 还是直接上 CNN3.1 传统特征和深度特征的分界线这个资源里同时提到了 SIFT、SURF 和 CNN说明作者想覆盖两条路线。我的经验是数据量小于 2000 张、类别差异主要体现在形状和边缘时SIFT SVM 仍然能打而且训练快、可解释。数据量上去以后CNN 端到端学习明显更稳因为烟火图像的类内差异很大——同样是烟花不同颜色、不同曝光、不同背景手工特征很难覆盖全。SIFT 的关键点检测对尺度变化鲁棒但计算慢一张 512×512 的图可能要几百毫秒。SURF 是它的加速版但专利问题在一些场景下要留意。实际做烟火分类我一般会先跑一个小实验用 SIFT 提取关键点聚成 BoW 词袋再喂 SVM看验证集准确率能不能过 80%。如果能过说明形状特征够用如果过不了直接换 CNN别在传统特征上死磕。code.ipynb里如果有 SIFT 的可视化单元格重点看关键点是不是落在火焰边缘和烟花轮廓上。如果关键点大量落在背景灯光上说明预处理没做到位或者需要加掩膜把高亮区域单独提出来。3.2 CNN 模型结构怎么选main.py里通常会定义一个 CNN。烟火图像分类不需要上来就 ResNet-152参数量太大小数据集上反而容易过拟合。我一般从一个小型 VGG 或自定义 4 层卷积开始配合全局平均池化最后接全连接分类头。下面是一个可复现的结构示例。import torch import torch.nn as nn class FireworkCNN(nn.Module): def __init__(self, num_classes2): super().__init__() self.features nn.Sequential( # 输入 3x224x224 nn.Conv2d(3, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 112x112 nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 56x56 nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 28x28 nn.Conv2d(128, 256, kernel_size3, padding1), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool2d(1) # 1x1 ) self.classifier nn.Sequential( nn.Flatten(), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): x self.features(x) return self.classifier(x)逻辑说明四层卷积通道数从 32 翻到 256每层后面跟 BatchNorm 和 ReLU池化用 MaxPool。最后用AdaptiveAvgPool2d(1)把空间维度压成 1×1再接 Dropout 和全连接。Dropout(0.5)在小数据集上是必要的不然训练准确率很快到 99%验证集却不动。num_classes2对应二分类如果后面要分多种烟火类型改这个数就行。训练循环里损失函数用CrossEntropyLoss优化器用Adam学习率从1e-3开始每 10 个 epoch 乘 0.1。批大小 32 或 64看显存。如果验证集准确率震荡把学习率降到1e-4或者加学习率预热。3.3 数据扩增的参数怎么设烟火图像的拍摄角度、距离、光照变化大扩增是必须的。但不要无脑加旋转角度太大可能把烟花转成不合理的形态。我一般用随机水平翻转、±15 度旋转、亮度对比度微调、随机裁剪。颜色抖动要谨慎因为颜色是烟火的重要特征抖动太猛会把红色烟花变成橙色标签就不对了。from torchvision import transforms train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.1), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])参数说明RandomRotation(15)是角度范围超过 30 度烟火轨迹会变得不自然。ColorJitter里saturation0.1调得很轻就是为了保住颜色特征。归一化用的 ImageNet 均值方差如果你从零训练也可以改成自己数据集的统计值但差别通常不大。4. 训练、评估与调参准确率上不去先查这五个地方4.1 训练循环里必须记录的指标main.py的训练部分除了 loss一定要记录每个 epoch 的训练准确率和验证准确率。只看 loss 容易被误导——loss 在降但验证准确率可能已经过拟合了。评估指标用准确率、精确率、召回率和 F1。烟火识别场景里召回率往往比精确率更重要因为漏报一个真实烟火比误报一个普通灯光代价更大。from sklearn.metrics import classification_report, confusion_matrix def evaluate(model, loader, device): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) preds outputs.argmax(dim1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) print(confusion_matrix(all_labels, all_preds)) print(classification_report(all_labels, all_preds, digits4))逻辑说明model.eval()关掉 Dropout 和 BatchNorm 的训练行为torch.no_grad()省显存。混淆矩阵能直接看出哪一类被误判成哪一类。如果 0 类被大量判成 1 类说明模型对负样本的区分能力不够要么加负样本要么调类别权重。4.2 超参数调整的优先级调参不要一把抓按这个顺序来先调学习率再调批大小然后看网络深度最后才是正则化系数。学习率是影响最大的1e-3不行就试1e-4和3e-4。批大小受显存限制但太小会让 BatchNorm 不稳定建议至少 16。如果训练集准确率远高于验证集先加 Dropout 或权重衰减再考虑减网络层数。temp.jpg可以作为单张推理的入口训练完拿它跑一次看输出的类别概率。如果概率接近 0.5说明模型没学到东西回去查预处理和标签。注意不要用测试集调参。测试集只在最后跑一次调参阶段用验证集。这个坑我见过太多次调完参测试集准确率虚高上线就崩。5. 避坑与排查烟火分类项目里最容易翻车的五个地方现象一训练准确率 99%验证准确率 60%。原因过拟合模型记住了训练集的背景噪声而不是烟火特征。 解决加数据扩增、加 Dropout、减小网络容量或者检查训练集和验证集是不是来自同一批拍摄条件。如果验证集全是白天场景而训练集全是夜晚分布不一致再怎么调都没用。现象二模型把亮灯窗户全判成烟火。原因预处理没做掩膜模型学到了“高亮区域烟火”的捷径。 解决在预处理阶段加一个亮度阈值掩膜把非烟火的高亮区域压暗或者用颜色空间过滤只保留火焰色域内的区域再送进网络。现象三BMP 读取出来是 None程序直接崩。原因文件名里有中文路径、空格或者 OpenCV 不支持某些 BMP 编码格式。 解决用cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR)替代cv2.imread能绕过路径编码问题。另外检查文件是不是 16 位 BMPOpenCV 读 16 位图会出问题先转 8 位。现象四验证集 loss 突然变成 NaN。原因学习率太大或者某张图归一化后出现极端值。 解决先把学习率降一个数量级然后在 Dataset 里加断言检查每张图归一化后的均值和方差是否在合理范围。如果某张图全黑或全白直接跳过。现象五类别不平衡导致召回率极低。原因0 类样本远多于 1 类模型倾向于全预测成多数类。 解决在损失函数里加weight参数按类别频率的倒数设置权重或者用WeightedRandomSampler在采样阶段就平衡。不要只靠过采样复制样本会让过拟合更严重。6. 从单张推理到批量验证一个能落地的收尾技巧训练完模型别只看验证集数字。我习惯做一件事把temp.jpg和测试集里随机抽的 20 张图拼成一张网格图每张图上面标真实标签和预测标签一眼就能看出模型在哪些场景下翻车。这个习惯帮我省了很多“数字好看但实际不能用”的后悔药。具体做法是用main.py里已经训练好的模型加载权重后跑一个predict_batch函数。下面这段代码可以直接接在训练脚本后面。import matplotlib.pyplot as plt import numpy as np from PIL import Image def visualize_predictions(model, image_paths, labels, device, n20): model.eval() fig, axes plt.subplots(4, 5, figsize(15, 12)) axes axes.flatten() for i, (path, true_label) in enumerate(zip(image_paths[:n], labels[:n])): img Image.open(path).convert(RGB) tensor val_tf(img).unsqueeze(0).to(device) with torch.no_grad(): pred model(tensor).argmax(dim1).item() axes[i].imshow(img) color green if pred true_label else red axes[i].set_title(fT:{true_label} P:{pred}, colorcolor, fontsize10) axes[i].axis(off) plt.tight_layout() plt.savefig(prediction_grid.png, dpi150) print(已保存 prediction_grid.png)逻辑说明val_tf是验证集的变换不要用训练集的扩增变换否则预测结果会随机。标题里T是真实标签P是预测标签绿色表示正确红色表示错误。跑完打开prediction_grid.png重点看红色标题的图——如果错的全是某种特定背景比如霓虹灯招牌那就针对性地补这类负样本。参数方面n20可以改成你想要的张数但别超过测试集大小。figsize和子图行列数按n调整保证每张图看得清。保存的 dpi 设 150 足够发报告用。从那以后我每次训练完烟火分类模型都强制走一遍这个可视化流程不看到红色错误样本的具体样子就不敢说模型能上线。希望帮到你。本文还有配套的精品资源点击获取
返回列表