基于MATLAB的深度卷积神经网络图像去噪:残差学习与PSNR验证
简介面向图像处理与深度学习研究的开发者提供一套基于深度卷积神经网络DCNNs的图像去噪实现方案。方案以MATLAB环境下构建DCNN模型的思路为背景压缩包内以Python脚本为主要实现工具可直接运行训练、测试与数据生成流程并配有研究文档和说明便于理解网络结构搭建与残差学习的核心环节。包体仅20KB共8个文件包括6个Python脚本覆盖训练、测试、数据生成及多图像批量测试、1个研究文档压缩包和1份README说明文件整体小巧轻量便于下载和本地实验。已有166人浏览学习适合具备基础神经网络知识、希望通过实际代码理解图像去噪模型的读者。获取后可快速复用完整的训练与测试流程也可将脚本逻辑迁移至自身去噪项目中节省从零搭建代码的时间。1. 基于 MATLAB 的深度卷积神经网络图像去噪从数据生成到 PSNR 验证用手机拍夜景或者用工业相机在弱光下取像得到的图放大后往往布满了细小的颗粒。这些噪声不是随机散点它和传感器增益、温度、暗电流都有关系简单中值滤波或双边滤波在抑制噪声的同时会把头发丝、裂纹、织物纹理一起抹掉。深度卷积神经网络能够通过残差学习把“噪声成分”单独分离出来再用“带噪图减残差图”的方式重建干净图像。基于 MATLAB 实现的这套 DCNN 去噪方案把数据生成、网络训练、多图测试都收拢在同一套脚本里方便在 MATLAB 环境中直接改网络结构、调超参数并查看训练曲线。适合做图像预处理、算法验证以及工程交付前快速落地的工程师参考。2. 深度卷积神经网络去噪的残差学习原理与 MATLAB 网络定义2.1 残差学习为什么比直接回归干净图像更容易收敛图像去噪网络通常做成输入带噪图、输出干净图但这会逼着网络从零开始生成整幅图像的结构信息包括边缘、亮度、阴影。真实场景中去噪希望保留原始结构只去掉噪声因此更适合让网络学习带噪图和干净图之间的差异也就是残差图。设带噪图像为 y对应的干净图像为 x噪声残差为 r y - x网络的任务是拟合 r。训练时使用均方误差损失目标函数为1/NΣ || f(y_i; θ) - (y_i - x_i) ||²在 MATLAB 中这个损失函数可以直接用regressionLayer表示。残差图比完整图像稀疏得多很多区域接近零网络不必为每个像素重建亮度所以收敛速度更快、波动更小。Deep Learning Toolbox 会自动求梯度训练时只需要把 4 维数组和层结构准备好。相比从 Python/Caffe 环境转过来的方案MATLAB 的优势在于每个中间变量在工作区里都可视遇到维度不匹配或 NaN 损失时能当场查矩阵形状调试成本低很多。2.2 一个可复用的 MATLAB DnCNN 网络结构DnCNN 是深度卷积神经网络去噪里最常被复用的结构核心是“卷积 批量归一化 ReLU”的串联。下面是一段适合在 MATLAB 中直接跑的层定义输入为 50×50 的灰度图像块layers [ imageInputLayer([50 50 1], Normalization, none) convolution2dLayer(3, 64, Padding, same, WeightsInitializer, he) reluLayer convolution2dLayer(3, 64, Padding, same, WeightsInitializer, he) batchNormalizationLayer reluLayer convolution2dLayer(3, 64, Padding, same, WeightsInitializer, he) batchNormalizationLayer reluLayer convolution2dLayer(3, 1, Padding, same) regressionLayer ];上面的层结构用三层卷积作示意完整版通常会把“卷积 批归一化 ReLU”重复 1520 次。imageInputLayer设置输入尺寸和通道数这里Normalization设置为none因为图像已在预处理阶段做过归一化避免 MATLAB 默认再减均值。convolution2dLayer的第一个参数是滤波器大小第二个是输出通道数Padding取same可以保证特征图尺寸不变这样残差图与输入图能直接做矩阵相减。WeightsInitializer使用 He 初始化适配 ReLU 激活函数的方差分布避免深层网络激活值消失。下表列出网络各层的角色方便在改网络时对照层类型作用常见设置imageInputLayer接收带噪图像块[patchSize patchSize 1]convolution2dLayer提取局部边缘和纹理特征FilterSize3, NumFilters64batchNormalizationLayer减轻内部协变量偏移训练时自动更新均值和方差reluLayer引入非线性无训练参数regressionLayer输出残差图并计算 MSE输入为网络输出标签为 y-x2.3 标签格式与残差计算在 MATLAB 中训练数据要组织成 H×W×C×N 的格式H 和 W 是图像块边长C 是通道数N 是样本数。如果采用残差学习标签不是干净图 x而是 r y - x。也就是说网络输入是带噪图像块输出目标是对应的噪声图。推理阶段拿到网络预测的残差后再用denoised noisy - predictedResidual恢复干净图。这里有一个常被忽略的细节图像必须是 single 或 double 类型uint8 做减法时会因为无符号整数截断出现大面积黑色区域。我一般会在数据生成阶段先用im2double把图像转成 [0,1] 浮点再生成噪声和残差。网络回归层的输出没有激活函数限制所以预测残差可以出现负值这正是恢复过程需要的。3. MATLAB 训练图像去噪网络噪声数据生成、批处理与超参调优3.1 用 imnoise 和 randn 生成训练数据资源中的generateData.py负责把大图切成小块并叠加噪声MATLAB 端完全可以用同样逻辑生成训练数据。下面是一段常见做法直接把干净图和带噪图切成 50×50 的 patchcleanImage im2double(imread(train_clean.png)); sigma 25 / 255; noisyImage cleanImage sigma * randn(size(cleanImage)); patchSize 50; step 25; [cropsClean, cropsNoisy] deal({}); k 1; for i 1:step:size(cleanImage, 1) - patchSize 1 for j 1:step:size(cleanImage, 2) - patchSize 1 cleanPatch cleanImage(i:ipatchSize-1, j:jpatchSize-1, :); noisyPatch noisyImage(i:ipatchSize-1, j:jpatchSize-1, :); cropsClean{k} cleanPatch; cropsNoisy{k} noisyPatch; k k 1; end endrandn产生标准正态分布随机数乘sigma就是指定标准差的高斯噪声。也可以用imnoise(cleanImage, gaussian, 0, sigma^2)但imnoise内部对 uint8 做了量化生成的数据更接近真实传感器而randn方式更简洁且与generateData.py里常见的 numpy 写法一致。步长step控制在 1/2 patchSize 左右既能扩充样本数又不至于让相邻 patch 高度重叠。如果需要完全复现资源中的数据就把这些 patch 存成.mat文件训练时用load读入。3.2 trainNetwork 与残差标签训练数据准备好后标签是残差 R X - C。这里 X 是带噪块C 是干净块。训练代码可以写成load(denoising_dataset.mat); % 包含 noisyPatches, cleanPatches X single(noisyPatches); C single(cleanPatches); R X - C; % 残差标签 layers [ ... ]; % 2.2 中定义的层结构 options trainingOptions(adam, ... InitialLearnRate, 5e-4, ... MiniBatchSize, 128, ... MaxEpochs, 30, ... Shuffle, every-epoch, ... L2Regularization, 1e-4, ... Plots, training-progress, ... VerboseFrequency, 20); net trainNetwork(X, R, layers, options);上述代码先把带噪图和干净图都转成single然后计算残差并作为trainNetwork的目标。trainNetwork的第二个参数可以是数值数组但要求与网络输出尺寸一致。50×50 的 patch 配合 batch size 128显存占用约 3~4 GB如果显存不足把 patch 改成 40×40或把MiniBatchSize降到 64。InitialLearnRate设成 5e-4 而不是常见的 1e-3因为回归任务对权重更新幅度比分类更敏感学习率过大容易导致 loss 直接发散。Shuffle设为every-epoch可以在每个 epoch 重新打乱数据顺序提高残差学习的泛化性。训练选项的调整可以看成这样一个过程超参数建议值出现问题的信号InitialLearnRate1e-4 ~ 5e-4loss 前几步变成 NaN说明学习率过高MiniBatchSize64 ~ 128显存溢出时减少MaxEpochs30 ~ 80验证集 PSNR 不再上升时停止L2Regularization1e-4 ~ 1e-3权重过大时适当增大如果需要每训练几个 epoch 就保存一份临时模型可以在trainingOptions中加入CheckpointPath, ./checkpoints和CheckpointFrequency, 5。这样训练中途崩溃也不必从头再来。3.3 训练曲线异常时先查数据再查结构用 MATLAB 训练去噪网络时最常见的问题是 loss 在第一个 epoch 后不降反升。我会先检查R矩阵里是否存在 NaN随后再确认输入X和标签R是否一一对应。另一个很容易踩的坑是忘记把输入转成single导致后续trainNetwork报数据类型错误。如果 loss 一开始就特别小比如小于 1e-5很可能是把X本身当成了标签模型学到一个恒等映射最终测试时去噪图几乎等于带噪图。资源里的cnn_denoising_train.py会把 loss 输出到终端MATLAB 端则直接看training-progress窗口里的平滑曲线。4. PSNR/SSIM 测试与多图推理MATLAB 去噪模型的验证细节4.1 推理流程与结果越界处理测试阶段先用predict得到残差再用带噪图减去残差。下面这段代码可以直接放到测试脚本里load(trained_net.mat, net); noisy im2double(imread(test_noisy.png)); noisy single(noisy); residual predict(net, noisy); denoised noisy - residual; denoised min(max(denoised, 0), 1); imwrite(im2uint8(denoised), denoised.png);predict对单张 H×W×C 图像会返回同样尺寸的残差图。由于网络最后一层没有 Sigmoid 或 Tanhresidual中的值可能超过 [0,1]所以相减后的denoised也需要做截断。常见做法是先min(max(...))再转回 uint8。如果图像尺寸不是 8 的倍数卷积层不受影响但后续如果要加池化或反卷积就必须对图像做镜像填充否则输出尺寸会对不上数据标签。4.2 计算 PSNR 和 SSIM 的两种方式MATLAB 自带psnr和ssim函数直接调用就行clean im2double(imread(test_clean.png)); psnrVal psnr(denoised, clean); ssimVal ssim(denoised, clean); fprintf(PSNR: %.2f dB, SSIM: %.4f\n, psnrVal, ssimVal);这里psnr默认峰值取 1因此输入必须是 [0,1] 的浮点图如果传 uint8峰值会变成 255同样的噪声会得到完全不同的数值。为了核对结果也可以手写mseVal mean((denoised(:) - clean(:)).^2); manualPsnr 10 * log10(1 / mseVal);手写 PSNR 可以帮助理解算法本质PSNR 本质是最大信号平方与均方误差的比值单位是分贝。SSIM 的计算窗口默认是 11×11 高斯窗对边缘和纹理的损失更敏感。去噪模型如果只追求 PSNR往往会输出过度平滑的图像SSIM 能反映结构保真度所以两者要一起看。资源里的test_muli_image.py批量测试脚本在 MATLAB 端等价于循环读取测试集并逐张计算这两个指标。4.3 多图测试的内存管理与尺寸对齐多图推理时不要一次性把所有测试图都堆成 H×W×C×N 的数组内存容易被撑爆。更稳妥的方式是用imageDatastore配合循环imds imageDatastore(test_images, ... ReadFcn, (filename) single(im2double(imread(filename)))); numImages numel(imds.Files); psnrList zeros(numImages, 1); ssimList zeros(numImages, 1); for i 1:numImages noisy read(imds); residual predict(net, noisy); denoised noisy - residual; psnrList(i) psnr(denoised, cleanImages{i}); ssimList(i) ssim(denoised, cleanImages{i}); endReadFcn会在读取的同时完成类型转换避免imread返回 uint8 后忘记转浮点。这个循环里每个 batch 只占一张图的内存即使测试集很大也能跑完。需要注意的是imageDatastore默认按字母顺序读取文件所以测试结果数组的顺序要和干净图列表保持一致。如果测试图尺寸不同可以用imresize统一大小但缩放会引入额外失真PSNR 会偏低。更好方案是按 patch 推理逐块处理后再拼接资源中的多图测试脚本通常就是按 patch 方式组织的。5. 从固定噪声水平到噪声自适应输入通道扩展的 MATLAB 实现5.1 固定 sigma 模型的局限前面训练的 DnCNN 模型只在某个固定噪声水平比如 sigma25/255下表现好。一旦测试图的噪声标准差翻倍输入分布和训练分布出现偏移PSNR 会立刻下降几个 dB。工业场景中相机增益会随环境光变化噪声水平不是恒定值这就需要在网络输入里显式告诉模型“当前噪声有多大”。5.2 在输入层拼接噪声水平图一种轻量级做法是给网络增加一个输入通道内容是整张图统一的 sigma 值。具体来说把原始带噪图从单通道变成双通道sigma 25 / 255; noiseMap sigma * ones(size(noisy), single); input2Channel cat(3, noisy, noiseMap);cat(3, ...)在第三维拼接noiseMap里每个像素都等于 sigma。这样网络能看到噪声强度的全局信息之前的imageInputLayer([50 50 1])要改成imageInputLayer([50 50 2])。第一层卷积的输入通道数会自动变成 2其余结构不用变。这个思路和噪声自适应去噪方法LAN 等在输入信息组织上是相同的只是 LAN 还额外估计每个像素位置的噪声水平图而这里直接用已知 sigma 填充。训练时不要只用固定 sigma。常见做法是每个 batch 随机抽取一个 sigma 值sigma 0.01 0.2 * rand; % 范围 0.01~0.21 noisy cleanPatch sigma * randn(size(cleanPatch), single); noiseMap sigma * ones(size(cleanPatch), single); inputBatch cat(3, noisy, noiseMap);5.3 验证噪声自适应模型测试阶段需要临时生成噪声水平图再传给predict。如果没有真值 sigma可以先用小波或中值绝对偏差估计一个全局值再填入noiseMap。如果噪声是非均匀的比如模拟传感器渐晕带来的中心清晰、边缘模糊就把noiseMap换成一个与图像同尺寸的二维矩阵并在拼接前用imresize将噪声图缩放到与输入图像一致。这样网络不仅知道整体噪声水平还能感知噪声分布的空间变化。验证时分别用 sigma15、25、50 的测试集评估观察 PSNR 下降幅度。相比固定 sigma 模型输入通道扩展后的模型在低噪声段可能略差但在高噪声段的鲁棒性明显更好。本文还有配套的精品资源点击获取