尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

小波变换+CNN高相似度图像分类:能量特征与调参实战

小波变换+CNN高相似度图像分类:能量特征与调参实战 简介这份PDF是一篇聚焦高相似度图像识别与分类的算法研究论文面向计算机视觉、深度学习方向的科研人员与研究生也适合从事农业、医学、安防等图像判别场景的工程师参考。论文提出融合小波变换与卷积神经网络的方案先以小波变换提取图像纹理特征通过不同类别、不同分辨率图像集训练确定最佳纹理差异度参数再对图像做子图分解、提取各子图能量特征并归一化最后经卷积与池化交替的CNN把特征向量转为一维向量随训练次数与数据量增加不断优化网络参数得到分类准确率最高的模型。实验中以鸡蛋、苹果两类图像数据集验证完成散养与圈养鸡蛋识别及苹果产地判定平均鉴别准确率超过90%说明该方法能有效区分细微纹理差异。资源包为1个PDF文件约793KB已有158人学习下载适合作为算法复现、论文写作与课程学习的参考资料。1. 高相似度图像分类为什么“加深网络”反而更差散养鸡蛋和圈养鸡蛋放在同一张桌面上颜色、形状、光照几乎一致山西苹果和陕西苹果的果面纹理差异肉眼都需要凑近看。这类任务里同类样本的类间距离极小背景一旦有一点变化网络就会把背景当成判别依据。我在复现《一种融合小波变换与卷积神经网络的高相似度图像识别与分类算法》时踩到的第一个坑就是直接把原图缩到 224×224 丢进 CNN训练集准确率能冲到 0.9 以上测试集却掉到 0.6 出头过拟合得毫无遮掩。论文给出的答案不是堆层数而是换了一条路先用小波变换把图像拆成低频近似子带和高频细节子带提取各子图的能量特征并归一化再把这个特征向量喂给 CNN结构上采用 4 层卷积加 2 层池化交替。实验用鸡蛋、苹果两个数据集训练集 1200 幅、测试集 400 幅平均鉴别准确率达到 90% 以上。这篇要讲的就是怎么把这套“小波预处理 CNN 分类”的组合完整跑起来从参数怎么定、子图怎么提到训练不收敛时该看哪一层。2. 小波变换子图分解与能量特征提取2.1 为什么用二维离散小波而不是傅里叶傅里叶变换把信号摊到整个时域上你看得到有哪些频率却不知道频率出现在图像的哪个位置。高相似度图像的判别信息恰恰藏在局部——蛋壳表面的细小斑点分布、苹果果皮的条纹走向这些是局部的、非平稳的。小波基是有限长且会衰减的做二维离散小波变换DWT后每个尺度上会得到四类子带子带含义在高相似度任务里的作用LL低频近似信号图像主体轮廓、整体亮度分布LH水平细节垂直方向的边缘果皮纵向纹理HL垂直细节水平方向的边缘蛋壳环状纹HH对角细节斜向纹理与高频噪声论文的做法是低频子带做强对比、抑背景的非线性增强高频子带做小波去噪然后把各子图的能量值算出来归一化。因为能量是各子带系数的平方和它天然刻画了不同尺度、不同方向的纹理强度比原始像素更抗光照漂移。2.2 子图能量特征的计算代码用 PyWavelets 做二次分解并提取能量是我试过最省事的复现方式几行就能跑通import pywt import numpy as np import cv2 def wavelet_energy_feature(img_path, wavedb1, level2): img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (128, 128)) # 二次二维离散小波分解db1 即 Haar 小波边界处理用 symmetric coeffs pywt.wavedec2(img.astype(np.float32), waveletwave, levellevel, modesymmetric) feats [] for i, detail in enumerate(coeffs[1:]): # coeffs[0] 是 LL 低频 cH, cV, cD detail # 水平/垂直/对角细节 for band in (cH, cV, cD): energy np.sum(band ** 2) / band.size # 归一化能量 feats.append(energy) # 低频子带能量单独补一项 feats.append(np.sum(coeffs[0] ** 2) / coeffs[0].size) feats np.array(feats) return feats / (feats.sum() 1e-8) # 整体归一化到和为 1 print(wavelet_energy_feature(egg_01.jpg))waveletdb1对应论文里的 Haar 小波做二次分解刚好得到 7 个特征2 层各 3 个细节子带加 1 个低频维度低、训练快这也是我在小数据集上优先选它的原因——换成 db4 特征更平滑但在 1200 幅量级下容易把细纹理磨掉。modesymmetric是为了在图像边界对称延拓避免边缘处出现虚假的强响应这一点在果皮、蛋壳这类满纹理图像上尤其明显用默认的 zero-padding 会在四条边上拉开一圈高能量假象。提示纹理差异度阈值不是拍脑袋定的要在不同类别、不同分辨率的子集上各跑一遍看哪档尺度下的类间能量方差最大把它固定下来再进入 CNN 阶段。2.3 纹理差异度参数怎么定论文提到的“最佳纹理差异度参数”落到实处就是两个分解层数 level 和高低频分离后的响应阈值。层数越多细节子带尺寸越小噪声成分越集中层数太少又抓不到细纹理。我的经验做法是对同一批样本跑 level 取 1、2、3 三档统计类内能量方差和类间能量均值差# 用 ImageMagick 先把数据集统一尺寸避免小波分解前重采样引入插值伪影 mkdir -p data_resized/train for f in data_raw/train/*.jpg; do convert $f -resize 128x128! -colorspace Gray data_resized/train/$(basename $f) done统一尺寸后用上面的能量函数对每个类别算均值看类间差异。若 level2 时类间差值明显大于 level1就取 2这与论文里二次分解得到的子图效果一致。3. CNN 权值更新机制与网络结构搭建3.1 卷积层与下采样层的前向公式落到代码论文里的卷积层前向传播是标准的x_j^l f(Σ x_i^{l-1} * k_ij^l b_j^l)也就是上一层的特征图与可学习卷积核做卷积、加偏置、过激活函数。下采样层则对邻域求和再乘权重。这两步在 PyTorch 里直接对应nn.Conv2d和nn.MaxPool2d但论文特别强调了误差代价函数和灵敏度反向传播所以我们还是按它的结构手动搭一遍别用现成 backbone否则感受野和论文对不上。import torch import torch.nn as nn class WaveletCNN(nn.Module): def __init__(self, in_dim7, num_classes2): super().__init__() # 输入是 7 维能量特征向量先升维成 1x1 的空间图再进卷积 self.reshape nn.Unflatten(1, (1, 1, in_dim)) self.conv1 nn.Conv2d(1, 8, kernel_size1) # 第 1 层卷积 self.pool1 nn.MaxPool2d(1) # 第 1 层池化 self.conv2 nn.Conv2d(8, 16, kernel_size1) # 第 2 层卷积 self.pool2 nn.MaxPool2d(1) # 第 2 层池化 self.conv3 nn.Conv2d(16, 32, kernel_size1) # 第 3 层卷积 self.conv4 nn.Conv2d(32, 64, kernel_size1) # 第 4 层卷积 self.lrn nn.LocalResponseNorm(5, alpha1e-4) # 局部响应归一化 self.fc1 nn.Linear(64, 32) # 全连接层 self.fc2 nn.Linear(32, num_classes) # Softmax 分类层 self.relu nn.ReLU(inplaceTrue) def forward(self, x): x self.reshape(x) x self.relu(self.conv1(x)); x self.pool1(x) x self.relu(self.conv2(x)); x self.pool2(x) x self.relu(self.conv3(x)) x self.lrn(self.relu(self.conv4(x))) x torch.flatten(x, 1) return self.fc2(self.relu(self.fc1(x)))这里 4 层卷积、2 层池化、2 层全连接和论文的表 2 结构一致。nn.LocalResponseNorm对应论文提到的 LRN 层它模仿生物神经的侧抑制响应大的神经元被进一步放大、响应小的被压制从而提升泛化能力。kernel_size1是因为输入已经是降维后的能量向量再做空间卷积没有意义1×1 卷积实际是在特征通道间做线性组合论文里“将输入图像特征向量转化为一维向量”说的就是这一步。3.2 反向传播与权值更新论文强调权值更新的关键在卷积层和子采样层的灵敏度计算。PyTorch 的 autograd 会自动完成但理解梯度来源能帮你在训练不收敛时定位问题import torch.optim as optim model WaveletCNN() criterion nn.CrossEntropyLoss() # 误差代价函数 optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9) for epoch in range(2000): model.train() for x, y in train_loader: optimizer.zero_grad() out model(x) loss criterion(out, y) loss.backward() # 反向传播逐层求梯度 optimizer.step() # 按梯度方向更新权值 if epoch % 100 0: print(fepoch{epoch}, loss{loss.item():.4f})lr0.01、momentum0.9是论文场景下比较稳的组合学习率再大容易在能量特征这种小维度输入上震荡。epoch设到 2000 是因为论文提到训练次数增加到一定程度准确率才收敛但要在验证集上盯住未必每次都要跑满。3.3 关键参数速查参数论文取值调整建议卷积分层4 层卷积 2 层池化数据集小于 1000 幅时减为 2 层卷积全连接神经元输入层后接 512 个神经元能量特征只有 7 维时可降到 3264循环次数2000每 100 轮记录一次损失收敛即停激活函数ReLU高相似度任务可试 LeakyReLU 缓解死区数据增量每轮 100 幅用于观察“数据量—准确率”曲线注意论文提到梯度下降到局部收敛时会出现参数无法保存、每次训练都重新随机初始化的问题。复现时务必加 checkpoint否则你辛苦跑出的权值一次进程退出就没了。4. 鸡蛋与苹果数据集的训练实战与准确率验证4.1 数据集划分与标签映射论文的数据集共 1600 幅训练 1200、测试 400标签规则如下数据集属性标签鸡蛋散养 / 圈养0 / 1苹果山西 / 陕西 / 江苏 / 河南0 / 1 / 2 / 3鸡蛋是二分类苹果是四分类两者共用同一套小波特征和网络只需把输出维度改成 2 或 4。划分时别用随机切分我一般先按拍摄批次分层抽样避免同一批光照的图全挤进训练集导致验证分数虚高。from torch.utils.data import Dataset, DataLoader import os class EnergyDataset(Dataset): def __init__(self, root, label_map): self.samples [] for cls_name, label in label_map.items(): folder os.path.join(root, cls_name) for fn in os.listdir(folder): self.samples.append((os.path.join(folder, fn), label)) self.label_map label_map def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] feat wavelet_energy_feature(path) # 复用第 2 章的特征函数 return torch.tensor(feat, dtypetorch.float32), label train_set EnergyDataset(data_resized/train, {free_range: 0, cage: 1}) train_loader DataLoader(train_set, batch_size64, shuffleTrue)batch_size64在单节点双 GPU、内存 32G 的配置下比较舒适显存吃不满但吞吐够用shuffleTrue保证每轮能量向量的组合顺序不同这对只有 7 维输入的网络很重要否则梯度方向会呈周期性震荡。4.2 准确率随数据量和训练次数的变化论文给出的两条规律值得记下来数据量增加网络参数持续被优化准确率上升数据量不变而训练次数增加参数不会陷入局部最小全局优化效果变好。为了在本地复现这两条曲线我建议写一个循环扫描for n_train in [200, 400, 600, 800, 1000, 1200]: sub_set torch.utils.data.Subset(train_set, range(n_train)) loader DataLoader(sub_set, batch_size64, shuffleTrue) acc train_and_eval(model, loader, test_loader, epochs500) print(ftrain_size{n_train}, test_acc{acc:.4f})这段代码每次扩大 100 幅训练样本跑完打印测试集准确率。论文的实验结果里融合算法的平均准确率在 90% 以上单独小波变换约 78%、单独 CNN 约 82%融合后拉开明显差距这个对比表值得自己再跑一遍验证在本地数据上是否成立。4.3 典型报错与排查路径复现时最容易遇到的三种情况一是 loss 一直下不去多半是能量特征没做归一化量纲差异让梯度被大值特征主导回到第 2 章把特征除总和即可二是训练集 0.99、测试集 0.6这是过拟合按表 3 减少卷积分层或加 dropout三是准确率卡在某个值反复横跳检查是否每次训练都重新随机初始化了权重而没加载历史参数这正是论文专门点出的局部收敛问题。5. 让融合模型稳定过 90% 的三个调参技巧第一个技巧是分清“增强低频”和“去噪高频”的执行顺序。论文明确低频做非线性增强以突出目标对比、高频做小波去噪顺序不能颠倒先对高频去噪再做整体增强阈值会被放大后的系数带偏去噪就失效了。我的做法是把pywt.wavedec2出来的系数先按子带分别处理再waverec2重建重建图只用于可视核验真正的网络输入仍是能量向量这样能直观确认增强是否把背景压了下去。第二个技巧是给 LRN 单独做一组消融。LRN 在这类细纹理任务上收益并不总是正的。可以先跑一组带 LRN、一组去掉 LRN 的对照各训练 500 轮看测试集差别。我在鸡蛋二分类上观察到带 LRN 时收敛更稳但在苹果四分类且样本量偏小时去掉 LRN 后测试准确率反而高两三个点原因是四分类的类间能量差异本来就小侧抑制把弱响应类别直接压没了。这个结论和论文的通用描述不完全一致属于数据集相关的边界情况值得你自己测一遍再决定。第三个技巧是用动量和学习率做联合退火而不是只调一个。小波能量特征维度低、梯度噪声相对小适合从lr0.01起每 300 轮衰减到 0.1 倍同时把动量从 0.9 提到 0.95。这个组合让网络在接近收敛时不容易被单个样本拉偏论文里“训练次数增加但参数不陷局部最小”的现象很大程度就是靠这类退火策略实现的。技巧关键参数观测指标子带处理顺序去噪阈值、增强系数重建图背景灰度方差LRN 消融是否启用、窗口大小测试集准确率差联合退火lr 衰减倍率、momentumloss 曲线尾部抖动幅度最后给一个方便验证的检查脚本骨架把训练过程中每 100 轮的验证准确率记下来画成曲线再对照论文的训练次数图history [] for epoch in range(2000): train_one_epoch(model, train_loader, optimizer, criterion) if epoch % 100 0: acc evaluate(model, test_loader) history.append((epoch, acc)) print(fepoch{epoch}, val_acc{acc:.4f})把history存下来用 matplotlib 画出来如果 500 轮之后准确率曲线还在缓慢上升而不是压平说明训练次数还不够如果曲线在 300 轮附近先冲高后回落那就是过拟合的信号该回头减层或加正则了。evaluate里记得切model.eval()并关掉梯度否则 LRN 和 dropout 在推理阶段仍在改动激活值测出来的准确率会偏低且不稳定。本文还有配套的精品资源点击获取
返回列表