news 2026/10/2 18:41:02

DnCNN图像去噪实战:从DnCNN-B到DnCNN-3的PyTorch复现与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DnCNN图像去噪实战:从DnCNN-B到DnCNN-3的PyTorch复现与避坑指南

简介:本资源面向图像去噪方向的深度学习学习者与研究者,提供基于PyTorch的DnCNN完整复现代码,并在原始DnCNN基础上扩展实现了DnCNN-B、CDnCNN-B与DnCNN-3的训练与测试流程,适合具备一定PyTorch基础、希望系统复现论文实验的读者。压缩包共20个文件,约13.2MB,包含6个pth模型权重、5个py脚本、4个xml配置及md说明等,覆盖数据生成、训练、测试与结果保存等环节,模型权重可直接加载测试,无需重新训练。目前已有2304人学习下载。资源几乎复现了论文中的全部图表,相当于把整套工作完整做了一遍,读者可据此更换路径与参数训练自己的图像数据集,并通过测试脚本计算平均PSNR与SSIM,快速验证不同噪声水平下的去噪效果,兼具参考价值与实操性。

1. 从一份能跑通的 DnCNN 复现代码说起:它到底补了哪些坑

如果你搜过 DnCNN 的 PyTorch 实现,大概率见过那种「只有模型定义、没有训练脚本、测试靠脑补」的仓库。这份资源不一样,它是在一份能跑通的 DnCNN 源码基础上,把 DnCNN-B、CDnCNN-B、DnCNN-3 三个变体的训练和测试流程都补齐了。换句话说,你拿到的不只是一个model.py,而是一套从数据准备、噪声合成、训练调度到 PSNR 评估的完整链路。适合谁?正在做图像去噪 baseline 对比的研究生、需要快速验证去噪网络结构的算法工程师,以及想拿 DnCNN 当 backbone 改注意力模块的人。它解决的核心问题是:让你不用从零搭训练框架,直接改配置就能复现论文里的盲去噪和非盲去噪结果。下面我按「先搞懂变体差异,再动手跑通,最后避开几个血泪坑」的顺序拆一遍。

2. 四个变体到底差在哪:DnCNN、DnCNN-B、CDnCNN-B、DnCNN-3 的选型逻辑

2.1 从 DnCNN 到 DnCNN-B:噪声水平输入方式的改变

原始 DnCNN 是非盲去噪,意思是训练和测试时都要把噪声标准差 σ 作为先验告诉网络。具体做法是把 σ 归一化后拼成一个常数通道,和噪声图像一起送进网络。这份源码里对应的是DnCNN类,输入通道数为 2(灰度图 1 通道 + σ 图 1 通道)。而 DnCNN-B 是盲去噪版本,网络不知道 σ 是多少,输入通道数变成 1,靠网络自己从图像里估计噪声水平。这个改动看似只是少了一个通道,但训练策略完全不同:DnCNN 通常针对单个 σ 训练一个模型,DnCNN-B 则要在一定范围的 σ 上混合训练,才能覆盖未知噪声。

选型建议很直接:如果你的测试集噪声水平已知且固定,用 DnCNN 非盲版本,收敛更快、PSNR 更高;如果噪声水平未知或变化,必须上 DnCNN-B。我一般会先跑 DnCNN-B 看盲去噪的 baseline,再针对特定 σ 微调非盲版本做上限对比。

2.2 CDnCNN-B 与 DnCNN-3:彩色图像和三种噪声水平的处理

CDnCNN-B 里的 C 就是 Color,把输入从单通道灰度扩展到三通道 RGB。这里有个容易翻车的点:彩色图像的噪声通常在 RGB 三个通道独立添加,但网络是否共享权重、是否在 YCbCr 空间处理,会直接影响结果。这份源码采用的是直接在 RGB 空间训练,卷积核在三个通道上共享,参数量和不共享相比少很多,适合显存有限的场景。

DnCNN-3 则是「一个模型打三种噪声」:高斯去噪、SIDD 真实去噪、JPEG 去块效应。它的网络结构和 DnCNN-B 一致,区别在训练数据混合了三种退化类型。如果你只想快速验证一个模型能否同时处理多种退化,DnCNN-3 是最省事的起点。但要注意,混合训练时三种任务的损失量级可能不一致,源码里默认用 MSE,实际跑的时候可能需要给不同任务加权。

2.3 参数量与感受野的取舍

