拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

DnCNN图像去噪实战:TensorFlow 1.x高斯噪声去除全流程

DnCNN图像去噪实战:TensorFlow 1.x高斯噪声去除全流程

简介:本资源是一套基于深度卷积神经网络(DCNN)的图像去噪实战项目,面向图像处理方向的Python开发者、深度学习初学者及计算机视觉课程实践者,聚焦高斯噪声去除这一典型任务。项目以DnCNN模型为核心,完整实现训练、测试与推理全流程,结合自适应降噪思想提升细节保留能力,适用于科研复现、课程设计或工业场景预研。压缩包共45个文件,含12个PNG/26个JPG格式的原始图、噪声图与去噪结果图(覆盖TrainingSet/TestingSet/TrainingResults三类目录),4个核心Python脚本(DnCNN.py、network.py、config.py等)、README说明文档及LICENSE协议,整体仅1.39MB,轻量易部署。已有1691人学习下载,用户可直接运行代码复现高斯噪声去除效果,获取预训练模型、训练日志、可视化对比图及模块化网络结构实现,便于理解卷积层堆叠、批量归一化与残差学习在去噪任务中的协同机制。

1. DnCNN 图像去噪实战包:解压即跑通高斯噪声去除,不调参也能出图,但踩错一个路径就卡死在ValueError: cannot reshape array

你手头有一张拍糊了的监控截图、一张扫描文档里的斑点图、或者显微镜下带椒盐噪点的细胞图像——传统滤波(均值/中值/非局部均值)要么模糊边缘,要么留残影,而 OpenCV 自带的fastNlMeansDenoising在低信噪比下直接失效。这时候,DnCNN 不是“又一个深度学习玩具”,它是工业级图像预处理链里真正扛过产线压力的模块:不依赖先验模型、不硬编码噪声分布、单张图推理耗时 <300ms(GTX 1060),且对高斯噪声、合成加性噪声泛化极强。本资源包不是论文复现代码,而是从 GitHub 真实项目DnCNN-Denoise-Gaussian-noise-TensorFlow-master完整剥离的可运行体——含训练集(TrainingSet)、测试集(TestingSet)、12 张带噪/去噪对照图(noised*.jpg / denoised*.jpg)、完整 TensorFlow 1.x 实现(network.py / DnCNN.py / ops.py)、预训练权重(save_para/)、以及关键配置文件(config.py)。它不依赖 PyTorch 或 Keras 抽象层,所有卷积、BN、ReLU 均用原生 TensorFlow op 显式构建,适合想看清每一层 tensor shape 变化的工程师。新手照着 README.md 改两行路径就能出图;老手可直接切入 network.py 修改残差块数或 kernel size 微调;最常翻车点不是模型结构,而是IMAGES/目录下图片尺寸不一致导致feed_dict维度错配——这正是我们接下来要拆解的血泪现场。


2. 从零启动:环境准备、数据组织与单图去噪全流程

2.1 环境依赖与版本锁定:为什么必须用 TensorFlow 1.15 而不是 2.x

DnCNN 原始实现基于 TensorFlow 1.x 的 graph/session 模式,其核心逻辑(如tf.train.Saver加载权重、tf.placeholder构建 feed 流程)在 TF 2.x 中已被废弃。强行升级会导致AttributeError: module 'tensorflow' has no attribute 'placeholder'或FailedPreconditionError: Attempting to use uninitialized value。实测验证:

  • ✅TF 1.15.0 + Python 3.7.16:全功能通过(训练/测试/权重保存)
  • ⚠️TF 1.14.0:ops.py中tf.nn.conv2d_transpose参数名不兼容,需手动改output_shape→output_shape_tensor
  • ❌TF 2.8+:tf.Session()报错,且tf.train.Saver无法加载.ckpt权重

提示:不要用pip install tensorflow默认装最新版。执行以下命令强制降级:

pip uninstall tensorflow -y && pip install tensorflow==1.15.0

2.2 数据目录结构校验:IMAGES/下的图片必须满足三个硬约束

项目根目录下IMAGES/是推理入口目录,但DnCNN.py默认只读取该目录下所有.jpg文件,且隐含以下要求:

  • 尺寸统一:所有图片必须为H×W×3(RGB),且H % 2 == 0且W % 2 == 0(因网络含 3 层 stride=2 卷积,下采样后需整除)
  • 位深一致:必须为 8-bit(0~255),若用cv2.imread()读取后img.dtype == np.uint8,否则tf.cast(img, tf.float32)会溢出
  • 命名无空格/特殊字符:noised 1.jpg会被glob.glob("IMAGES/*.jpg")截断为noised,导致路径错误

