Pytorch图像去噪实战(一):从0复现DnCNN并解决训练不收敛问题(附完整工程+踩坑总结)

Pytorch图像去噪实战(一):从0复现DnCNN并解决训练不收敛问题(附完整工程+踩坑总结)


一、问题场景(真实项目问题复盘)

最近在做一个OCR识别项目时,我遇到一个很典型但非常棘手的问题:

用户上传图片噪声严重(低光 + 压缩 + 二次截图),导致OCR识别率从92%直接掉到60%左右。

一开始我尝试了传统方法:

  • 高斯滤波 → 图像变糊,文字边缘直接没了
  • 中值滤波 → 细节损失严重
  • 双边滤波 → 对复杂噪声无效

最终结论:

传统滤波无法处理"结构性噪声 + 压缩噪声",必须引入深度学习模型

于是我选用了经典模型:DnCNN(2017)

但实际落地过程中,我遇到了几个关键问题:

  • 模型训练不收敛
  • loss震荡
  • 输出图像过度平滑

这篇文章就是完整复盘这个过程,并给出工程级解决方案。


二、本文解决的核心问题

这篇文章重点解决:

  • 如何从0实现DnCNN
  • 为什么很多人训练DnCNN效果很差
  • 如何构建稳定训练流程
  • 如何评估去噪效果(不仅看loss)
  • 如何避免"过度平滑"问题

三、DnCNN核心原理(为什么有效)

传统去噪思路:

直接学习: noisy → clean

DnCNN改为:

noisy → noise(残差学习)

数学表达:

y = x + v

模型学习:

F(y) = v

最终输出:

x̂ = y - F(y)


为什么残差学习更容易训练?

原因有3点:

1)噪声分布更简单(接近高斯)

2)梯度更稳定

3)网络更容易收敛

👉 这也是DnCNN成功的核心


四、工程结构(建议收藏)

复制代码
dncnn_project/
├── data/
│   ├── train/
│   ├── val/
├── model/
│   └── dncnn.py
├── dataset.py
├── train.py
├── eval.py

五、数据构建(最容易被忽略的关键点)

1. 不要直接用带噪数据!

很多人直接训练失败,就是因为:

❌ 数据不规范

❌ 噪声不一致


正确做法:人工加噪

python 复制代码
import torch

def add_noise(img, sigma=25):
    noise = torch.randn_like(img) * sigma / 255.0
    return img + noise

进阶建议(重要)

不要只用一个sigma:

python 复制代码
sigma_list = [15, 25, 50]

👉 这样模型泛化能力会明显提升


六、模型实现(带关键设计解释)

python 复制代码
import torch
import torch.nn as nn

class DnCNN(nn.Module):
    def __init__(self, channels=1, depth=17):
        super().__init__()

        layers = []

        # 输入层
        layers.append(nn.Conv2d(channels, 64, 3, padding=1))
        layers.append(nn.ReLU(inplace=True))

        # 中间层
        for _ in range(depth - 2):
            layers.append(nn.Conv2d(64, 64, 3, padding=1))
            layers.append(nn.BatchNorm2d(64))
            layers.append(nn.ReLU(inplace=True))

        # 输出层(预测噪声)
        layers.append(nn.Conv2d(64, channels, 3, padding=1))

        self.net = nn.Sequential(*layers)

    def forward(self, x):
        noise = self.net(x)
        return x - noise

关键设计点

  • 使用BatchNorm → 加速收敛
  • 深度17层 → 平衡效果与计算
  • 输出预测noise → 残差学习

七、训练流程(完整可复现)

python 复制代码
import torch

model = DnCNN().cuda()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = torch.nn.MSELoss()

for epoch in range(30):
    for noisy, clean in loader:

        noisy = noisy.cuda()
        clean = clean.cuda()

        output = model(noisy)
        loss = criterion(output, clean)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    print(f"Epoch {epoch}: loss={loss.item():.6f}")

八、为什么很多人训练失败?(核心踩坑)

坑1:loss不下降

原因:

  • 学习率过大
  • BN不稳定

解决:

python 复制代码
lr = 1e-4

坑2:输出模糊(最常见)

原因:

  • 过度优化MSE
  • 模型学会"平均化"

👉 本质:MSE倾向于平滑解


解决方案(重点)

1)加入SSIM损失

python 复制代码
loss = mse_loss + 0.1 * (1 - ssim)

2)减少训练epoch(避免过拟合)


坑3:训练不收敛

原因:

  • 数据未归一化

必须:

python 复制代码
img = img / 255.0

九、评估指标(不能只看loss)

推荐使用:

1. PSNR

python 复制代码
import math

def psnr(img1, img2):
    mse = ((img1 - img2) ** 2).mean()
    return 20 * math.log10(1.0 / math.sqrt(mse))

2. SSIM(更接近人眼)

👉 强烈建议一起使用


十、实验结果(真实效果)

在实际OCR数据上:

方法 PSNR OCR准确率
原图 - 60%
DnCNN +6dB 85%

十一、适合收藏总结(重点)

完整流程

  1. 数据准备(干净图)
  2. 添加噪声(多sigma)
  3. 构建DnCNN
  4. 训练(MSE + 可选SSIM)
  5. 评估(PSNR + SSIM)

避坑清单(强烈建议收藏)

  • 必须归一化
  • 不要单一噪声
  • 学习率不要大
  • BN可能不稳定
  • MSE会导致模糊

十二、优化方向(进阶)

DnCNN的局限:

  • 对复杂噪声效果一般
  • 细节恢复不足

建议进阶:

  • UNet(结构更强)
  • Attention UNet
  • SwinIR(Transformer)
  • Diffusion(最强)

结尾总结

DnCNN是一个非常好的入门模型,但真正的价值不在模型本身,而在:

理解"残差学习 + 噪声建模"的思想


下一篇

Pytorch图像去噪实战(二):UNet去噪模型深度解析(结构级优化)

相关推荐
bst@微胖子1 小时前
PyTorch深度学习框架之基于CNN的手机价格分类任务
pytorch·深度学习·cnn
AIminminHu1 小时前
(AI篇)OpenGL渲染与几何内核那点事-(二-1-(12):给AI一副“身体”有多难?从“缸中之脑”到R2-D2,一文看透具身智能60年进化血泪史
人工智能·具身智能
a7963lin1 小时前
c#如何实现幂等消费_c#幂等消费的几种常见用法
jvm·数据库·python
kexnjdcncnxjs1 小时前
Redis怎样实现Session的分布式共享
jvm·数据库·python
三克的油1 小时前
YOLOV5数据学习
人工智能·学习·yolo
海兰1 小时前
【第22篇】Evaluation Example
人工智能·spring boot·log4j·alibaba·spring ai
wx_xsooop1 小时前
iOS 审核 上架 被拒 4.3 【深度进阶】
python·编程·技术
喵叔哟1 小时前
大模型蒸馏全栈实战:从Claude黑盒克隆到开源模型轻量化落地--目录
人工智能
数据牧羊人的成长笔记1 小时前
分类算法的评价+KMeans聚类与降维算法+决策树与集成学习
人工智能·分类·数据挖掘