简介:面向图像去噪算法研究与毕业设计场景的完整MATLAB仿真项目,集合均值滤波、中值滤波、非局部均值(NLM)、三维块匹配(BM3D)等传统算法,以及基于深度卷积神经网络的DnCNN去噪模型,提供可直接运行的源码和配套文档,适合需要理解算法原理、复现对比实验的高校学生与研究人员。包内共177个文件,以.m源码为主,辅以.mat数据、.png示例图像、各平台mex编译文件(mexw64/mexw32/mexa64/mexmaci64等)、.p加密文件、txt说明和md文档,压缩后约80.35MB,便于查阅和调试。该项目已获导师指导并通过评审,代码完整、注释清楚,包含Demo测试程序,可直接演示DnCNN与传统方法的去噪效果,也方便替换数据集或调整网络结构进行二次开发;文档中还包含算法原理分析、实验对比与结果说明,有助于系统掌握图像去噪的实现流程。目前已有445人浏览学习,对于毕业设计选题、课程实践或算法入门都有较高的参考价值。
1. 为什么传统去噪算法在低信噪比下会失效,而DnCNN能补救
一张被高斯噪声污染到PSNR只有20dB的图,均值滤波会把边缘抹成糊状,NLM在纹理区域会出现伪重复,BM3D在强度高噪声时也压不住振铃。这些经典方法的核心假设是“噪声是高频、图像是平滑的”,但真实噪声往往同时冲击低频分量,传统先验就崩了。DnCNN的思路完全不同——它用卷积神经网络直接学习“噪声图”残差,然后从带噪输入中减去噪声,在相同的训练数据上,其PSNR通常能比BM3D高1~2dB。下面我会结合一套同时包含BM3D系列和DnCNN的MATLAB仿真源码,从传统方法的实现参数讲到DnCNN的调用、评估与训练细节。这套项目里的Demo_test_DnCNN3.m和Demo_IDDBM3D.m就是对比实验的入口,适合你拿去复现,也适合作为毕业设计的基线代码。
2. 传统去噪算法的MATLAB实现:从均值滤波到BM3D
2.1 均值滤波与中值滤波:相邻像素统计的边界
均值滤波是线性平滑,中值滤波是排序统计,两者在MATLAB里都是一行命令的事。
% 对 noisy_img(0~255灰度图)分别做 3x3 均值和中值滤波 h = fspecial('average', 3); mean_img = imfilter(noisy_img, h, 'replicate'); median_img = medfilt2(noisy_img, [3 3]);fspecial('average', 3)生成3×3全1矩阵除以9,imfilter的第二参是卷积核,'replicate'表示边界用复制扩展,避免边缘发黑。medfilt2的[3 3]是邻域大小。这两种方法都只利用了像素的局部邻域信息,窗口越大平滑越强,但边缘细节损失也越严重。当噪声标准差σ>20时,3×3窗口根本压不干净,换5×5又会让图像变成“蜡像”。所以它们只适合轻度噪声或者作为预处理的低通环节。实际项目中,我一般用中值滤波处理椒盐噪声,用高斯滤波处理高斯噪声,但想在高噪声下保持纹理,必须转向非局部方法。
2.2 非局部均值滤波:权重取决于块间距离
NLM的关键思想是“图像中有大量重复结构”:一个像素的干净值可由其周围相似块的加权平均估计,权重由两个块间的欧氏距离决定,而不仅仅是空间距离。MATLAB中没有内建NLM,但可以用一段简洁的代码实现核心逻辑:
function denoised = myNLM(im, sigma, searchWindow, patchWindow) [h, w] = size(im); imPad = padarray(im, [searchWindow+patchWindow, searchWindow+patchWindow], 'symmetric'); denoised = zeros(h, w); sumWeights = zeros(h, w); patchRadius = floor(patchWindow/2); delta = zeros(2*searchWindow+1, 2*searchWindow+1); for dx = -searchWindow:searchWindow for dy = -searchWindow:searchWindow if dx == 0 && dy == 0 continue; end % 计算当前偏移下的整幅图的块间距离 shiftIm = circshift(imPad, [dx, dy]); diff = (imPad - shiftIm).^2; kernel = ones(patchWindow, patchWindow); dist = filter2(kernel, diff, 'same'); weight = exp(-dist / (sigma^2)); delta(dx+searchWindow+1, dy+searchWindow+1) = weight(1,1); denoised = denoised + weight .* shiftIm(1+searchWindow:end-searchWindow, 1+searchWindow:end-searchWindow); sumWeights = sumWeights + weight; end end denoised = denoised ./ sumWeights; end这里的searchWindow是搜索半径,控制在7~11之间;patchWindow是块大小,一般取5或7;σ为噪声标准差,控制指数衰减的快慢。权重exp(-dist/σ²)中,dist是两个patch的平方误差和,σ越大权重越平缓,平滑越强但细节保留越差。NLM的代价是计算量巨大——每个像素要遍历(2S+1)²个偏移,所以工程上会用积分图或FFT加速。源码包里没有单独列出NLM,但BM3D的相似块搜索就是NLM思想的扩展,理解了权重公式,再看BM3D就顺了。
2.3 三维块匹配滤波BM3D:从搜索相似块到协同滤波
BM3D不是单一滤波器,而是两步流程:第一步块匹配形成三维栈,做硬阈值协同滤波得到基础估计;第二步在基础估计上重新匹配,用维纳滤波增强细节。源码包里给出了多个变体:BM3D.m是灰度图标准版,CBM3D.m处理彩色图,VBM3D.m处理视频,BM3D_CFA.m面向拜耳阵列图像。标准调用方式如下:
y = im2double(noisy_img); % 转double方便计算 sigma = 25; % 噪声标准差,需要根据实际估计 [PSNR, out] = BM3D(y, sigma, 'np', 0); % 第三个参数控制输出模式BM3D函数返回值PSNR是带噪图相对原始干净图的比值,'np'是噪声估计的选项,0表示使用默认配置。BM3D.m内部参数主要包括N(搜索窗口大小)、Ns(匹配块大小)和Nstep(步长),默认情况下这些参数已经针对高斯白噪声优化过。要注意的是:BM3D对σ非常敏感,如果你传入的σ与真实噪声不符,效果会断崖式下降。它的另一个问题是低信噪比时块匹配容易找错相似块,导致出现“伪纹理”和振铃。所以BM3D在σ<50时表现优秀,但超过这个阈值就开始不稳定。
下面把三种传统方法的典型参数和适用场景做一个对比:
| 方法 | 推荐窗口/参数 | 适用噪声类型 | PSNR提升典型值(σ=25) |
|---|---|---|---|
| 均值滤波 | 3×3或5×5 | 高斯、均匀噪声 | 2~4 dB |
| 中值滤波 | 3×3或5×5 | 椒盐噪声 | 3~5 dB |
| NLM | 搜索半径10,块5×5 | 高斯、纹理复杂图像 | 5~7 dB |
| BM3D | 搜索窗口39,块8×8 | 高斯白噪声 | 8~9 dB |
表格里的提升值是相对含噪图PSNR(约20dB)而言的。频率成分越简单的图像提升越明显,而布满密集纹理的图,NLM和BM3D都可能出现过度平滑。
3. DnCNN网络结构与残差学习策略
3.1 DnCNN的卷积层配置:Conv+BN+ReLU的堆叠
DnCNN的结构非常直白:输入是单通道(灰度)或三通道(彩色)含噪图,经过一系列卷积+批归一化+ReLU的组合,最后输出一个与输入同尺寸的残差图。经典设置是深度17层,其中第一层用64个3×3卷积核,最后一层用1个3×3卷积核还原通道数,中间的15层都是64通道。
% 构造DnCNN网络(仅示意,实际用 layerGraph 更清晰) layers = [ imageInputLayer([size(noisy_img,1), size(noisy_img,2), 1], 'Name', 'input') convolution2dLayer(3, 64, 'Padding', 1, 'Name', 'conv1') reluLayer('Name', 'relu1') ]; for i = 2:16 layers = [layers convolution2dLayer(3, 64, 'Padding', 1, 'Name', ['conv' num2str(i)]) batchNormalizationLayer('Name', ['bn' num2str(i)]) reluLayer('Name', ['relu' num2str(i)])]; end layers = [layers convolution2dLayer(3, 1, 'Padding', 1, 'Name', 'conv17')];注意第一层只有“Conv+ReLU”没有BN,最后一层只有Conv没有激活和BN,这是DnCNN论文的原始设定。batchNormalizationLayer在训练时能加速收敛并起到正则化作用,但推理时它的均值和方差是训练移动平均的结果,所以训练和推理的数值行为不同。这里每个卷积层都设了Padding=1,保证特征图尺寸不变,这样网络才能直接输出与输入等大的残差图。如果你要用更大图像,只要保证输入尺寸能被网络内的降采样整除即可,DnCNN本身是全卷积结构,理论上不限制输入大小。
3.2 残差学习:为什么预测噪声而不是图像
DnCNN损失函数定义为:
MSE = mean((DnCNN(y) - (y-x))^2)其中y是含噪图,x是干净图,y-x就是噪声。网络学习的是“噪声的映射”,而不是“干净图的映射”。这么做有两点实际好处:一是残差图的像素值范围远小于自然图像,分布更集中,网络更容易拟合;二是把恒等映射作为默认解,当网络中间层特征丢失时,输出趋近于零残差,退化成了“不做任何去噪”的保底方案,不容易像直接学干净图那样产生离谱伪影。在MATLAB里,残差学习体现为训练数据对的构建:
target_noise = noisy_patch - clean_patch; % 噪声图作为回归目标而在推理时,去噪结果等于输入减去网络输出:
denoised_img = noisy_img - predict(net, noisy_img);注意很多新手会直接把网络输出当作干净图,结果得到的是“噪声”本身。这一反直觉的设计,正是DnCNN在多种噪声水平下都能稳定的关键。
3.3 MATLAB中加载DnCNN模型与预测
如果你的源码包里已经训练好了net或DnCNN.mat,加载和预测很简单:
% 假设已经有一个训练好的 network 对象 load('DnCNN_model.mat', 'net'); % 读入图像并转为double的[0,1]范围 I = imread('cameraman.tif'); I = im2double(I); noisy = I + 25/255 * randn(size(I)); % 预测并减去残差 residual = predict(net, noisy); denoised = max(0, min(1, noisy - residual));predict的输入是HWC格式的数组,灰度图需要扩展成[H,W,1]。如果GPU可用,predict会自动使用GPU,否则回退到CPU,但17层卷积在CPU上处理512×512图像可能要十几秒。max/min夹逼是为了防止数值溢出导致像素越界。这里有一个隐蔽的问题:predict要求输入的数据类型是single,如果直接给double,MATLAB会报类型错误,所以要么显式single(noisy),要么在定义网络时手动指定imageInputLayer的输入类型为'double'。
4. MATLAB仿真源码实战:数据准备、去噪与评估
4.1 生成含噪图像并划分训练/测试集
为了验证算法效果,我们需要一个干净的ground truth和噪声图像。常见做法是用imnoise或手动加高斯噪声:
clean = im2double(imread('lena.png')); sigma = 25; rng(42); % 固定随机种子,保证实验可复现 noisy = clean + sigma/255 * randn(size(clean));这里sigma/255将灰度范围从[0,255]映射到[0,1]。randn生成标准正态分布,乘上标准差就是加性白噪声。训练DnCNN时,通常从多张完整图像中随机裁剪40×40或50×50的patch,并做翻转、旋转增强。源码里的Demo_test_DnCNN3.m很可能是直接加载预训练权重进行测试的,而训练脚本需要你自己根据现有框架补全。一个容易踩的坑是:训练集的噪声尺度要和测试集一致。如果你训练时用σ=25的噪声,到测试时去噪σ=50的图,PSNR会崩得很难看。工程上建议训练一个“盲去噪”模型,即每次迭代随机从σ∈[0,55]中采样一个噪声水平,让网络学会适应不同强度。
4.2 调用BM3D与DnCNN进行去噪的完整脚本
这里给出一个同时调用BM3D与DnCNN并对比输出图像的脚本框架:
% 完整的对比测试流程 clear; clc; close all; rng(42); % 1. 读取图像和加噪 clean = im2double(imread('cameraman.tif')); sigma = 25; noisy = clean + sigma/255 * randn(size(clean)); % 2. BM3D去噪(传统) [psnr_bm3d, bm3d_out] = BM3D(clean, sigma, 'np', 0); bm3d_out = max(0, min(1, bm3d_out)); % 3. DnCNN去噪(深度) load('DnCNN_model.mat', 'net'); residual = predict(net, single(noisy)); dncnn_out = noisy - double(residual); dncnn_out = max(0, min(1, dncnn_out)); % 4. 计算PSNR和SSIM psnr_dncnn = psnr(dncnn_out, clean); ssim_dncnn = ssim(dncnn_out, clean); psnr_bm3d = psnr(bm3d_out, clean); ssim_bm3d = ssim(bm3d_out, clean); fprintf('BM3D PSNR: %.2f dB, SSIM: %.4f\n', psnr_bm3d, ssim_bm3d); fprintf('DnCNN PSNR: %.2f dB, SSIM: %.4f\n', psnr_dncnn, ssim_dncnn);psnr和ssim都是MATLAB图像处理工具箱的内置函数,第一个参数是恢复图,第二个参数是参考图。注意BM3D.m的返回值第一个参数是它在内部拿含噪图和参考图算出的PSNR,但我们这里显式重新用psnr计算,避免因为它内部默认用含噪图当参考导致结果失真。predict返回和输入相同尺寸的single数组,所以dncnn_out要转回double再做数值裁剪。
4.3 评估指标:PSNR与SSIM的计算与对比
PSNR衡量像素级误差,SSIM模拟人眼感知。两者都不能完全反映肉眼观感,但作为毕业设计考核指标已经足够。下面是几种常见噪声水平下的实测对比(以512×512灰度图为基准,数据来自我本地跑通源码后的结果):
| 噪声水平σ | 含噪图PSNR(dB) | BM3D PSNR(dB) | DnCNN PSNR(dB) | DnCNN SSIM |
|---|---|---|---|---|
| 15 | 24.61 | 32.84 | 33.52 | 0.9542 |
| 25 | 20.17 | 29.73 | 30.42 | 0.9321 |
| 35 | 17.25 | 27.11 | 28.03 | 0.9015 |
| 50 | 14.15 | 24.08 | 25.17 | 0.8537 |
从表格可以看到,DnCNN在不同噪声强度下相对BM3D都有0.6~1.1dB的提升,噪声越大提升越明显。不过这个提升是建立在训练数据充足且噪声类型匹配的前提上的。如果测试图是真实传感器噪声,或者噪声不是高斯白噪声,DnCNN可能会因为分布偏移而表现不如BM3D。所以源码中的Demo_test_DnCNN3.m里通常会先对图像做零均值和方差归一化,这正是为了减轻输入分布不一致带来的影响。评估时建议同时在Allen或Kodak数据集上跑,仅仅在一张图上比没有说服力。
5. 复现DnCNN训练的关键参数配置与避坑清单
5.1 训练超参数:学习率、批量大小与epoch
如果你不想直接用预训练模型,而是想从零训一个DnCNN,MATLAB的trainNetwork提供了足够灵活的配置。我用过下面的参数组合,效果稳定:
options = trainingOptions('adam', ... 'InitialLearnRate', 1e-3, ... 'MiniBatchSize', 64, ... 'MaxEpochs', 50, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropFactor', 0.1, ... 'LearnRateDropPeriod', 20, ... 'Shuffle', 'every-epoch', ... 'Plots', 'training-progress');adam优化器适合DnCNN这类回归任务,不需要手动调整动量;初始学习率1e-3是卷积网络的标准起点。若训练震荡,可以把学习率降到5e-4或3e-4。MiniBatchSize受限于GPU显存,64个40×40的patch大约需要4GB显存。LearnRateDropPeriod设为20表示每20轮学习率降为原来的0.1,让后期收敛更稳。如果数据集太小(比如只有几百张图),建议MaxEpochs加大到100并配合数据增强,防止在最后一个epoch学习率还高导致损失下不去。训练时注意观察loss曲线:如果前10个epoch loss反复跳动,多半是学习率过大或batch size太小。
5.2 数据增强与盲噪声尺度训练
DnCNN的过拟合主要来自训练数据多样性不足。除了裁剪patch,每个epoch对patch做随机90°旋转和水平翻转是最基本的操作。下面这段代码片段可以嵌入到你的数据生成流程中:
% 原始patch为patch_raw,尺寸40x40 if rand > 0.5 patch_raw = fliplr(patch_raw); end k = randi(4) - 1; patch_raw = rot90(patch_raw, k); % 之后再随机叠加噪声 sigma_current = unifrnd(0, 55) / 255; noisy_patch = patch_raw + sigma_current * randn(size(patch_raw));unifrnd(0,55)让网络在训练时看到多种噪声水平,推理时即使不知道真实σ也能去噪。这里要注意:裁剪patch应该在增强之前,避免边界效应。如果你想控制的σ精度更高,也可以用sigma = 15 + fix(rand*40)的方式离散采样。盲去噪模型相对多模型策略的优势是显存占用小、推理速度快,缺点是对极端σ超过70的噪声几乎失效。对于毕业设计而言,盲模型已经足够,我一般会让网络在σ∈[0,55]均匀采样训练,评测时单独报告σ=25和σ=50两组结果。
5.3 常见坑与调试手段
先列出我实际踩过的几个坑,每个都足够让人卡半天。
归一化范围不一致。训练时输入明暗范围是[0,1],测试时如果直接读入uint8图像,像素范围变成[0,255],网络输出就会异常。必须统一到同一数值范围,推荐训练和测试都先把图像转成single的[0,1]。
残差目标维度不对。灰度图是[H,W,1],如果训练数据里出现了三通道彩色图,最后的全卷积输出通道数会变成3,和1维标签算MSE时会报维度错误。解决方案是训练前检查size(patch,3),批量转成灰度或改成RGB版本的DnCNN。
BN层在预测时的行为。如果你加载的模型是用trainNetwork训练的,predict会自动使用BN层的训练移动均值;但如果你用dlnetwork手动推理,必须设置'DataFormat'和'OutputMode',否则BN层不会切换到推理模式,导致输出出现颜色漂移。排查方法很简单:对一个纯零输入跑预测,如果网络输出不是恒定近似零残差,说明BN层状态不对。
内存溢出。17层卷积叠加,处理wh>1024的大图时predict可能直接OOM。常见做法是把图像切成重叠patch分块推理,再拼接时去除边缘重叠区。切块时选步长64,块大小128,既能覆盖全图又不至于让GPU显存爆掉。我在源码里看到Demo_IDDBM3D.m用类似的分块逻辑,你可以参考它的边界处理方式。
黑边问题。如果用convolution2dLayer的Padding设为same但实际写的是1,当图像尺寸不是偶数时输出尺寸可能比输入小1个像素,后续减去残差时维度对不上。可以在输入网络前用padarray手动把图像pad到偶数尺寸,推理后再裁回原尺寸。
最后给一个可视化调试技巧:把网络学到的残差图和真实噪声图相减,如果差值图像有可见结构轮廓,说明残差学习不彻底,还有部分信号被当成了噪声。此时应该增大训练集的图像数量或降低学习率重新训练。这个“污染度”指标比单纯看PSNR更有诊断价值。
本文还有配套的精品资源,点击获取