简介基于MATLAB平台的深度卷积神经网络去噪对比代码包完整实现了DnCNN模型与均值滤波、中值滤波、非局部均值NLM、三维块匹配BM3D四种传统算法的图像去噪实验适合图像处理、计算机视觉方向的学生与研究者用于算法复现与对比评估。项目以Set12数据集为测试对象覆盖噪声强度从10到70共13个等级采用峰值信噪比与结构相似性作为评价指标其中BM3D还提供了彩色图像和视频去噪的扩展版本方便与深度学习模型做多场景比较。压缩包共177个文件约80.31MB核心内容包含m脚本、mat数据、png测试图以及面向Windows、Linux、Mac的多平台mex编译文件可解压后直接运行避免了繁琐的编译配置。已有3759人下载学习代码注释清晰、目录组织合理支持修改数据集路径与噪声参数也附带了txt和md说明文档无论是用作课程设计、毕业设计还是作为科研入门的基础代码都具有很高的参考价值。资源面向系统开源爱好者既可直接体验去噪流程也可进一步扩展算法或数据集开展新实验。1. 一张中等噪声的图传统滤波和卷积神经网络各能救回多少图像去噪是个永远有活干的方向。拿到一套 matlab 卷积神经网络去噪代码之前我先说结论传统算法不是过渡品是标尺。均值滤波、中值滤波、非局部均值滤波NLM实现简单、输出稳定最适合当基线卷积神经网络能学出比固定滤波器更聪明的映射但对训练数据、硬件和时间都有要求。标题里那个“三”字看起来是截断了按从业者习惯我理解成 NLM 之后最值得对标的 BM3D三维块匹配滤波。整篇围绕一个目标让读者能在 MATLAB 里把这两类方法都跑通、比出高低并在一张对比表里知道自己代码处在什么水平。适合正在做图像预处理、想验证“深度学习到底值不值得上”的从业者。2. 传统滤波先跑通均值与中值在 MATLAB 里的实现和边界2.1 fspecial 与 imfilter均值滤波的窗口、边界与速度均值滤波是最容易写错的一段代码因为它看起来只有一行实际上有三个必须一起定的东西卷积核、边界策略和输出类型。先跑一个最小例子用 MATLAB 自带的合成影像 phantom 做实验对象它没有版权和人脸争议适合反复测试。clean phantom(256); % 合成幻影图256x256double 类型 noisy imnoise(clean, gaussian, 0, 0.01); % 加高斯噪声方差 0.01 h fspecial(average, 5); % 5x5 均值卷积核 denoised_mean imfilter(noisy, h, replicate); % replicate 边界避免黑边 p_mean psnr(denoised_mean, clean); % 和干净图比 PSNRfspecial(average, k) 生成一个 k×k、所有元素为 1/k² 的归一化核。k 越大图像越平滑细节损失越多k3 时基本只压掉了高频抖动k11 时边缘已经糊成一片。imfilter 第三个参数是边界策略默认补零会让四边出现一条明显的暗框我在本地第一次跑就翻过这个车改用 replicate 后黑边立刻消失。输出类型上imfilter 对 double 输入返回 double但很多代码习惯把 uint8 直接丢进去得到的还是 uint8 数组后续和别人结果做对比时类型不一致PSNR 很容易虚高或虚低。均值滤波的速度不用太担心imfilter 在普通 PC 上处理 1080p 灰度图是毫秒级瓶颈从来不在它身上。2.2 medfilt2中值滤波的邻域形状与边界填充占位中值滤波和均值滤波的思路相反均值把邻域里的值求平均中值把邻域里的值排序后取中间那个。正因为如此它对椒盐噪声几乎是天生的杀手但对高斯噪声的压制能力一般。MATLAB 里调用同样简单denoised_med medfilt2(noisy, [3 3], symmetric); % 3x3 邻域对称填充 p_med psnr(denoised_med, clean); % 椒盐噪声场景单独测一次 noisy_salt imnoise(clean, salt pepper, 0.05); denoised_salt medfilt2(noisy_salt, [3 3], symmetric); p_salt psnr(denoised_salt, clean);medfilt2 第二个参数是邻域尺寸默认 [3 3]写成 [5 5] 后去噪能力更强代价是图像里的细线和角点会被当成噪声削掉。第三个参数 padopt 控制边界填充我用 symmetric也就是镜像扩展效果比补零自然得多。实测里有个容易忽略的点中值滤波处理含 5% 椒盐噪声的图像PSNR 能比均值滤波高 46 dB但处理纯高斯噪声时两者差距往往在 1 dB 以内甚至均值更好。所以别拿到图就套中值先判断噪声类型。2.3 噪声类型与算法匹配高斯、椒盐、脉冲各找各妈传统算法选型本质上是对噪声模型的猜题。下表是我在多个模拟项目里验证过的优先顺序适合作为初筛依据噪声类型第一选择第二选择原因高斯噪声NLM / BM3D高斯滤波、均值滤波块匹配能利用自相似性压制平稳区噪声椒盐噪声中值滤波双边滤波排序取中值天然排除极值点混合噪声先中值后NLMBM3D两道工序各自处理一种噪声形态纹理丰富的图NLM小搜索窗BM3D块匹配对结构保护更好均值会毁掉纹理一个常见误用是拿中值滤波去处理高斯噪声图噪点密度不大时看不出问题噪声一强图会呈现明显的“水彩感”这是因为排序操作丢掉了邻域内的灰度层次。反过来拿均值滤波处理椒盐噪声极值点会被平均进邻域图像上留下灰白相间的脏点。所以我在接到图像预处理任务时第一件事永远是先看噪声直方图再用一行 imnoise 的反向参数去猜噪声类型而不是直接调滤波函数。3. NLM 与 BM3D从局部滤波到块匹配的分水岭3.1 NLM 原理一句讲透与 MATLAB 调用写法非局部均值滤波NLM的核心判断是图像里很多小块会在其他位置重复出现与其只看像素周围的固定邻居不如在整个搜索窗里找“长得像我的块”用相似度做加权平均。权重由块间欧氏距离的指数函数决定距离越近权重越大这就是 NLM 比均值滤波聪明的地方。为了看清这个计算过程我通常建议先用一个简化版实现跑小图理解权重是怎么算出来的function out nlm_simple(noisy, h, sw, cw) % noisy: 输入灰度图double, 0~1 % h: 平滑参数越大越平滑sw: 搜索窗半径cw: 比较窗半径 [rows, cols] size(noisy); I padarray(noisy, [swcw, swcw], symmetric); % 边界对称填充 out zeros(rows, cols); for i 1:rows for j 1:cols r i sw cw; c j sw cw; % 原图(i,j)在填充图中的位置 centerBlk I(r-cw:rcw, c-cw:ccw); % 当前像素的比较块 acc 0; wsum 0; for x -sw:sw for y -sw:sw candBlk I(rx-cw:rxcw, cy-cw:cycw); % 候选块 dist sum((centerBlk(:) - candBlk(:)).^2); % 块间欧氏距离 w exp(-dist / (h*h)); % 指数衰减权重 acc acc w * I(rx, cy); % 加权累加 wsum wsum w; end end out(i, j) acc / wsum; end end这段代码三层循环跑 128×128 的小图都要十几秒所以只适合讲原理。它的关键参数是 hh 越小只有非常相似的块才参与平均细节保得好但噪声残留多h 越大相似要求放宽平滑明显但容易出现“抹布感”。真正处理项目时我一般直接用 MATLAB 图像处理工具箱里的 NLM 实现denoised_nlm imnlmfilt(noisy, ... DegreeOfSmoothing, 0.04, ... SearchWindowSize, 21, ... ComparisonWindowSize, 7);DegreeOfSmoothing 对应上面代码里的 h范围通常取 0.020.08SearchWindowSize 是搜索窗的边长必须是奇数21 意味着在目标点周围 21×21 的范围内找相似块ComparisonWindowSize 是比较块的边长7 表示用 7×7 的块做相似度比较。窗口越大去噪越彻底计算开销也越大1080p 图上 SearchWindowSize 超过 31 就会明显发烫。3.2 把“三”补成 BM3D为什么它是传统算法的压轴标题里的“三”字后面没有内容了按我做这类项目时最常用的补全它指向 BM3DBlock-Matching and 3D Filtering。BM3D 的思路是在 NLM 上再走一步先把相似的块按距离找出来堆成一个三维数组对这个三维数组做变换在变换域里收缩系数再逆变换回空间域。这一步让相似块之间的相关性被真正利用起来而不只是像 NLM 那样做二维加权平均。BM3D 的处理流程分三步第一步块匹配把每个参考块在邻域里找到最相似的若干块第二步协同滤波把堆好的三维块做 3D 变换后硬阈值收缩第三步聚合把处理完的块放回原位置重叠区域按权重叠加。MATLAB 基础工具箱里没有官方版本常见做法是找编译好的 mex 实现或者可信的第三方源码。调用方式通常约定成这样% 假设环境里已有一个 bm3d 接口输入 double 图sigma 为噪声标准差 [denoised_bm3d, ~] bm3d(noisy, 0.1); p_bm3d psnr(denoised_bm3d, clean);这套接口的隐性要求是输入必须是 0~1 的 double不能直接扔 uint8 进去。sigma 参数决定收缩强度给小了噪声残留给大了图像会“塑料化”。我的经验是先对噪声图做一次估计用一个 7×7 中值滤波后的残差图估算标准差再传给 BM3D比手填数字稳得多。3.3 传统去噪参数影响表平滑度、搜索窗、块大小怎么配合NLM 和 BM3D 都有三件套参数互相牵制。整理成表方便对照参数过小过大常见起步值DegreeOfSmoothing (NLM)噪声压不掉边缘和纹理被抹平0.04SearchWindowSize (NLM)找不到相似块计算量大慢21ComparisonWindowSize (NLM)相似度判据不牢块太大细节反而丢7sigma (BM3D)噪声残留过平滑失去细节0.050.1搜索步长 (BM3D)精细但慢快但块匹配质量降收敛在46需要特别提醒的是NLM 的 ComparisonWindowSize 取 7 以上时小尺度纹理很容易被整体平均掉。处理医学影像或者工业缺陷图这类纹理敏感场景时我会把比较窗收到 5并用 SearchWindowSize 补偿去噪强度。批量调参时别用手一个个试写个三层 for 循环把 PSNR、SSIM 同时记录下来选其中 SSIM 最高的那组因为 PSNR 对轻微过平滑不敏感SSIM 更贴近人眼感受。4. 卷积神经网络去噪代码的写法从残差学习出发复刻 DnCNN 思路4.1 为什么 CNN 能同时压制噪声和保住纹理传统算法的问题是滤波规则固定无论图像内容是边缘还是平坦区都套同一套相似度逻辑。CNN 去噪的核心变化是把“去噪规则”变成由数据学出来的参数。最常见的做法是复刻 DnCNN 的思路让网络学习残差——输入噪声图输出噪声本身干净图等于输入减去网络输出。这个设计的聪明之处在于网络不需要记住各种纹理长什么样只需要判断哪些像素偏移属于噪声。平坦区域噪声是均匀的随机偏移边缘区域噪声会被抑制学出来就是一个自适应的噪声估计器。数据驱动带来的好处是训练集里包含什么噪声网络就擅长处理什么噪声代价是换了一种噪声就要重新训练这也是深度学习项目里最大的隐性成本。4.2 训练数据生成成对噪声图是 CNN 去噪的前提CNN 去噪必须有成对数据同一张干净图配上加噪版本。在 MATLAB 里最常见的做法是准备一批干净图像然后在线切块、加噪、存成数组。下面的代码演示了怎么生成训练集clear; rng(42); patchSize 64; numPatches 2048; sigma 0.05; % 噪声标准差 0.05 cleanP zeros(patchSize, patchSize, 1, numPatches); noisyP zeros(size(cleanP)); % 准备几张干净图随便什么内容都行这里用占位文件名 files {sim_graph_a.png, sim_graph_b.png, sim_graph_c.png}; for k 1:numPatches f files{randi(numel(files))}; img im2double(imread(f)); img imresize(img, [128 128]); % 统一尺寸便于切块 r randi(size(img, 1) - patchSize 1); c randi(size(img, 2) - patchSize 1); patch img(r:rpatchSize-1, c:cpatchSize-1); if rand 0.5, patch fliplr(patch); end % 随机水平翻转增强 cleanP(:, :, 1, k) patch; noisyP(:, :, 1, k) imnoise(patch, gaussian, 0, sigma^2); end target cleanP - noisyP; % 残差作为学习目标这里三个关键变量是 patchSize、numPatches、sigma。patchSize 决定网络感受野的上限64 是比较保险的起点numPatches 少于 1024 时网络很快过拟合大于 4096 时训练时间明显变长但对精度提升有限sigma 决定网络面对的噪声强度训练和推理时噪声强度差距过大效果会断崖式下跌。数据生成时只做了水平翻转实际项目里我还会加随机旋转 90° 的倍数相当于白拿四倍数据。4.3 网络层定义与训练参数层数、卷积核、学习率怎么定训练数据准备好后网络结构可以很朴素。常见配置是 3×3 卷积、ReLU 激活、卷积后接 BNBatch Normalization最后输出单通道残差图layers [ imageInputLayer([64 64 1], Name, in) convolution2dLayer(3, 64, Padding, same, Name, conv1) reluLayer(Name, relu1) convolution2dLayer(3, 64, Padding, same, Name, conv2) batchNormalizationLayer(Name, bn2) reluLayer(Name, relu2) convolution2dLayer(3, 64, Padding, same, Name, conv3) batchNormalizationLayer(Name, bn3) reluLayer(Name, relu3) convolution2dLayer(3, 64, Padding, same, Name, conv4) batchNormalizationLayer(Name, bn4) reluLayer(Name, relu4) convolution2dLayer(3, 1, Padding, same, Name, conv_out) regressionLayer(Name, loss) ]; options trainingOptions(adam, ... InitialLearnRate, 1e-3, ... MaxEpochs, 30, ... MiniBatchSize, 64, ... Shuffle, every-epoch, ... Verbose, false); net trainNetwork(noisyP, target, layers, options);三个要注意的细节。第一BN 层不能省残差的值域在 0 附近波动训练初期梯度很容易被噪声主导BN 能把中间特征重新拉回合理范围如果不加 BN学习率要降到 1e-4 才能稳定。第二初始学习率 1e-3 只适合这种小网络层数复制到 10 层以上时要同步降到 1e-4。第三推理时别忘记把网络输出加回输入testNoisy imnoise(im2double(phantom(256)), gaussian, 0, 0.05^2); predNoise predict(net, single(testNoisy)); % predict 需要 4 维输入 denoised_cnn testNoisy - predNoise;这里减号对应训练时的 target cleanP - noisyP。如果训练 target 反过来写成 noisyP - cleanP推理就要改成加号符号搞反会导致输出完全错乱——这属于深度学习项目里最常见的低级翻车现场值得在代码注释里写清楚。5. 去噪项目里翻车的 5 个现场现象、根因与解法5.1 坑 1去噪图四边发黑现象均值滤波或高斯滤波后图像四周出现一条肉眼可见的暗边PSNR 也比预期低 0.51 dB。原因imfilter 默认边界补零边缘像素参与卷积时核覆盖到大量 0 值把输出拉暗了。解决给 imfilter 显式传边界参数replicate 把边缘像素往外复制symmetric 镜像扩展都比补零自然。检查时用imfilter(noisy, h, replicate)连续处理多张图时把边界策略统一写进封装函数避免有一处调用漏掉参数。5.2 坑 2PSNR 算出来虚高或虚低现象同一组去噪结果自己算的 PSNR 和论文数据差了 10 dB 以上。原因uint8 图像直接相减MATLAB 会把负差值截断成 0差值分布被破坏PSNR 大幅偏高另外有人把带噪声图当参考图或者把去噪前的图当干净图指标完全失去意义。解决先统一 im2double再算 mse 和 psnr参考图必须是加噪声之前的同尺寸干净图。代码里固定写成p psnr(im2double(denoised), im2double(clean));不管输入是什么类型先 double 再算。5.3 坑 3NLM 去噪出现“磨皮”假象现象NLM 处理完的图噪声没了但皮肤、墙面、天空区域变得像塑料纹理细节全部消失。原因DegreeOfSmoothing 调得太大或者 ComparisonWindowSize 超过 9相似块判据太宽松导致大量不同结构的块被平均到一起。解决把 DegreeOfSmoothing 从 0.04 往 0.02 方向压同时把比较窗收到 5。如果还是磨皮检查输入图像是否被 im2double 并且值域在 0~1值域不对时 h 的标定会整体失效。5.4 坑 4CNN loss 降了视觉上却没有变干净现象训练过程中 loss 稳步下降验证 PSNR 也在涨但把网络接到测试图上输出要么偏暗要么边缘出现重影。原因最常见的是残差符号不对网络输出错误方向的信息等于在图像上叠加了反相噪声另一个常见原因是训练和推理输入尺寸不一致predict 对二维数组和四维数组的处理方式不同导致维度错位。解决先拿一张图做闭环验证把噪声图输入网络输出加上去之后和干净图对比如果叠加后噪声更明显把加减号反过来再测。维度问题通过size(predNoise)断言确认确保与输入前两维一致。5.5 坑 5BM3D 跑起来像死机进度条一动不动现象2000×2000 的图丢给 BM3D等了几分钟没有反应以为是 MATLAB 卡死。原因块匹配阶段在每一个参考块周围都要做全搜索搜索窗大、步长小计算量接近暴力枚举纯 MATLAB 循环版本基本跑不动大图。解决先用小图 256×256 验证流程同时把搜索步长调大到 8搜索窗缩到 11确认流程能跑完再逐步放大。实际项目中我倾向于用编译好的 mex 版本MATLAB 循环版只用来做算法验证不参与生产跑图。6. 把四条去噪流程装进同一个评估脚本批量对比与参数微调每次实验都手动算一遍 PSNR 和 SSIM既容易漏掉边界策略又浪费时间。我最终沉淀的做法是维护一个统一评估函数把所有方法串成表每次只改参数不碰流程。核心代码如下methods {mean, median, nlm, cnn}; results table(); for m 1:numel(methods) t tic; switch methods{m} case mean out imfilter(noisy, fspecial(average, 5), replicate); case median out medfilt2(noisy, [3 3], symmetric); case nlm out imnlmfilt(noisy, DegreeOfSmoothing, 0.04, ... SearchWindowSize, 21, ComparisonWindowSize, 7); case cnn predNoise predict(net, single(noisy)); % 注意转成 4 维 out noisy - predNoise; end elapsed toc(t); results(m, :) table({methods{m}}, psnr(out, clean), ... ssim(out, clean), elapsed, ... VariableNames, {Method, PSNR, SSIM, Time}); end disp(results);这段脚本统一了输入输出契约所有方法都接收 double 类型噪声图输出 double 去噪图。这样不同算法之间才具备可比性。批量调参时我会在外面再套一层参数遍历把每次的 PSNR、SSIM 和运行时间追加到同一个表里跑完直接看哪组参数在视觉和指标上取得平衡。说到视觉有一件事比指标更值得养成习惯把去噪结果和原始噪声图叠在一起用 imshowpair 或并排显示放大到 200% 观察边缘区域。很多参数组合的差异在指标上只有 0.1 dB但边缘的锐利程度差别很大。我有一次为了追 PSNR 把 NLM 的平滑度调高PSNR 涨了 0.15 dB边缘却糊成一片从那以后我的流程里永远是 PSNR、SSIM、视觉三者一起看缺一不可。希望帮到你。本文还有配套的精品资源点击获取