MATLAB图像去噪实战:传统算法与DnCNN深度卷积神经网络对比
简介这份资源面向高校学生与图像处理初学者提供基于MATLAB的传统图像去噪算法与深度卷积神经网络DnCNN去噪算法的完整实现可用于毕业设计、期末大作业与课程设计等场景难度适中兼顾经典方法与深度学习方案。压缩包共177个文件约80.33MB以m脚本、mat数据、png图像、mexw64与mexw32等平台编译文件为主另含少量txt与md说明文档覆盖算法调用、数据加载与跨平台运行需求。内容预览可见BM3D、CBM3D、VBM3D等传统算法脚本及DnCNN演示程序便于对比不同去噪思路。源码经本地编译验证可运行并附文档说明已有165人学习下载。读者可据此掌握从传统滤波到卷积网络去噪的完整流程理解网络结构、训练与测试环节并借助现成脚本快速复现实验、修改参数为论文撰写与项目答辩提供可参考的实现基础。1. 从均值滤波到 DnCNN一条 MATLAB 图像去噪的落地路线手里有一批带噪图像领导要你两天内给出「传统算法 vs 深度学习」的对比结果还要能复现——这是我被问过最多的一类需求。传统图像去噪在 MATLAB 里几行代码就能跑均值滤波、中值滤波、高斯滤波、小波变换图像去噪各有适用场景但噪声一强、纹理一细传统方法就开始糊边、丢细节。DnCNNDenoising Convolutional Neural Network把残差学习和批归一化引入去噪用「预测噪声」而不是「预测干净图」的思路在合成高斯噪声上把 PSNR 拉高了好几个 dB。这篇笔记就围绕「基于 MATLAB 实现传统图像去噪算法 基于深度卷积神经网络的 DnCNN 图像去噪算法」这条线把数据准备、传统基线、DnCNN 搭建训练、推理对比、踩坑排查一次讲透。适合正在做 matlab 图像处理大作业、想把深度学习卷积神经网络落到去噪任务上的同学和工程师源码和文档说明的思路也会一并交代清楚。2. 传统去噪基线先把对照组跑出来2.1 为什么一定要先做传统基线很多人一上来就搭 DnCNN结果训练完发现 PSNR 只比原图高一点点回头才发现连对照组都没有根本说不清提升来自哪里。传统去噪算法的价值不只是「陪跑」它决定了你后面所有结论的可信度。常见做法是选三类代表空域线性滤波均值、高斯、空域非线性滤波中值、变换域方法小波变换图像去噪。均值和高斯适合高斯噪声中值对椒盐噪声几乎是标配小波在保留边缘上比前两者好但阈值选不好会出现振铃。在 MATLAB 里这几类都有现成函数但要注意图像类型。读进来的图如果是 uint8滤波前最好转 double 并归一化到 [0,1]否则卷积和阈值计算会出现截断误差。下面这段是我常用的基线脚本一次把四种方法跑完并输出 PSNR。% baseline_denoise.m % 传统去噪基线均值/中值/高斯/小波统一评估 PSNR img imread(lena.png); if size(img,3) 3 img rgb2gray(img); % 去噪一般先转灰度避免通道处理复杂化 end img im2double(img); % 归一化到 [0,1]避免 uint8 截断 sigma 25; % 噪声标准差按 255 尺度计 noisy img sigma/255 * randn(size(img)); noisy min(max(noisy,0),1); % 截断回合法范围 % 1) 均值滤波 h_mean fspecial(average, 3); d_mean imfilter(noisy, h_mean, replicate); % 2) 中值滤波 d_med medfilt2(noisy, [3 3]); % 3) 高斯滤波 h_gauss fspecial(gaussian, 5, 1.0); d_gauss imfilter(noisy, h_gauss, replicate); % 4) 小波变换去噪 [c, s] wavedec2(noisy, 2, sym4); thr sigma/255 * sqrt(2*log(numel(noisy))); % 通用阈值 c_thr wthcoef2(h, c, s, 1:2, thr, s); % 软阈值 d_wave waverec2(c_thr, s, sym4); fprintf(Noisy PSNR %.2f dB\n, psnr(noisy, img)); fprintf(Mean PSNR %.2f dB\n, psnr(d_mean, img)); fprintf(Median PSNR %.2f dB\n, psnr(d_med, img)); fprintf(Gauss PSNR %.2f dB\n, psnr(d_gauss, img)); fprintf(Wavelet PSNR %.2f dB\n, psnr(d_wave, img));逻辑上这段脚本先统一数据类型再对同一张噪声图分别施加四种滤波最后用psnr统一打分。参数上fspecial(average,3)的 3 是窗口大小窗口越大越平滑但越糊fspecial(gaussian,5,1.0)里 5 是模板尺寸、1.0 是标准差标准差越大越模糊小波部分wavedec2的 2 是分解层数sym4是常用小波基阈值用经典的通用阈值公式。跑完你会看到sigma25 时均值滤波大概 28 dB 上下小波能到 30 dB 左右但边缘已经有肉眼可见的损失——这就是 DnCNN 要补的差距。2.2 评估指标别只盯 PSNRPSNR 是像素级指标和主观感受经常不一致。我一般同时算 SSIM它更贴近人眼对结构信息的敏感度。MATLAB 里ssim函数直接可用但要注意它默认对 [0,1] 的 double 图计算如果你的图还是 uint8结果会偏。另一个坑是边界imfilter默认零填充边缘会出现暗边用replicate或symmetric能缓解。做对比实验时所有方法必须用同一张噪声图、同一个随机种子否则 PSNR 波动能到 0.5 dB结论就站不住。提示噪声生成用randn前先rng(0)固定种子保证传统方法和 DnCNN 面对的是完全相同的输入。3. DnCNN 网络搭建残差学习为什么适合去噪3.1 预测噪声而不是预测干净图DnCNN 的核心思想很朴素让网络输出噪声残差干净图 噪声图 − 网络输出。相比直接回归干净图这个设计有两个好处。第一噪声分布通常比图像内容简单网络更容易学第二残差连接让梯度直接回传深层网络也能训得动。原论文用了 17 层卷积每层 64 个 3×3 卷积核除第一层和最后一层外都接 BatchNorm 和 ReLU。第一层只做卷积ReLU最后一层只做卷积输出单通道残差。在 MATLAB 里搭这个网络可以用 Deep Learning Toolbox 的dlnetwork配合自定义训练循环也可以用layerGraph加trainNetwork。我倾向dlnetwork因为去噪任务常需要自定义损失和逐像素评估灵活度高。下面给出网络定义。% build_dncnn.m % 构建 17 层 DnCNN输入单通道噪声图输出噪声残差 function net build_dncnn(depth, numFilters) if nargin 1, depth 17; end if nargin 2, numFilters 64; end layers [ imageInputLayer([50 50 1], Name, input, Normalization, none) convolution2dLayer(3, numFilters, Padding, same, Name, conv1) reluLayer(Name, relu1) ]; for i 2:depth-1 layers [layers convolution2dLayer(3, numFilters, Padding, same, ... Name, sprintf(conv%d, i)) batchNormalizationLayer(Name, sprintf(bn%d, i)) reluLayer(Name, sprintf(relu%d, i)) ]; end layers [layers convolution2dLayer(3, 1, Padding, same, Name, convOut) regressionLayer(Name, output) ]; net layerGraph(layers); end逻辑说明输入固定 50×50 单通道是为了训练时能整批塞进显存Padding,same保证每层输出尺寸不变这样残差才能和输入逐像素相减。参数上depth控制层数17 是原论文配置层数越多感受野越大但训练越慢numFilters是每层通道数64 是精度和显存的平衡点显存紧张可以降到 32PSNR 大概掉 0.20.3 dB。Normalization,none很重要因为我们要自己控制输入范围让工具箱再归一化会打乱噪声尺度。3.2 训练数据怎么造才不翻车DnCNN 是监督学习需要「干净图-噪声图」成对数据。常见做法是拿一批干净图在线加高斯噪声每个 batch 重新采样噪声这样等于无限扩充数据。数据集可以用 BSD400、Set12 这类公开图也可以用自己的图。关键点是训练噪声水平要和测试一致。如果你训练只用 sigma25测试却用 sigma50PSNR 会崩。原论文的做法是训练一个盲去噪模型噪声水平在 [0,55] 随机采样这样一个模型通吃多个噪声强度。% make_patch.m % 从干净图随机裁 50x50 patch并在线加噪 function [noisyPatch, cleanPatch] make_patch(img, patchSize, sigmaRange) [H, W] size(img); r randi(H - patchSize 1); c randi(W - patchSize 1); cleanPatch img(r:rpatchSize-1, c:cpatchSize-1); sigma sigmaRange(1) diff(sigmaRange) * rand(); % 随机噪声水平 noise sigma/255 * randn(patchSize, patchSize); noisyPatch min(max(cleanPatch noise, 0), 1); end逻辑上每次调用随机裁一块并随机抽一个 sigma实现盲去噪训练。参数上patchSize取 50 是原论文设置太小感受野不够太大 batch 装不下sigmaRange一般设 [0,55]覆盖常见噪声强度。注意加噪后要截断到 [0,1]否则训练目标里会出现非法像素值网络学出来的残差会有偏。3.3 自定义训练循环与损失函数用dlnetwork训练时损失用 MSE 即可但要注意网络输出的是残差所以损失是「预测残差」和「真实噪声」之间的 MSE不是和干净图的 MSE。这一点新手极易搞反导致训练不收敛。% train_dncnn.m % 自定义训练循环损失为预测残差与真实噪声的 MSE numEpochs 30; miniBatch 32; learnRate 1e-3; avgGrad []; avgSqGrad []; for epoch 1:numEpochs shuffleIdx randperm(numel(trainImgs)); for i 1:miniBatch:numel(shuffleIdx) idx shuffleIdx(i:min(iminiBatch-1, numel(shuffleIdx))); X dlarray(zeros(50,50,1,numel(idx)), SSCB); T dlarray(zeros(50,50,1,numel(idx)), SSCB); for k 1:numel(idx) [np, cp] make_patch(trainImgs{idx(k)}, 50, [0 55]); X(:,:,1,k) np; T(:,:,1,k) np - cp; % 目标 噪声残差 end [loss, grads] dlfeval(modelLoss, net, X, T); [net, avgGrad, avgSqGrad] adamupdate(net, grads, ... avgGrad, avgSqGrad, epoch, learnRate); end fprintf(Epoch %d, loss %.5f\n, epoch, double(loss)); end function [loss, grads] modelLoss(net, X, T) Y forward(net, X); loss mse(Y, T); grads dlgradient(loss, net.Learnables); end逻辑说明每个 batch 现场造数据X是噪声图T是噪声残差。adamupdate用 Adam 优化器学习率 1e-3 是常用起点。参数上miniBatch取 32 受显存限制显存小就降到 16numEpochs30 是经验值配合在线加噪一般够收敛loss 降到 0.001 量级就差不多了。如果 loss 震荡先把学习率降到 5e-4。4. 推理与对比把 DnCNN 和传统方法放同一张表里4.1 全卷积推理与边界处理训练用 50×50 patch推理时整图尺寸任意因为网络是全卷积的。但要注意卷积的same填充在图像边界会引入伪影常见做法是推理时对边界做镜像填充或者干脆裁掉边缘若干像素再算 PSNR。我一般用padarray镜像填充 8 像素推理完再裁回来。% infer_dncnn.m % 整图推理镜像填充避免边界伪影 pad 8; noisyPad padarray(noisy, [pad pad], symmetric); dlX dlarray(reshape(noisyPad, [size(noisyPad) 1 1]), SSCB); residual extractdata(forward(net, dlX)); residual residual(:,:,1,1); denoised noisyPad - residual; denoised denoised(pad1:end-pad, pad1:end-pad); denoised min(max(denoised, 0), 1); fprintf(DnCNN PSNR %.2f dB\n, psnr(denoised, img));逻辑上先镜像填充再前向再减去残差最后裁回原尺寸并截断。参数上pad8对应网络有效感受野的一半左右17 层 3×3 卷积的感受野是 35取 8 到 17 都行越大越稳但越慢。这一步不做的话边界一圈 PSNR 会明显偏低整图指标被拖累。4.2 一张对比表看清差距把前面所有方法的结果汇总sigma25 时典型数值如下具体数值随图和种子浮动看趋势即可。方法PSNR (dB)SSIM边缘保持推理耗时噪声图20.20.42——均值滤波28.50.78差1 ms中值滤波27.10.74中1 ms高斯滤波28.90.80差1 ms小波去噪30.30.85中10 msDnCNN32.60.90好50 ms从表里能看出DnCNN 在 PSNR 和 SSIM 上都领先代价是推理慢一个量级。如果你的场景是批量离线处理这点耗时无所谓如果是实时视频流就得考虑模型压缩或换轻量结构。这也是选型时要先问清楚的地方精度优先还是速度优先。注意上表数值是单张图的典型值做正式报告时至少跑 10 张图取平均否则容易被单张图的偶然性误导。5. 避坑与排查DnCNN 训练中最容易翻车的 5 个点5.1 现象loss 一直不降PSNR 和噪声图差不多原因最常见的是损失函数写反了把网络输出直接和干净图算 MSE而网络学的却是残差目标和输出对不上。另一个可能是输入没归一化uint8 直接喂进去数值范围 0255梯度爆炸。解决确认T np - cp损失是mse(Y, T)。输入统一im2double到 [0,1]。如果还不行把学习率降到 1e-4 观察前几个 epoch 的 loss 是否下降。5.2 现象训练集 PSNR 很高测试集一塌糊涂原因过拟合。训练图太少或者训练和测试的噪声水平不一致。DnCNN 参数量不小几百张图很容易记住。解决增加训练图数量在线加噪本身就是一种正则训练时 sigma 范围设宽一点做盲去噪加 Dropout 或权重衰减。我一般训练集至少准备 400 张图。5.3 现象推理结果边缘一圈明显发暗或有条纹原因卷积边界填充方式导致same填充在边界补零残差预测在边界失准。解决推理时镜像填充如 4.1 节所示。或者训练时也用镜像填充的 patch让网络见过边界情况。5.4 现象BatchNorm 导致小 batch 训练不稳定原因BatchNorm 在 batch 很小时统计量不准训练和推理行为不一致。解决把 miniBatch 提到 16 以上或者把 BatchNorm 换成 GroupNorm 之类对小 batch 友好的归一化。显存实在不够就减网络通道数而不是减 batch。5.5 现象PSNR 算出来比预期高很多怀疑有 bug原因psnr函数对 uint8 和 double 的处理不同如果一张图是 uint8、另一张是 double结果会虚高。另外如果去噪图被截断到 [0,1] 之外再算也会异常。解决算指标前统一im2double并确认两张图尺寸、类型完全一致。养成先whos看一眼变量类型的习惯这个血泪经验能省很多调试时间。6. 进阶技巧用噪声水平估计做自适应去噪前面训练的是盲去噪模型但如果你知道测试图的噪声水平可以做得更好。一个实用技巧是先估计噪声 sigma再把它作为条件输入网络或者直接选对应噪声水平训练的单模型。噪声估计可以用中值绝对偏差MAD对图像做小波分解取最细尺度对角细节系数的 MAD 除以 0.6745就是 sigma 的稳健估计。% estimate_sigma.m % 用最细尺度对角细节系数的 MAD 估计高斯噪声标准差 function sigma estimate_sigma(noisy) [~, c, ~] wavedec2(noisy, 1, db1); % wavedec2 输出结构里细节系数按 H/V/D 排列取对角分量 [~, s] wavedec2(noisy, 1, db1); d detcoef2(d, c, s, 1); sigma median(abs(d(:))) / 0.6745; end逻辑上干净图的小波细节系数稀疏MAD 主要反映噪声除以 0.6745 是把 MAD 换算成高斯标准差。参数上db1是最短小波适合做噪声估计层数取 1 就够。估出 sigma 后你可以一是把它作为额外通道拼到输入上训练条件 DnCNN二是从多个按固定 sigma 训练的模型里挑最接近的那个。前者精度更高但要多改网络后者实现简单适合快速验证。另一个值得做的验证是跨噪声水平测试。把同一个盲去噪模型在 sigma15、25、50 上分别测 PSNR画一条曲线。如果 sigma50 时掉得特别厉害说明训练时 sigma 范围没覆盖够把范围上限提到 55 甚至 70 再训一轮。我自己的习惯是每次训完先跑这条曲线比单点 PSNR 更能说明模型的泛化边界。最后说个工程上的取舍DnCNN 在 MATLAB 里训练一次400 张图、30 epoch、单卡大概几小时推理单张 50 ms 左右。如果你的项目只是做课程设计或对比实验这套流程完全够用如果要上生产得考虑模型量化和代码生成。我踩过最大的坑是拿训练集里的图当测试图PSNR 虚高 3 dB被评审一眼看穿——所以数据划分一定要在造 patch 之前就做好别偷懒。希望帮到你。本文还有配套的精品资源点击获取