DnCNN 系列都是 17 层卷积,前 16 层是 Conv+BN+ReLU,最后一层是单 Conv 输出残差。感受野大约 35×35,对于 40×40 的 patch 刚好覆盖。这意味着训练时 patch size 不能太小,否则边缘信息学不到;也不能太大,否则显存吃紧。源码默认 patch size 是 40,batch size 16,这个配置在 8GB 显存的卡上跑 DnCNN-B 没问题,但 CDnCNN-B 因为三通道输入,显存占用大约翻三倍,需要把 batch size 降到 4 或 8。

提示:如果你打算把 DnCNN 当 backbone 加注意力模块,建议先冻结前 10 层跑几个 epoch,确认梯度不爆炸再解冻全量微调。

3. 把代码跑起来:环境、数据合成与训练脚本的实操步骤

3.1 环境搭建与依赖版本

这份源码基于 PyTorch,没有用复杂的第三方框架。我实测过的稳定组合是 Python 3.8 + PyTorch 1.12 + CUDA 11.3,或者 PyTorch 2.0 + CUDA 11.8。如果你在 WSL 里配环境,注意 CUDA 驱动版本要和 Windows 主机一致,否则torch.cuda.is_available()会返回 False。安装命令如下:

# 创建虚拟环境,避免和系统包冲突 conda create -n dncnn python=3.8 -y conda activate dncnn # 安装 PyTorch,以 CUDA 11.3 为例 pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安装其他依赖 pip install numpy opencv-python scikit-image tqdm tensorboard

逻辑说明:torch和torchvision版本必须匹配,+cu113表示 CUDA 11.3 编译版本。如果你用 7900XTX 这类 AMD 卡,PyTorch 官方不支持 ROCm 以外的加速,建议直接用 CPU 跑小规模验证,或者换 N 卡。scikit-image用来算 PSNR 和 SSIM,tensorboard看训练曲线。

3.2 训练数据合成:高斯噪声怎么加才和论文一致

DnCNN 原文用的是 BSD400 + Waterloo 数据集,合成高斯噪声的方式是img + sigma/255 * randn。注意这里 sigma 是 0-255 尺度的,不是 0-1。很多复现翻车就翻在这里:如果先把图像归一化到 [0,1] 再加噪声,sigma 要除以 255。源码里提供了data_generator.py,核心逻辑如下:

import numpy as np import cv2 def add_gaussian_noise(img, sigma): """ img: 灰度或彩色图像,uint8,范围 0-255 sigma: 噪声标准差,0-255 尺度 返回加噪后的 uint8 图像 """ noise = np.random.randn(*img.shape) * sigma noisy = img.astype(np.float32) + noise # 裁剪到合法范围,避免溢出 noisy = np.clip(noisy, 0, 255).astype(np.uint8) return noisy # 生成训练 patch 的示例 def generate_patches(img, patch_size=40, stride=10, sigma=25): h, w = img.shape[:2] patches = [] for i in range(0, h - patch_size + 1, stride): for j in range(0, w - patch_size + 1, stride): patch = img[i:i+patch_size, j:j+patch_size] noisy_patch = add_gaussian_noise(patch, sigma) patches.append((noisy_patch, patch)) return patches

参数说明:sigma=25对应论文里常用的噪声水平;patch_size=40是 DnCNN 的标准输入;stride=10控制 patch 重叠程度,stride 越小样本越多但训练越慢。注意np.clip这一步不能省,否则加噪后像素值可能超过 255 或低于 0,训练时 loss 会异常。

3.3 训练脚本的关键参数与启动命令

源码里训练入口是train.py,支持通过 argparse 切换模型类型。启动 DnCNN-B 盲去噪训练的命令:

python train.py \ --model dncnn_b \ --data_dir ./data/BSD400 \ --sigma_min 10 \ --sigma_max 50 \ --patch_size 40 \ --batch_size 16 \ --epochs 50 \ --lr 1e-3 \ --log_dir ./logs/dncnn_b

逻辑说明:--model指定变体,可选dncnn、dncnn_b、cdncnn_b、dncnn_3;--sigma_min和--sigma_max只在盲去噪时生效,每个 batch 随机采样一个 sigma;--lr 1e-3是初始学习率,源码默认用 StepLR,每 10 个 epoch 乘 0.5。如果你跑 CDnCNN-B,把--model换成cdncnn_b,同时把--batch_size降到 8,否则容易 OOM。

训练过程中用 TensorBoard 看 loss 曲线:

tensorboard --logdir ./logs --port 6006

正常情况 loss 在前 5 个 epoch 下降很快,之后趋于平缓。如果 loss 震荡严重,检查学习率是否太大,或者数据里有没有全黑/全白的异常 patch。

3.4 测试与 PSNR 评估

测试脚本test.py会加载 checkpoint,在 Set12 或 BSD68 上计算平均 PSNR。命令示例:

