简介这份资源是《基于图方法的单幅图像去模糊》配套源码包面向计算机视觉与图像处理方向的研究者、研究生及开发者聚焦盲图像去模糊这一经典难题——在缺乏模糊核形状与大小等先验信息时仅凭单张模糊照片恢复清晰图像。项目以图理论建模将像素视为节点、相邻像素关系视为边借助图信号处理估计模糊过程并逆向反卷积可应对图像不同区域受非均匀模糊影响的情况。压缩包共51个文件约6.49MB以15个p文件、14个m文件MATLAB脚本与函数为核心辅以11个png、6个jpg、2个bmp测试图像及mat数据、readme与md说明覆盖预处理、图构建、图信号平滑、反卷积与结果评估等环节。已有264人学习适合借此理解算法实现细节、复现实验并探索图像恢复与增强的延伸应用。1. 图盲去模糊到底在解决什么从一张糊片说起你手里有一张因为相机抖动或者物体运动而糊掉的图想把它救回来。如果只知道模糊后的图不知道模糊核长什么样、有多大、方向如何这就是盲去模糊。而“Graph-Based”意味着用图结构来建模像素或特征之间的关系把去模糊当成图上的信息传播问题。这个方向适合两类人一是做图像复原的算法工程师想找一个比传统卷积更灵活的结构二是做手机摄影、监控、医学影像的开发者需要在没有配对训练数据的情况下也能恢复细节。我见过太多人一上来就堆深度网络结果在真实模糊上翻车因为合成模糊和真实模糊的分布差得太远。图方法的优势在于它不依赖规则网格能把非局部相似块连起来对纹理和边缘的恢复更稳。这一章不展开公式只把问题边界划清楚输入是一张模糊图输出是清晰图中间要估计模糊核或者直接端到端映射。后面几章会从图构建、核估计、训练策略到避坑一步步拆开。2. 图结构怎么建从像素图到特征图的三种落地方式2.1 为什么用图代替卷积非局部连接的实际收益卷积核在空间上是局部的感受野靠堆层扩大但模糊核往往把远处像素混在一起。图结构可以把任意两个相似块连边信息一步传到位。常见做法是把图像切成 patch每个 patch 当节点特征相似度当边权。这样在去模糊时一个清晰边缘可以从相似区域借来高频信息。我一般会先用 SLIC 超像素做粗分割再在超像素上建图节点数从百万降到几千计算量可控。另一种是直接在特征图上建动态图每层都重新算邻接矩阵适合端到端训练。选哪种取决于你的算力超像素图适合传统优化动态图适合 GPU 训练。注意图拉普拉斯矩阵的稀疏性很关键全连接图会直接爆显存。2.2 用 Python 构建图像图的最小代码import numpy as np from sklearn.feature_extraction import image from scipy.sparse import csr_matrix def build_patch_graph(img, patch_size8, stride4, k10): # img: 灰度图shape (H, W) patches image.extract_patches_2d(img, (patch_size, patch_size)) # 展平并归一化避免亮度影响相似度 feats patches.reshape(patches.shape[0], -1) feats (feats - feats.mean(axis1, keepdimsTrue)) / (feats.std(axis1, keepdimsTrue) 1e-8) # 计算余弦相似度 norm np.linalg.norm(feats, axis1, keepdimsTrue) feats_norm feats / (norm 1e-8) sim feats_norm feats_norm.T # 每个节点只保留 top-k 邻居构建稀疏邻接矩阵 n sim.shape[0] rows, cols, data [], [], [] for i in range(n): idx np.argsort(sim[i])[-k-1:-1] # 去掉自身 for j in idx: rows.append(i) cols.append(j) data.append(sim[i, j]) adj csr_matrix((data, (rows, cols)), shape(n, n)) return adj这段代码把图像切成重叠 patch每个 patch 当节点用余弦相似度找 top-k 邻居。参数patch_size控制局部细节太小会丢结构太大会模糊边缘我一般从 8 开始试。stride决定重叠程度stride 越小节点越多图越密计算越慢。k是每个节点的邻居数k10 在多数去模糊任务里够用太大反而引入噪声。返回的adj是稀疏矩阵可以直接喂给图卷积或者谱聚类。注意这里只用了灰度图彩色图可以每个通道单独建图再融合或者把 RGB 拼成特征向量。2.3 图拉普拉斯在去模糊优化中的角色建好图之后图拉普拉斯矩阵 L D - A 用来做正则化。在盲去模糊里清晰图 x 的图平滑先验可以写成 x^T L x意思是相似节点上的像素值应该接近。优化目标通常是 min ||y - k * x||^2 λ x^T L x其中 y 是模糊图k 是模糊核* 是卷积。λ 控制平滑强度太大图会糊太小噪声会放大。我一般用 L1 范数代替 L2对异常值更鲁棒。求解时可以用交替方向乘子法把 x 和 k 分开更新。图拉普拉斯是稀疏的矩阵向量乘很快但要注意归一化否则节点度数差异大会导致数值不稳。常见做法是用对称归一化 L_sym I - D^{-1/2} A D^{-1/2}。3. 盲去模糊的核估计图方法怎么绕开传统假设3.1 模糊核的图表示与参数化传统盲去模糊假设核是均匀的或者简单的运动模糊但真实场景里核可能空间变化。图方法可以把核也建成图每个位置的核当节点核之间的相似度当边。这样空间变化的核可以通过图传播来估计。我一般会把图像分成若干区域每个区域估一个核然后用图平滑让相邻区域的核过渡自然。参数化方面运动模糊核可以用线段长度和角度表示散焦核用高斯半径表示。图上的节点特征就是这些参数边权是区域间颜色或深度的相似度。这样做的好处是即使某个区域纹理少、核估计不准也能从相似区域借信息。3.2 交替优化固定核更新图固定图更新核def alternating_optimize(y, adj, init_k, lambda10.1, lambda20.05, iters20): # y: 模糊图adj: 图邻接矩阵init_k: 初始核 from scipy.signal import convolve2d x y.copy() k init_k.copy() L csr_matrix(adj.shape[0], adj.shape[0]) # 实际用归一化拉普拉斯 for it in range(iters): # 固定 k更新 x解线性系统 (K^T K lambda1 * L) x K^T y # 这里简化为梯度下降 for _ in range(5): grad_x convolve2d(convolve2d(x, k, same), k[::-1, ::-1], same) - convolve2d(y, k[::-1, ::-1], same) grad_x lambda1 * L.dot(x.flatten()).reshape(x.shape) x - 0.01 * grad_x # 固定 x更新 k在核空间做梯度下降 for _ in range(5): grad_k convolve2d(convolve2d(x, k, same), x[::-1, ::-1], same) - convolve2d(y, x[::-1, ::-1], same) k - 0.01 * grad_k k np.clip(k, 0, None) k / k.sum() 1e-8 return x, k这个交替优化框架里lambda1控制图平滑强度lambda2本来用于核的图正则这里省略了。iters是交替轮数一般 20 轮左右收敛。内层梯度下降步数可以调步长 0.01 是经验值太大发散太慢收敛。注意每次更新 k 后要非负归一化否则能量不守恒。实际跑的时候图拉普拉斯 L 要用稀疏矩阵否则内存扛不住。如果核估计不准可以加一个核的图先验相邻区域的核应该相似用 L 作用在核参数上。3.3 从粗到细多尺度图金字塔的搭建真实模糊核可能很大直接在全分辨率上估计算量大且容易陷局部极小。常见做法是建图像金字塔从低分辨率开始估核再逐级上采样作为下一级初始值。图金字塔可以每层单独建图也可以跨层连边。我一般会在每层用超像素建图低分辨率节点少图小优化快高分辨率节点多但核已经接近真值只需要微调。跨层连边可以让低层的结构信息传到高层比如低层的一个超像素对应高层多个超像素边权用面积重叠度。这样从粗到细核估计的鲁棒性明显提升。注意每层上采样核时要插值否则会引入块效应。4. 训练与推理图神经网络在去模糊中的实操配置4.1 图卷积层的实现与参数选择import torch import torch.nn as nn import torch.nn.functional as F class GraphConv(nn.Module): def __init__(self, in_features, out_features, biasTrue): super().__init__() self.weight nn.Parameter(torch.FloatTensor(in_features, out_features)) self.bias nn.Parameter(torch.FloatTensor(out_features)) if bias else None self.reset_parameters() def reset_parameters(self): nn.init.xavier_uniform_(self.weight) if self.bias is not None: nn.init.zeros_(self.bias) def forward(self, x, adj): # x: (N, in_features), adj: 稀疏归一化邻接矩阵 (N, N) support torch.mm(x, self.weight) out torch.spmm(adj, support) if self.bias is not None: out out self.bias return out这个图卷积层用稀疏矩阵乘法实现adj要是归一化的否则节点度数差异会导致梯度爆炸。in_features和out_features根据你的特征维度定去模糊任务里第一层可以设 64后面 128 或 256。bias一般保留。初始化用 Xavier 均匀分布适合图卷积。前向传播里torch.spmm是稀疏矩阵乘稠密矩阵比稠密乘法省显存。注意如果图是动态的每层都要重新算adj这时可以把adj作为 forward 的参数传进来。训练时用 Adam 优化器学习率 1e-3 起步权重衰减 1e-5。4.2 损失函数像素损失、感知损失与图平滑损失的组合def total_loss(pred, target, adj, lambda_pix1.0, lambda_per0.1, lambda_graph0.01): # 像素 L1 损失 loss_pix F.l1_loss(pred, target) # 感知损失用预训练 VGG 提取特征 vgg torchvision.models.vgg16(pretrainedTrue).features[:16].eval() for p in vgg.parameters(): p.requires_grad False feat_pred vgg(pred.repeat(1, 3, 1, 1)) feat_target vgg(target.repeat(1, 3, 1, 1)) loss_per F.mse_loss(feat_pred, feat_target) # 图平滑损失pred 在图上应该平滑 pred_flat pred.view(pred.shape[0], -1) loss_graph torch.mean(pred_flat adj pred_flat.t()) return lambda_pix * loss_pix lambda_per * loss_per lambda_graph * loss_graph像素 L1 损失保底感知损失让纹理更自然图平滑损失约束相似块一致。lambda_pix一般最大1.0 左右lambda_per0.1 量级太大会让颜色偏移lambda_graph0.01 量级太大图会过度平滑。注意感知损失用的 VGG 要冻结参数且输入要归一化到 ImageNet 分布。图平滑损失里adj是归一化邻接矩阵pred_flat adj pred_flat.t()算的是所有节点对的加权平方差取均值。如果显存不够可以只对部分节点采样算。训练时先训像素损失稳定后再加感知和图损失否则容易震荡。4.3 推理阶段图构建与网络前向的时序推理时先对输入模糊图建图可以用超像素或者固定网格。如果训练时用的是动态图推理也要动态算但可以固定 top-k 邻居加速。我一般会先把图构建好存成稀疏矩阵然后网络前向。注意推理时不需要梯度用torch.no_grad()包起来。如果图很大可以分块推理块之间重叠几个像素避免边界效应。输出清晰图后可以再做一次非局部均值或者导向滤波做后处理但别过度否则会引入伪影。整个流程在 GPU 上512x512 的图大概几十毫秒具体看图和网络深度。5. 避坑与排查图盲去模糊里最容易翻车的五个点5.1 图构建太密导致显存爆炸现象一跑就 OOM即使 batch size 设为 1。原因patch 数量太多top-k 邻居又大邻接矩阵非零元过多。解决增大 patch_size 或 stride减少节点数降低 k 到 5 左右用稀疏矩阵而不是稠密矩阵如果还不行改用超像素建图节点数降一个数量级。5.2 核估计收敛到平凡解现象估计出的核是一个 delta 函数去模糊后图没变化。原因优化时核没有归一化或者没有非负约束梯度把核推向零。解决每次更新后k np.clip(k, 0, None)再k / k.sum()加核的图先验让相邻区域核相似从粗到细多尺度估计低分辨率先给一个好初始值。5.3 图平滑损失过大导致细节丢失现象去模糊后图很干净但纹理没了像水彩画。原因lambda_graph设得太大图拉普拉斯正则过度平滑。解决降低lambda_graph到 0.001 量级改用 L1 范数而不是 L2只在低分辨率层用图平滑高分辨率层关掉或者用自适应权重边缘处权重小。5.4 训练集和真实模糊分布不匹配现象合成模糊上 PSNR 很高真实照片上全是伪影。原因合成模糊用高斯核或者线性运动核真实模糊有噪声、饱和、非线性响应。解决在合成时加噪声、加 JPEG 压缩、模拟相机响应曲线用域适应在真实模糊上做无监督微调或者直接用真实模糊数据集但要注意配对问题。5.5 图邻接矩阵归一化错误现象训练 loss 不下降或者 NaN。原因邻接矩阵没有归一化节点度数差异大梯度爆炸。解决用对称归一化D^{-1/2} A D^{-1/2}或者行归一化D^{-1} A加一个小的对角线项A I避免孤立节点检查稀疏矩阵的索引是否越界。6. 进阶技巧用图注意力机制动态调整邻居权重图注意力网络在去模糊里比固定权重的图卷积更灵活。核心是每个节点对邻居的权重不是预先算好的相似度而是通过网络学习。实现上先算节点特征之间的注意力系数再 softmax 归一化最后加权聚合。我一般会在图卷积层里加一个注意力头头数 4 或 8每个头独立算权重再拼接。这样在模糊核空间变化时网络能自动关注更可靠的邻居。验证方法很简单在合成模糊上对比固定图卷积和注意力图卷积的 PSNR通常能涨 0.3 到 0.5 dB。但要注意注意力会增大计算量推理时间可能翻倍。如果部署在移动端可以只在前几层用注意力后面用固定图。另一个技巧是残差图连接把输入特征和输出特征相加避免过平滑。我自己的习惯是每次改图结构先在小数据集上跑 10 个 epoch看 loss 曲线和验证集 PSNR再决定要不要全量训练。图盲去模糊不是玄学但图怎么建、权重怎么学确实需要反复试。希望帮到你。本文还有配套的精品资源点击获取