ARTICLE DETAIL

资讯详情

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

MATLAB图像去噪实战:传统算法与DnCNN对比及实现

MATLAB图像去噪实战:传统算法与DnCNN对比及实现 简介这份MATLAB代码包聚焦图像去噪两大技术路线传统算法如BM3D系列及其多类变体与基于深度卷积神经网络的DnCNN方法目标是为读者提供一套开箱即用、可对比的去噪实验方案。资源面向计算机、人工智能、通信工程、自动化、电子信息等专业的在校学生、教师及企业工程人员既适合作为课程设计、毕业设计、项目初期立项演示也适合零基础入门者对照源码理解去噪原理和实现细节。包内共177个文件压缩后约80.3MB以.m源码文件为主配合.mat数据文件、png效果图、mex编译库以及txt/md说明文档层次清晰便于按传统算法、深度学习模型和工具函数分模块学习。通过自带Demo脚本可直接观察不同算法在同一噪声场景下的复原效果比较BM3D与DnCNN的优劣并在此基础上修改参数或加入新的去噪思路。目前已有264人学习下载项目经过运行验证适合需要系统实现和快速复现图像去噪算法的读者。1. 为什么MATLAB里的图像去噪还值得重新做一遍读者一看传统图像去噪算法 DnCNN就觉得是老题但真正在MATLAB里把两者放到同一份评估脚本里跑过的人不多。反直觉的地方在于传统算法和深度卷积神经网络不是替代关系在有些噪声类型上中值滤波仍然比DnCNN稳尤其当训练数据与现场噪声分布不一致的时候。我们要做的是讲清怎么在MATLAB环境里实现两组算法——空间域、变换域、非局部均值以及基于深度卷积神经网络的DnCNN——并用同一套PSNR/SSIM指标给它们排座次。适合正在做图像去噪算法选型、课设或工程验证的工程师和学生。你不一定要有GPU读完就能先啃下来的部分是用MATLAB把传统去噪的边界摸清再把DnCNN拉起来对比。2. 传统图像去噪算法在MATLAB里的实现与参数边界2.1 空间域滤波的最小实现均值、中值与高斯空间域滤波的思想是做窗内邻域统计。MATLAB里最容易上手的三个函数是imfilter、medfilt2和imgaussfilt。均值滤波用fspecial生成核再卷积等价于一个低通滤波器代价是边缘被抹平中值滤波对椒盐噪声几乎免疫但对高斯噪声的提升有限高斯滤波适合噪声满足正态分布的场景sigma要与噪声标准差匹配过大就会把纹理一起滤掉。% 读取灰度图像添加高斯噪声与椒盐噪声 img im2double(imread(cameraman.tif)); noisy_gauss imnoise(img, gaussian, 0, 0.01); noisy_salt imnoise(img, salt pepper, 0.05); % 高斯滤波sigma直接对应噪声标准差 denoised_gauss imgaussfilt(noisy_gauss, sqrt(0.01)); % 中值滤波第二个参数控制邻域大小 denoised_med medfilt2(noisy_salt, [3 3]); % 均值滤波用fspecial生成5x5均匀核 h fspecial(average, 5); denoised_mean imfilter(noisy_gauss, h, replicate);这里imgaussfilt的一维sigma在2D图像上会做x/y方向的高斯卷积实际效果约等于两个一维高斯核的叠加。imgaussfilt默认核大小由sigma自动计算不需要手动指定尺寸。medfilt2对每个像素取邻域中值3x3邻域在椒盐噪声密度超过0.3时就会出现残留点这时把邻域放大到5x5能改善但画面会明显变模糊。均值滤波的replicate边界选项比默认的补零更能抑制边缘偏色。窗越大去噪越强细节损失也越明显没有免费午餐。提示如果图像是uint8而不是doubleimgaussfilt会自动处理数据类型但后续计算PSNR前最好统一转成double避免溢出。2.2 变换域小波去噪的阈值选择小波去噪利用的是信号和噪声在小波系数上的分布差异真实结构对应的大系数很少噪声均匀分布在所有尺度上。MATLAB里最省事的入口是wdenoise2它内置了贝叶斯阈值和通用阈值两种策略默认使用Daubechies 4小波。更灵活性的是wdencmp可以设置分解层数、阈值类型和软硬阈值。% 使用wdenoise2自动去噪默认软阈值 [denoised_wavelet, ~] wdenoise2(noisy_gauss, 4, Wavelet, sym4, ... DenoisingMethod, Bayesian, ThresholdRule, Soft); % 手动估计通用阈值thr sigma * sqrt(2*log(N)) [C, S] wavedec2(noisy_gauss, 3, sym4); detail_coeffs C(S(1,1)1 : end); sigma_hat median(abs(detail_coeffs)) / 0.6745; thr sigma_hat * sqrt(2 * log(numel(noisy_gauss))); keepapp 1; [denoised_wcmp, ~, ~] wdencmp(gbl, noisy_gauss, sym4, 3, thr, s, keepapp);wdencmp里最后一个参数keepapp控制是否保留近似分量系数一般设为1。通用阈值thr里的N是像素总数sigma用第一层细节系数的Median Absolute Deviation估计sigma_hat median(abs(detail))/0.6745。软阈值比硬阈值更平滑但会略微压低边缘幅度。如果你发现去噪后图像发虚多半是分解层数太多或阈值取高了改成4层加VisuShrink阈值会硬朗一些。在实际项目里我一般不会直接使用固定阈值。先用小波分解得到细节系数再对每一层单独计算sigma_hat做层自适应阈值效果比单一阈值好一截。代价是代码量增加十几行但对纹理多的工业图像值得。2.3 非局部均值与自相似性利用非局部均值NLM的核心假设是图像中远距离像素块也有重复模式用块间相似度做加权平均比局部窗口更聪明。MATLAB从R2016a开始内置了imnlmfilt不需要自己写循环。用法如下% NLM去噪DegreeOfSmoothing控制滤波强度默认值偏保守 denoised_nlm imnlmfilt(noisy_gauss, DegreeOfSmoothing, 0.05, ... SearchWindowSize, 21, ComparisonWindowSize, 7);参数上DegreeOfSmoothing和噪声方差近似线性关系高斯噪声方差为0.01时取0.05是合理起点。SearchWindowSize决定搜索范围越大计算越慢ComparisonWindowSize决定块大小7x7适合自然图像太小保留噪声太多太大边缘会糊。如果图像有重复纹理NLM会表现很好如果图像是细胞切片这类随机纹理NLM可能和高斯滤波拉不开差距。传统算法的边界在选型表里一眼就能看出来见下表。算法核心函数关键参数优势场景主要缺点均值滤波imfilter/fspecial核大小高斯噪声去除边缘模糊中值滤波medfilt2邻域大小椒盐噪声高斯噪声下收益低高斯滤波imgaussfiltsigma符合正态分布的噪声对离群点敏感小波去噪wdenoise2/wdencmp层数、阈值、小波基非平稳噪声、混合噪声阈值选择不好容易产生振铃非局部均值imnlmfiltDegreeOfSmoothing、搜索窗有重复纹理的自然图像大搜索窗下计算开销高这张表可以作为算法初筛的依据。优先处理椒盐噪声就选中值纯高斯噪声先用高斯滤波做基线如果图像结构复杂再考虑NLM。传统算法的共同问题是对噪声类型和强度的先验假设强换一个噪声分布就需要重新调参。3. DnCNN在MATLAB中的模型构建与训练数据准备3.1 网络结构残差学习才是DnCNN的核心DnCNN不是单纯的“卷积-激活-卷积”堆叠它的关键设计是预测残差图像而不是直接输出干净图像。设输入为y干净图像为x噪声为vDnCNN学习的是v y - x去噪结果就是y - v。残差学习让网络在浅层就能把高频噪声先拟合出来保留低通结构。深度卷积神经网络在这里的作用相当于一个非线性映射器它能把噪声模式从图像结构里剥离。MATLAB的Deep Learning Toolbox从R2019b开始推荐用dlnetwork构建网络而trainNetwork也仍然能用卷积层序列。下面是用trainNetwork可接受的形式构建17层DnCNN的网络层数组% 17层DnCNN结构3x3卷积 ReLU BN除最后一层 layers [ imageInputLayer([180 180 1], Name, input, Normalization, none) convolution2dLayer(3, 64, Padding, same, Name, conv1) reluLayer(Name, relu1) % 中间15层卷积BNReLU repetition 15; for i 1:repetition convName sprintf(conv%d, i1); bnName sprintf(bn%d, i1); reluName sprintf(relu%d, i1); layers [layers convolution2dLayer(3, 64, Padding, same, Name, convName) batchNormalizationLayer(Name, bnName) reluLayer(Name, reluName)]; end % 最后一层输出残差 layers [layers convolution2dLayer(3, 1, Padding, same, Name, conv_residual) regressionLayer(Name, residual_output)]; ];为什么中间层都是64个通道DnCNN原设计中对灰度图像去噪使用64个特征图这是参数量和感受野的折中。3x3卷积重复17层后的理论感受野是35x35能覆盖常见的噪声斑块。如果你做的是彩色图像去噪把输出通道改成3并在训练数据上使用RGB三通道tensor即可。最后一层用线性激活而不是ReLU是因为噪声残差可正可负回归层会直接约束输出接近噪声图像。3.2 训练数据生成从大图裁剪patch到配成输入输出对深度学习图像去噪依赖大量噪声图像对。MATLAB的标准做法是从干净的图像库中随机裁剪patch然后在patch上叠加高斯噪声。需要注意不能直接对整张大图输入网络训练显存和梯度更新效率都不允许180x180的小patch才能把MiniBatchSize顶上去。% 假设imds是干净灰度图像数据集这里用单张灰度图演示 clean_img im2double(rgb2gray(imread(my_image.png))); patchSize 180; numPatches 64; noiseLevel 0.05; % 随机裁剪到patchSize并加噪 patches_clean zeros(patchSize, patchSize, 1, numPatches); patches_noisy zeros(patchSize, patchSize, 1, numPatches); for i 1:numPatches r randi(size(clean_img,1) - patchSize 1); c randi(size(clean_img,2) - patchSize 1); patch clean_img(r:rpatchSize-1, c:cpatchSize-1); noisyPatch patch noiseLevel * randn(size(patch)); patches_clean(:,:,1,i) patch; patches_noisy(:,:,1,i) noisyPatch; end % 训练输入是带噪图像目标输出是残差噪声 trainInput patches_noisy; trainOutput patches_noisy - patches_clean;这里的关键是trainOutput设置为noise而不是clean。回归层的损失函数会直接比较网络输出和trainOutput的像素差异开始时网络的输出会偏向0相当于什么都不预测随着训练推进网络学会把噪声从输入中剥离。如果误把clean当作目标网络同样能训练但收敛后需要把网络输出再做一次减法推理链路多一步而且显存占用更高。提示如果图像库里图像尺寸都小于patchSize可以用randomCropWindow2d配合imcrop做随机裁剪或者先将图像放缩到适配尺寸再裁剪避免大量零填充干扰数据分布。3.3 损失函数与训练选项DnCNN收敛的关键设置DnCNN使用的是像素级均方误差损失MATLAB里由regressionLayer自动提供。训练选项直接影响收敛质量学习率、批次大小、梯度裁剪、学习率下降策略这四个参数值得反复调。下表是我在MATLAB里跑DnCNN时验证过的一组起始值训练选项推荐值说明solveradam相比sgdm收敛更快适合残差学习任务MiniBatchSize16~32180x180 patch下16为稳妥起点GPU显存够可到32InitialLearnRate1e-3大于1e-2会梯度爆炸小于1e-4收敛过慢MaxEpochs50~100小规模数据集50轮足够GradientThreshold0.1防止批内极端样本破坏权重更新LearnRateSchedulepiecewise每20轮乘以0.1精细调整阶段用小学习率% 训练网络 options trainingOptions(adam, ... MiniBatchSize, 16, ... MaxEpochs, 60, ... InitialLearnRate, 1e-3, ... LearnRateDropFactor, 0.1, ... LearnRateDropPeriod, 20, ... GradientThreshold, 0.1, ... Shuffle, every-epoch, ... Plots, training-progress); net trainNetwork(trainInput, trainOutput, layers, options);GradientThreshold设为0.1有时看起来太小但对残差学习很有效因为前几层梯度容易因噪声patch的方差而累积裁剪能稳定BN层的统计量。训练中如果看到训练loss出现周期性尖峰多半是学习率下降时BN动量太大把rlr设置为1e-3左右。训练结束后需要验证把测试噪声图像减去predict(net, noisy)得到去噪结果而不是直接拿网络输出当干净图像因为最后一层输出的是残差。4. 从传统算法到DnCNN的对比实验设计与结果量化4.1 噪声模型与评估指标用PSNR和SSIM统一标尺对比实验必须有统一的加噪方式和指标。合成噪声通常只做两类高斯噪声和椒盐噪声因为工业上大多数场景都能近似成这两种。PSNR衡量像素级差异SSIM更接近人眼对结构信息的判断。MATLAB自带psnr和ssim函数直接调用即可。% 添加固定随机种子可复现实验 rng(42); clean im2double(imread(cameraman.tif)); noiseSigma 0.05; noisy clean noiseSigma * randn(size(clean)); % 计算去噪结果的PSNR和SSIM psnr(denoised_gauss, clean) ssim(denoised_gauss, clean)注意psnr函数输入必须都是double且范围0-1整型图像会被自动转换但结果会有细微差别。为了公平传统算法应当使用与DnCNN相同的测试图像尺寸。如果测试图像不是训练patch的倍数网络可能需要分块推理这会影响整体性能。4.2 端到端评估脚本一次跑完五个方法下面是一个可执行的对比脚本综合了第2章和第3章的内容输出传统方法和DnCNN在同一张测试图上的PSNR/SSIM表格methods {Noisy, Gaussian, NLM, DnCNN}; psnr_vals zeros(1,4); ssim_vals zeros(1,4); % 得到DnCNN预测残差去噪结果为输入减残差 residual predict(net, noisy); denoised_dncnn noisy - residual; % 传统方法结果 denoised_gauss imgaussfilt(noisy, noiseSigma); denoised_nlm imnlmfilt(noisy, DegreeOfSmoothing, 0.1); % 计算指标 psnr_vals(1) psnr(noisy, clean); ssim_vals(1) ssim(noisy, clean); psnr_vals(2) psnr(denoised_gauss, clean); ssim_vals(2) ssim(denoised_gauss, clean); psnr_vals(3) psnr(denoised_nlm, clean); ssim_vals(3) ssim(denoised_nlm, clean); psnr_vals(4) psnr(denoised_dncnn, clean); ssim_vals(4) ssim(denoised_dncnn, clean); T table(methods, psnr_vals, ssim_vals, ... VariableNames, {Method, PSNR_dB, SSIM}); disp(T);这段代码有一个容易忽略的细节DnCNN的输入需要是4维数组[batch, height, width, channels]而psnr和ssim接受2维矩阵所以使用predict时把noisy的维度保持为[H W 1]即可。如果你用了mini-batch预测预测完后再squeeze回来。NLM的DegreeOfSmoothing在噪声方差0.01时取0.05在0.05时取0.1只是经验起点实际操作中可以先用10%噪声图像一组再对参数做网格搜索。4.3 结果解读什么时候DnCNN会输给传统算法不要默认DnCNN在所有指标上都赢。如果你只用少量干净图像训练DnCNN然后用它去处理椒盐噪声网络不一定见过这种噪声模式结果反而会出现细小的伪纹。传统中值滤波对椒盐噪声几乎是结构性的免疫这种场景下中值滤波的PSNR和SSIM往往高于DnCNN。另一个典型场景是低光照下的泊松噪声噪声方差和信号强度相关DnCNN如果只在固定高斯噪声上训练泛化能力不如小波域方差稳定方法。那么什么时候必须用DnCNN第一噪声不是简单的高斯或椒盐而是相机信号处理链留下的复杂噪声第二你有大量和测试场景同分布的数据可以让网络学到噪声形态第三你需要在同样的噪声水平下保留更多纹理细节传统算法在强去噪与细节保留之间难以平衡。做算法选型时别盯着PSNR高几个小数点把测试图放大到200%看边缘和纹理DnCNN的优势往往是视觉上的。场景噪声类型更推荐的方法原因黑白图像扫描件椒盐噪声中值滤波DnCNN训练噪声分布不匹配时泛化差自然景观照片高斯噪声DnCNN保留边缘纹理荧光显微镜图像泊松噪声小波/方差稳定变换噪声方差随信号变化手机夜景RAW复杂信号链噪声DnCNN数据驱动建模如果现场噪声和训练噪声的标准差差太多不需要急着重新训网络可以先在训练数据里把噪声sigma范围拉大比如0~0.1均匀采样再用大sigma区间约束网络学习。这就是噪声自适应思路的一个简化版本让模型见过连续分布的噪声水平推理时才能对未知sigma更宽容。5. 让DnCNN在MATLAB里更快更省内存的几个技巧5.1 用dlnetwork替换trainNetwork减少显存trainNetwork训练出的网络在推理时仍保留许多训练临时变量改用dlnetwork可以更精确地控制前向计算。先把训练好的层数组转成dlnetwork预测时用dlarray和predict函数dlnet dlnetwork(net.Layers); inputDL dlarray(noisy, SSCB); residualDL predict(dlnet, inputDL); denoised noisy - extractdata(residualDL);dlnetwork支持自动微分和自定义训练循环也更容易把推理图导出到GPU。你可以在网络尾部加一个“减去输入”的自定义层让模型直接输出干净图像不过这会丢失DnCNN残差设计的可解释性。我倾向于保留残差输出推理时做一次减法后续如果要换噪声自适应策略也方便。5.2 分块推理避免大图内存溢出测试图像往往比训练patch大得多比如4K分辨率。直接把整图塞进DnCNN会很容易吃掉数GB显存。分块推理是通用做法把图像切分成重叠patch预测完再拼回。patchH 180; patchW 180; overlap 10; stepH patchH - overlap; stepW patchW - overlap; denoised_img zeros(size(noisy)); count_map zeros(size(noisy)); for i 1:stepH:size(noisy,1)-patchH1 for j 1:stepW:size(noisy,2)-patchW1 patch noisy(i:ipatchH-1, j:jpatchW-1); residual predict(dlnet, dlarray(patch, SSCB)); denoised_img(i:ipatchH-1, j:jpatchW-1) ... denoised_img(i:ipatchH-1, j:jpatchW-1) ... patch - extractdata(residual); count_map(i:ipatchH-1, j:jpatchW-1) ... count_map(i:ipatchH-1, j:jpatchW-1) 1; end end denoised_img denoised_img ./ count_map;重叠区域累加再除以count_map是为了消除拼接缝。步长设为patch尺寸的一半是安全起点。注意边界上patch可能越界上面的循环用size限制确保patch完全落在图像内对于不能被整除的尺寸会漏掉最右和最下的窄条更完整的实现应该对边界做反射填充或者缩小最后一步的范围。5.3 从DnCNN到噪声自适应的迁移你已经掌握的不只是“去噪网络”而是一个残差学习范式。最近热门的噪声自适应图像去噪思路本质上也是在DnCNN的残差结构上增加对噪声级别的编码输入让网络在不同噪声水平间切换。在MATLAB里做这件事并不复杂把噪声标准差作为一个额外通道拼到输入张量后面网络前几个卷积核通道数从1改成2就能让DnCNN看到噪声的先验强度从而在推理时针对不同sigma给出不同强度的去噪。在需要处理非平稳噪声的场景这个技巧比训练多个模型更省事。如果你需要把灰度DnCNN迁移到彩色图像只需把输入层改成3通道输出残差通道改成3并用RGB patch训练。训练好的灰度模型也可以作为初始化权重用迁移学习只微调最后一层卷积训练时间能缩短一半。但要注意BN层的统计量在迁移时最好重新估计否则彩色图像上的颜色偏差会被放大。MATLAB的Deep Network Designer是调试这些改动很顺手的地方你可以可视化DnCNN的每一层激活值确认残差输出是否真的主要集中在噪声高频区域。把一个模型跑通后接下来值得花时间的地方是设计噪声退化模型让训练数据的噪声分布更加贴近真实场景这比单纯堆网络层数带来的收益大得多。本文还有配套的精品资源点击获取
返回列表