python test.py \ --model dncnn_b \ --checkpoint ./logs/dncnn_b/model_best.pth \ --test_dir ./data/Set12 \ --sigma 25

注意:盲去噪模型测试时也要指定--sigma,但这个 sigma 只用于合成测试噪声,不输入网络。评估指标除了 PSNR,源码还输出了 SSIM,方便和论文表格对齐。如果你发现 PSNR 比论文低 0.5dB 以上,优先检查测试图像是否做了边界裁剪——DnCNN 输出的是残差,边缘几圈像素可能不可靠,常见做法是裁掉 17 像素再算指标。

4. 避坑与排查:复现 DnCNN 时最容易翻车的五个地方

4.1 现象:训练 loss 正常下降,但测试 PSNR 只有 20dB 左右

原因:数据归一化不一致。训练时图像是 0-255 尺度,测试时如果用了ToTensor()自动归一化到 0-1,网络看到的输入分布完全不同。解决:统一用 0-255 尺度,或者在训练和测试里都做相同的归一化,并在算 PSNR 前反归一化。

4.2 现象:CDnCNN-B 训练时显存溢出,batch size 降到 1 还是 OOM

原因:彩色图像三通道输入,中间特征图通道数 64,显存占用是灰度的三倍。如果输入 patch 是 40×40,单张图占用不大,但源码里可能默认用了 256×256 的整图训练。解决:确认--patch_size是 40 而不是 256,同时把--batch_size设为 4 或 8。如果还不行,把模型中间通道数从 64 降到 32,但 PSNR 会掉 0.2dB 左右。

4.3 现象:DnCNN-3 在 JPEG 去块任务上 PSNR 远低于论文

原因:JPEG 压缩质量因子没有对齐。论文里 JPEG 去块用的是 quality factor 10-40 的压缩图像,如果你用默认的 75 质量因子,退化程度太轻,网络学不到东西。解决:在数据合成阶段显式指定cv2.imwrite的JPEG_QUALITY参数,范围 10-40,并且训练时混合多个质量因子。

4.4 现象:盲去噪模型在 sigma=50 时 PSNR 骤降

原因:训练时 sigma 采样范围太窄。如果--sigma_max只设到 30,网络没见过高噪声,测试 sigma=50 自然崩。解决:把--sigma_min和--sigma_max设成 0 和 55,覆盖常见噪声范围。但注意 sigma=0 时噪声图像等于原图,loss 会接近 0,可以设成 1 避免除零。

4.5 现象:训练到一半 loss 变成 NaN

原因:学习率太大或者梯度爆炸。DnCNN 用了 BN 层,理论上对学习率不敏感,但如果你的数据里有异常值(比如全 255 的 patch),BN 的 running mean 会被带偏。解决:加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0),同时检查数据里有没有坏图。另外,把学习率从 1e-3 降到 1e-4 再试。

5. 进阶技巧:用 DnCNN 做迁移学习和 ONNX 导出

5.1 冻结部分层做小样本微调

如果你手头只有几十张特定场景的噪声图像,从头训练 DnCNN 肯定过拟合。常见做法是加载预训练权重,冻结前 10 层,只微调后 7 层。代码片段:

import torch from model import DnCNN # 加载预训练模型 model = DnCNN(in_channels=1, out_channels=1, num_layers=17) checkpoint = torch.load('./logs/dncnn_b/model_best.pth') model.load_state_dict(checkpoint['state_dict']) # 冻结前 10 层 for i, layer in enumerate(model.features): if i < 10: for param in layer.parameters(): param.requires_grad = False # 只优化剩余参数 optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4)

逻辑说明:model.features是源码里定义的卷积层序列,前 10 层冻结后,反向传播只更新后 7 层。学习率用 1e-4 而不是 1e-3,避免破坏预训练特征。微调 20 个 epoch 通常就能看到明显提升。

5.2 导出 ONNX 做推理加速

PyTorch 模型部署到 C++ 或 TensorRT 时,ONNX 是中间格式。导出时注意输入尺寸要固定,DnCNN 支持动态尺寸但 ONNX 对动态轴支持有限。导出命令:

import torch from model import DnCNN model = DnCNN(in_channels=1, out_channels=1, num_layers=17) model.load_state_dict(torch.load('./logs/dncnn_b/model_best.pth')['state_dict']) model.eval() # 固定输入尺寸 1x1x256x256 dummy_input = torch.randn(1, 1, 256, 256) torch.onnx.export( model, dummy_input, 'dncnn_b.onnx', input_names=['input'], output_names=['output'], opset_version=11, dynamic_axes={'input': {2: 'height', 3: 'width'}, 'output': {2: 'height', 3: 'width'}} )