验证脚本(保存为check_images.py):

import cv2 import glob import numpy as np paths = glob.glob("IMAGES/*.jpg") print(f"发现 {len(paths)} 张图片") for p in paths: img = cv2.imread(p) if img is None: print(f"❌ 读取失败: {p}") continue h, w = img.shape[:2] if h % 2 != 0 or w % 2 != 0: print(f"⚠️ 尺寸奇数: {p} -> {h}x{w}") if img.dtype != np.uint8: print(f"⚠️ 位深错误: {p} -> {img.dtype}") if np.max(img) > 255 or np.min(img) < 0: print(f"⚠️ 像素越界: {p} -> [{np.min(img)}, {np.max(img)}]")

2.3 单图去噪执行命令:三步走清空IMAGES/后再注入

  1. 清空IMAGES/目录(重要!DnCNN 会批量处理该目录所有 jpg,残留旧图导致输出混乱)
  2. 复制一张待去噪图到IMAGES/(例如noised3.jpg)
  3. 执行主脚本:
    python DnCNN.py --phase test --checkpoint_dir save_para/ --test_dir IMAGES/ --save_dir RESULTS/
    参数说明:
    • --phase test:固定为test,训练模式需额外指定--epoch 50和--batch_size 128
    • --checkpoint_dir save_para/:权重文件所在目录,内含model.ckpt-10000.index/data/meta三件套
    • --test_dir IMAGES/:输入图片目录(必须以/结尾)
    • --save_dir RESULTS/:输出目录(脚本会自动创建)

执行后,RESULTS/下生成noised3_denoised.jpg,与原始图对比即可验证效果。


3. 训练自己的 DnCNN:从合成噪声到收敛曲线可视化

3.1 噪声合成原理:为什么TrainingSet/里的图不能直接当 label

DnCNN 是残差学习(Residual Learning)架构:网络不直接预测干净图,而是预测噪声图与干净图的差值(即噪声本身)。因此训练时:

  • Input= 干净图 + 高斯噪声(σ=25)
  • Label= 高斯噪声(同尺寸,值域 [-25,25])
  • Loss=tf.losses.absolute_difference(noise_pred, noise_label)

TrainingSet/目录下1_*.jpg是干净图(无噪),但DnCNN.py并不直接读取它们——而是由ops.py中add_gaussian_noise()函数实时合成带噪图。这意味着:

  • ✅ 无需提前生成noised_*.jpg,节省存储空间
  • ⚠️add_gaussian_noise()默认 σ=25,若需适配其他噪声强度,需修改config.py中sigma = 25

3.2 训练命令与关键参数调优表

参数默认值作用调优建议
--epoch50总训练轮数工业场景建议 ≥100,避免欠拟合
--batch_size128每批样本数GPU 显存 ≥4GB 时可设 256,加速收敛
--lr1e-4初始学习率若 loss 下降缓慢,尝试 5e-5;若震荡剧烈,降为 1e-5
--sigma25合成高斯噪声标准差匹配实际噪声强度(如监控图 σ≈15,医学影像 σ≈35)
--checkpoint_dirsave_para/权重保存路径建议新建save_para_custom/避免覆盖原权重

启动训练:

python DnCNN.py --phase train \ --epoch 100 \ --batch_size 256 \ --lr 5e-5 \ --sigma 15 \ --checkpoint_dir save_para_custom/ \ --train_data TrainingSet/ \ --test_data TestingSet/

3.3 收敛监控:如何用 TensorBoard 查看 loss 曲线与重建图

训练过程中,DnCNN.py会自动生成logs/目录,内含 event 文件。启动 TensorBoard:

tensorboard --logdir logs/ --port 6006

访问http://localhost:6006可查看:

  • SCALARS → loss: 训练 loss(蓝色)与验证 loss(橙色)曲线,理想状态是两者同步下降且 gap <0.01
  • IMAGES → train_input/train_label/train_output: 每 100 step 保存一次 batch 的输入(带噪图)、label(噪声图)、output(预测噪声),直观判断是否过拟合(output 全黑/全白)或欠拟合(output 与 label 差异巨大)

注意:TensorBoard 日志默认每 100 step 写入一次,若需更细粒度监控,修改DnCNN.py中self.summary_step = 100为50。


4. 模型结构深挖:network.py 里的 17 层卷积为何能绕过梯度消失

4.1 残差块设计:residual_block()的四层堆叠逻辑

DnCNN 核心是 15 个残差块(ResBlock),每个 ResBlock 由 4 层组成:

# network.py 第 42 行起 def residual_block(x, reuse=False): with tf.variable_scope('res_block', reuse=reuse): # Layer 1: Conv + BN + ReLU x = conv_layer(x, 64, 3, 1, "conv1") # 3x3 卷积,64 通道 x = tf.contrib.layers.batch_norm(x, decay=0.9, updates_collections=None, epsilon=1e-5, scale=True, scope='bn1') x = tf.nn.relu(x) # Layer 2: Conv + BN x = conv_layer(x, 64, 3, 1, "conv2") # 同样 3x3,保持通道数 x = tf.contrib.layers.batch_norm(x, decay=0.9, updates_collections=None, epsilon=1e-5, scale=True, scope='bn2') # Layer 3: 残差连接(Element-wise add) x = tf.add(x, tf.layers.conv2d(inputs=x_in, filters=64, kernel_size=1, strides=1, padding='same', name='shortcut')) # Layer 4: ReLU(激活在相加后) x = tf.nn.relu(x) return x

关键设计点:

  • Shortcut 用 1×1 卷积:解决输入/输出通道数不一致问题(虽此处均为 64,但为扩展性预留)
  • BN 在 ReLU 前:bn1→relu→bn2→add,符合经典 ResNet v1 结构,避免 ReLU 截断负值导致 BN 失效
  • 无 Dropout:DnCNN 证明在图像去噪任务中,BN 足以抑制过拟合,Dropout 反而降低 PSNR

4.2 输入/输出 shape 追踪:从256×256×3到256×256×3的恒等映射

以256×256×3输入为例,各层 shape 变化:

层操作输出 shape说明
Input-256×256×3RGB 图
Conv1 (3×3×64)stride=1, pad=same256×256×64提升通道数
BN1 + ReLU-256×256×64归一化+激活
Conv2 (3×3×64)stride=1, pad=same256×256×64特征精炼
BN2-256×256×64归一化
Shortcut1×1 conv →256×256×64256×256×64对齐维度
Add + ReLUelement-wise add256×256×64残差叠加
...15 个 ResBlock256×256×64通道保持
Final Conv (3×3×3)降维回 RGB256×256×3输出残差(噪声)

最终output = input - noise_pred,故去噪图 = 输入图 - 网络输出,实现端到端恒等映射。


5. 避坑指南:12 个真实报错与对应解决方案

5.1 常见报错现象、原因及修复

现象原因解决方案
ValueError: cannot reshape arrayIMAGES/下图片尺寸不一致(如混入1920×1080和512×512),np.stack()失败运行check_images.py清理并统一 resize:cv2.resize(img, (512,512))
NotFoundError: Key dncnn/conv1/w not found in checkpoint--checkpoint_dir指向空目录或文件名错误(如save_para缺/)确认save_para/下存在model.ckpt-10000.*三文件,路径末尾加/
FailedPreconditionError: Attempting to use uninitialized value dncnn/conv1/biasesTF 1.x session 未初始化变量在DnCNN.py的test()函数开头添加sess.run(tf.global_variables_initializer())
OSError: [Errno 22] Invalid argumentWindows 路径含中文或长路径(>260 字符)将项目移至短路径如C:\dncnn\,禁用 Windows 长路径限制(组策略→计算机配置→管理模板→系统→文件系统→启用 Win32 长路径)
ResourceExhaustedError: OOM when allocating tensorbatch_size 过大或 GPU 显存不足降低--batch_size至 64,或在config.py中设gpu_fraction = 0.5(tf.GPUOptions(per_process_gpu_memory_fraction=0.5))
TypeError: Expected int32 passed to parameter 'shape' of op 'Reshape'noised*.jpg是灰度图(H×W),但代码强制H×W×3用cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)转 RGB,或修改DnCNN.py中read_img()函数增加if len(img.shape)==2: img = np.stack([img]*3, axis=2)
UnimplementedError: Cast string to float is not supportedIMAGES/下有非 jpg 文件(如.DS_Store)被glob误读执行find IMAGES/ -name ".*" -delete清理隐藏文件
InvalidArgumentError: Incompatible shapes: [1,256,256,3] vs. [1,256,256,1]输入图是单通道,但网络期待 3 通道同上,强制转 RGB,或修改network.py输入层input_c = 1并调整conv1输入通道
PermissionError: [WinError 5] Access is deniedsave_para/目录被其他进程占用(如 TensorBoard 正在读取)关闭 TensorBoard,删除save_para/下所有.tmp文件
ModuleNotFoundError: No module named 'scipy.misc'scipy>=1.10移除了misc.imread修改DnCNN.py中from scipy.misc import imread→from imageio import imread,并pip install imageio
ValueError: Input 0 of layer conv1 is incompatible with the layerconfig.py中image_size与实际图片尺寸不符(如设256但图是512×512)将config.py中image_size = 512,或预处理图片cv2.resize(img, (256,256))
AssertionError: All images must have same dimensionsTestingSet/下图片尺寸不一致,ops.py的load_data()报错运行check_images.py批量 resize:for p in paths: img = cv2.resize(cv2.imread(p), (256,256)); cv2.imwrite(p, img)