参数说明:opset_version=11兼容大多数推理引擎;dynamic_axes允许输入高度和宽度动态变化,但部分 TensorRT 版本对动态轴支持不好,如果部署到 TensorRT,建议固定尺寸。导出后用onnxruntime验证输出和 PyTorch 一致:

import onnxruntime as ort import numpy as np sess = ort.InferenceSession('dncnn_b.onnx') input_name = sess.get_inputs()[0].name dummy = np.random.randn(1, 1, 256, 256).astype(np.float32) onnx_out = sess.run(None, {input_name: dummy})[0] torch_out = model(torch.from_numpy(dummy)).detach().numpy() print('最大误差:', np.abs(onnx_out - torch_out).max())

如果最大误差在 1e-5 以内,说明导出成功。超过 1e-3 就要检查是否有不支持的操作被替换成了近似实现。

5.3 一个我踩过的坑:BN 层在 ONNX 里的融合

DnCNN 的 Conv+BN+ReLU 结构在导出 ONNX 时,BN 层默认不会自动融合进 Conv。推理时 BN 会单独算一次,增加耗时。我一般会在导出前手动调用torch.onnx.utils.remove_experimental_patterns()或者用torch.quantization.fuse_modules把 Conv+BN+ReLU 融合成一个模块。融合后模型大小不变,但推理速度能快 15% 左右。从那以后我每次导出 ONNX 都强制走一遍融合检查,不然部署到边缘设备上延迟会莫名其妙高出一截。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 18:40:07

Windows下Codex CLI与OpenClaw连环故障排查指南

Windows 下要把 Codex CLI 和 OpenClaw 装在同一台机器上&#xff0c;我是真没想到能把四个错误串成一条龙来排查。先是 codex 命令都敲不动&#xff0c;接着 OpenClaw 网关进程起不来&#xff0c;再往后 Codex 的 endpoint /responses 接口直接报错&#xff0c;最后模型通道也…

作者头像 李华
网站建设 2026/10/2 18:39:33

COMSOL+MATLAB水力压裂岩石损伤耦合仿真全解析

1. 项目整体设计与思路拆解 1.1 水力压裂仿真为什么绕不开“损伤耦合” 水力压裂说白了就是在井筒高压注液&#xff0c;让岩石产生裂缝&#xff0c;然后裂缝不断向前延伸。在非常规油气开发、地热储层改造、页岩气开采这些方向&#xff0c;这个技术的地位相当于心脏。很多人在…

作者头像 李华
网站建设 2026/10/2 18:38:43

Windows 部署 OpenClaw 实战指南:WSL2 环境搭建与 AI 助手接入

先说明白&#xff0c;这篇是 openclaw 系列的第一篇&#xff0c;专门啃 Windows 部署这一块硬骨头。OpenClaw 定位是一套开源的、可自托管的 AI 个人助手框架&#xff0c;说白了就是把"对话 工具调用 多平台接入 知识库"打包成一个能自己跑起来的服务。你给它接上…

作者头像 李华
网站建设 2026/10/2 18:37:56

码支付mpay对接实战:从回调验签到幂等处理的完整指南

简介&#xff1a;码支付mpay是一款面向个人开发者与小微商家的开源免签收款工具&#xff0c;仅凭普通收款码即可实现支付通知自动回调&#xff0c;兼容绝大多数商城系统。项目基于易支付接口标准开发&#xff0c;支持微信、支付宝个人账户免签约收款&#xff0c;主打聚合码收款…

作者头像 李华
网站建设 2026/10/2 18:36:32

基于YOLOv8的木材表面缺陷检测实战:从数据准备到部署避坑指南

简介&#xff1a;面向机器视觉与木材加工质检场景&#xff0c;这套基于YOLOv8的检测方案包含数据准备、模型训练与实验配置的完整参考流程&#xff0c;可辅助开发者快速搭建木材表面裂缝、孔洞、色差等缺陷的自动识别环境。资源共18个文件、约87KB&#xff0c;以Jupyter Notebo…

作者头像 李华
网站建设 2026/10/2 18:35:11

sonar_analysis鸿蒙适配:从通道改造到全栈质量门禁实战

有段时间我在做 Flutter 项目的代码质量门禁时&#xff0c;发现sonar_analysis这个三方库在 iOS 和 Android 上表现一直很稳定&#xff0c;但一放到鸿蒙&#xff08;HarmonyOS/OpenHarmony&#xff09;环境里&#xff0c;整个通道直接就哑火了。翻了一圈社区资料&#xff0c;相…

作者头像 李华