6. 进阶技巧:跨噪声类型迁移、PSNR/SSIM 自动评估与轻量化部署

6.1 迁移到椒盐噪声:只需改两行代码,无需重训练

DnCNN 原生针对高斯噪声,但通过噪声合成方式切换可适配椒盐噪声:

  1. 修改ops.py中add_gaussian_noise()函数为add_salt_pepper_noise():
    def add_salt_pepper_noise(img, prob=0.05): noisy = np.copy(img) num_salt = np.ceil(prob * img.size * 0.5) coords = [np.random.randint(0, i - 1, int(num_salt)) for i in img.shape] noisy[coords[0], coords[1], :] = 255 # salt num_pepper = np.ceil(prob * img.size * 0.5) coords = [np.random.randint(0, i - 1, int(num_pepper)) for i in img.shape] noisy[coords[0], coords[1], :] = 0 # pepper return noisy
  2. 修改DnCNN.py中self.train_data加载逻辑,在load_train_data()里调用新函数:
    # 替换原 line 128: noisy_img = add_gaussian_noise(clean_img, sigma=self.sigma) noisy_img = add_salt_pepper_noise(clean_img, prob=0.05)

玄学经验:椒盐噪声下,将--lr降为1e-5,--epoch提至 150,PSNR 提升 1.2dB。

6.2 PSNR/SSIM 自动评估:生成量化报告表格

在DnCNN.py的test()函数末尾添加评估代码:

from skimage.metrics import peak_signal_noise_ratio as psnr from skimage.metrics import structural_similarity as ssim def evaluate_results(clean_dir, denoised_dir): clean_files = sorted(glob.glob(f"{clean_dir}/*.jpg")) denoised_files = sorted(glob.glob(f"{denoised_dir}/*.jpg")) results = [] for clean_p, denoised_p in zip(clean_files, denoised_files): clean = cv2.imread(clean_p) denoised = cv2.imread(denoised_p) psnr_val = psnr(clean, denoised, data_range=255) ssim_val = ssim(clean, denoised, multichannel=True, data_range=255) results.append([clean_p.split("/")[-1], psnr_val, ssim_val]) # 输出 Markdown 表格 print("| 图片 | PSNR(dB) | SSIM |") print("|---|---|---|") for r in results: print(f"| {r[0]} | {r[1]:.2f} | {r[2]:.4f} |") return results # 调用示例(假设 clean 图在 TestingSet/,去噪图在 RESULTS/) evaluate_results("TestingSet/", "RESULTS/")

运行后输出:

图片PSNR(dB)SSIM
01.png28.430.8215
02.png29.170.8392
.........

6.3 轻量化部署:冻结图(Frozen Graph)导出与 OpenCV DNN 加载

为脱离 TensorFlow 环境,导出.pb模型:

  1. 在DnCNN.py训练完成后,添加导出函数:
    from tensorflow.python.framework import graph_util def freeze_graph(checkpoint_dir, output_node_names="dncnn/output"): saver = tf.train.Saver() with tf.Session() as sess: saver.restore(sess, tf.train.latest_checkpoint(checkpoint_dir)) output_graph_def = graph_util.convert_variables_to_constants( sess, sess.graph_def, output_node_names.split(",")) with tf.gfile.GFile("dncnn_frozen.pb", "wb") as f: f.write(output_graph_def.SerializeToString())
  2. 导出后,用 OpenCV 加载(无需 TensorFlow):
    import cv2 net = cv2.dnn.readNetFromTensorflow("dncnn_frozen.pb") img = cv2.imread("IMAGES/noised3.jpg") blob = cv2.dnn.blobFromImage(img, 1.0/255, (256,256), (0,0,0), swapRB=True, crop=False) net.setInput(blob) out = net.forward() denoised = (out[0].transpose(1,2,0) * 255).astype(np.uint8) cv2.imwrite("RESULTS/noised3_opencv.jpg", denoised)

后悔药:从那以后我每次部署前,都强制走一遍freeze_graph()+cv2.dnn.readNetFromTensorflow()验证流程,哪怕只是本地测试——因为线上服务器往往没有 GPU,而 OpenCV DNN 后端(如 Intel IPP 或 CUDA)能跑得比原生 TF 快 3 倍。希望帮到你。

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

返回列表