
简介面向毕业设计与课程设计的MATLAB图像去噪项目资源系统覆盖传统算法与深度卷积神经网络DnCNN两条技术路线适合图像处理、计算机视觉方向的学生和入门开发者参考学习。传统去噪部分包含均值滤波、中值滤波、高斯滤波等基础算法同时提供BM3D及其变体等经典方法的脚本可结合不同噪声类型观察参数对细节保留与平滑效果的影响深度学习部分则完整搭建DnCNN网络涵盖卷积层、批量归一化、ReLU激活配置以及训练与预测流程帮助理解从传统滤波到数据驱动学习的差异。压缩包共177个文件约80.31MB以.m源码脚本、.mat训练数据、.png示例图片为主另含跨平台mex编译库和说明文档便于直接运行、效果对比与二次开发。该项目已有854人学习下载对需要开展图像去噪实验、算法对比或完成毕设工作的读者是一份完整且可复现的参考工程。1. 图像去噪不是滤掉噪声传统方法与 DnCNN 的建模差异决定代码怎么写第一次用 MATLAB 做图像去噪大作业时大多数人会先拖一个 medfilt2 出来看到画面光滑了就算交差。真正对比后会发现传统图像去噪算法和小波阈值在低噪声下表现尚可一旦噪声方差升高画面要么糊成一片要么出现振铃。基于深度卷积神经网络的 DnCNN 走的是另一条路它不是设计一个滤波器模板而是通过大量带噪图像对训练网络去预测噪声残差再用原图减去残差得到干净图。两种思路在 MATLAB 里的落地路径完全不同参数调节方式也几乎没有交集。这篇博文把传统算法和 DnCNN 从实现、参数、评价到自适应批处理逐层拆开适合正在做图像处理课程设计、毕业设计或工程验证的读者。2. 在 MATLAB 中实现传统图像去噪算法均值、中值、维纳与小波阈值传统图像去噪算法在 MATLAB 里不需要额外工具箱Image Processing Toolbox 自带的函数足够搭起一套完整对比基线。常见做法是把一张干净图叠加已知噪声再用不同滤波器处理最后用 PSNR 和细节保留情况判断好坏。比较时不要只看视觉上干不干净还要看边缘是否被磨平、弱纹理有没有消失。下面这套代码是图像处理大作业里最常用的一组组合我一般用它作为深度学习方法上线前的基准线。2.1 传统去噪算法的核心逻辑与 MATLAB 最小实现2.1.1 均值与中值滤波平滑假设下的两种取舍均值滤波假设噪声是高频信号图像真实内容在局部区域变化平缓所以用邻域均值替换中心像素能压低随机波动。中值滤波更像排序统计它对椒盐噪声特别有效但在平滑区域会产生细碎灰阶突变。两者共同问题是无差别处理边缘窗口越大去噪越强边缘损失也越明显。% 读取灰度图并转为 double值域保持 [0,1] img im2double(imread(cameraman.tif)); noisy imnoise(img, gaussian, 0, 0.01); % 高斯噪声均值为 0方差为 0.01 % 均值滤波3x3 与 7x7 对比 avg3 imfilter(noisy, fspecial(average, [3 3]), replicate); avg7 imfilter(noisy, fspecial(average, [7 7]), replicate); % 中值滤波同样对比两种邻域尺寸 med3 medfilt2(noisy, [3 3]); med7 medfilt2(noisy, [7 7]);代码说明fspecial(average, [3 3])生成一个归一化平均核所有元素和为 1卷积后不会改变图像整体亮度。imfilter的第三参数replicate控制边界填充方式意思是用边界的像素值向外复制避免黑边进入统计另一个常用选项是symmetric适合预期边界平滑过渡的场景。medfilt2是二维中值滤波对高密度椒盐噪声建议把窗口开到 5 以上但对细线、文字这类只有 1 像素宽的结构破坏非常明显。这两类滤波器的调参思路是单调的窗口越大去噪越彻底PSNR 不一定越高因为边缘对比度下降会在视觉上产生「塑料感」。如果只用这两种算法很容易陷入一个困局——为了指标好看不断加大窗口结果边缘一塌糊涂。2.1.2 维纳滤波与小波阈值从统计和频域出发维纳滤波和小波阈值比前两种多做了一步。维纳滤波器把信号和噪声当作随机过程在每个局部窗口内估计均值和方差再对中心像素做自适应收缩小波阈值则把图像变换到小波域认为真实边缘对应大幅值系数噪声分散在小幅值系数里设定阈值把低于阈值的系数置零或收缩。% 维纳滤波开窗 5x5自动估计局部均值与方差 wn5 wiener2(noisy, [5 5]); % 小波阈值去噪三层分解 软阈值 [c, s] wavedec2(noisy, 3, db4); thr wthrmngr(dw2ddenoLVL, sqtwolog, c, s); % 通用阈值规则 cT wthresh(c, s, thr); % s 表示软阈值 wavDen waverec2(cT, s, db4);代码说明wiener2默认会在每个窗口内估计噪声方差也可以手动传入已知噪声方差wiener2(noisy, [5 5], noiseVar)。当噪声方差已知且均匀时传参后恢复效果更稳当噪声是空间变化时反而会让局部方差估计更合理这是维纳滤波比固定模板滤波真正自适应的地方。wthrmngr是小波阈值规则管理器sqtwolog是通用阈值适合噪声强度未知但有较多系数的场景噪声较弱时可以换rigrsure它基于 Stein 无偏风险估计PSNR 通常更好minimaxi在两者之间取折中。软阈值wthresh(c, s, thr)会把大幅值系数也向零收缩硬阈值h则保留原值视觉效果更锐利但容易放大局部抖动。对小波基的选择db4计算量小sym8对称性更好多层分解后振铃更少。2.2 传统算法的适用边界与参数调整算法核心参数参数加大后的现象主要适用噪声均值滤波邻域窗口 n边缘模糊加重高斯、均匀噪声中值滤波窗口 n细节丢失、灰阶平台化椒盐噪声维纳滤波局部窗口 [m n]过度平滑、块状伪影高斯、混合噪声小波阈值分解层数、阈值规则振铃、细节稀疏化高斯、低信噪比当测试图像边缘密集时均值滤波的 PSNR 会开始下降而中值滤波在低密度椒盐噪声下表现稳定一旦椒盐比例超过 0.3窗口内干净像素点不足排序统计失去意义。维纳滤波的块状伪影通常来自窗口尺寸与图像纹理尺度不匹配纹理尺度小但窗口开得大局部均值就把纹理磨掉了反过来窗口小方差估计不稳定。小波阈值最值得调试的是分解层数。三层是常见起点层数加到五层以上时低频近似的尺寸变化不大但高频细节被反复分拆软阈值收缩量叠加容易出现横向振铃条纹。另一个容易忽略的点是wavedec2对小波基的边界扩展方式默认是周期扩展图像边缘不连续时会在小波系数两端产生大值伪影这些伪影经过阈值后仍保留。传统方法的调参几乎都是在「保留边缘」和「去除噪声」之间挪动没有哪个参数能同时解决两者。提示在传统算法和 DnCNN 做对比时传统方法不适合用同一组参数跑全部噪声强度。我一般会把 σ噪声标准差除以 255分成 5、15、25、50 四档每档单独调一次传统算法的窗口或阈值规则再与深度网络结果比较这样对 DnCNN 才算公平。3. DnCNN 网络结构、残差学习与 MATLAB 训练实现DnCNN 的核心思路是「用深度卷积神经网络学习噪声的分布」而不是直接学习干净图像。这个选择改变了训练数据构造方式、损失函数写法以及在 MATLAB 里搭建网络时的输出层设定。3.1 DnCNN 为什么预测噪声残差而不是直接输出干净图设带噪图像 Y X N其中 X 是干净图像N 是加性噪声。传统去噪算法直接估计 X而 DnCNN 让网络计算 F(Y) ≈ N推理时得到 X ≈ Y − F(Y)。这个修改在 MATLAB 里影响的是网络最后一层输出和训练响应网络本身并不复杂。残差学习的好处有两层。第一噪声图像 N 的能量远低于干净图像 X 的能量网络不需要重新学习从输入到输出的恒等映射深度加深后梯度更容易保持第二如果训练时噪声均值接近零残差图像近似零均值随机场网络最后一层可以输出负值而不会破坏图像结构。这就是为什么 DnCNN 的最后一层卷积之后不接 ReLU也不接回归层直接输出残差图。建筑基块由 3×3 卷积、批量归一化层 BatchNorm 和 ReLU 组成深度通常取 17 层左右这部分在 Deep Learning Toolbox 里可以逐层搭出来。3.2 用 Deep Learning Toolbox 搭建 DnCNN 并准备成对补丁3.2.1 生成 DnCNN 风格的 layerGraphfunction lgraph dncnnLayerGraph(hiddenLayers, filters, inputSize) % 生成 DnCNN 风格网络的 layerGraph % hiddenLayers: 中间卷积层数量不包含最后的输出卷积层常见取 16 % filters: 每层卷积核数量常用 64 % inputSize: [H W C]例如 [128 128 1] 表示 128x128 灰度图 layers imageInputLayer(inputSize, Name, input, ... Normalization, none); % 不做输入归一化 layers [layers; convolution2dLayer(3, filters, Padding, same, ... WeightsInitializer, he, Name, conv_input)]; layers [layers; reluLayer(Name, relu_input)]; for k 1:hiddenLayers - 1 layers [layers; convolution2dLayer(3, filters, Padding, same, ... WeightsInitializer, he, Name, [conv_ num2str(k)])]; layers [layers; batchNormalizationLayer(Name, [bn_ num2str(k)])]; layers [layers; reluLayer(Name, [relu_ num2str(k)])]; end layers [layers; convolution2dLayer(3, inputSize(3), Padding, same, ... WeightsInitializer, he, Name, conv_output)]; lgraph layerGraph(layers); end参数说明imageInputLayer里的Normalization, none很关键默认的zerocenter会减去训练数据均值而 DnCNN 的残差学习依赖输入像素的绝对数值归一化后反而会让残差统计失真。Padding, same保证每层卷积输出尺寸不变这样输入输出图像大小一致。he初始化适合后面接 ReLU 的网络比glorot更容易在训练初期保持激活方差。hiddenLayers取 16 时加上输入卷积和输出卷积整个网络共 18 层卷积灰度图上的参数量大约 60 万单张消费级 GPU 在视频训练数据上十几分钟能完成一轮。3.2.2 准备训练数据与训练选项DnCNN 训练时不能直接把整张图扔进去常见做法是随机裁剪成 128×128 的小块并在每个小块上独立添加随机强度的噪声这样相当于把数据增强和噪声建模合并在一起。% 用 imageDatastore transform 生成成对的带噪/残差补丁 imds imageDatastore(cleanImgFolder, FileExtensions, .png, ... ReadFcn, (x) im2double(imread(x))); dsTrain transform(imds, (x) makeNoisyPair(x, [5 25], [128 128])); function out makeNoisyPair(img, sigmaRange, patchSize) % 随机裁剪 patch h randi([0, size(img, 1) - patchSize(1)]); w randi([0, size(img, 2) - patchSize(2)]); cleanPatch img(h1:hpatchSize(1), w1:wpatchSize(2), :); % 从 [5,25] 中随机取一个噪声标准差 sigma sigmaRange(1) rand * diff(sigmaRange); noisyPatch cleanPatch sigma / 255 * randn(size(cleanPatch)); % trainNetwork 回归任务要求输出 {输入, 响应} out {noisyPatch, noisyPatch - cleanPatch}; % 响应是噪声残差 end代码说明响应是noisyPatch - cleanPatch而不是cleanPatch这对应 DnCNN 的残差学习目标。如果训练时响应写成干净图网络就变成了普通卷积回归去噪模型BatchNorm 对残差的作用会被稀释。随机裁剪不仅增加样本量还让网络在不同图像结构上都能看到噪声避免过拟合到固定尺寸。opts trainingOptions(adam, ... InitialLearnRate, 1e-3, ... MiniBatchSize, 32, ... MaxEpochs, 30, ... L2Regularization, 1e-4, ... Shuffle, every-epoch, ... Plots, training-progress, ... ExecutionEnvironment, auto); net trainNetwork(dsTrain, lgraph, opts);这里的ExecutionEnvironment设为auto有 GPU 时自动用 GPU没有 GPU 时退回 CPU。CPU 上训练 30 个 epoch 会比较慢建议把MaxEpochs降到 10或者把 patch 尺寸改到 96×96。3.3 训练 DnCNN 的关键参数速查参数建议值调整说明InitialLearnRate1e-3梯度出现 NaN 时降到 1e-4MiniBatchSize32显存不足时降到 16但 BatchNorm 统计会抖动MaxEpochs20~50小数据集 30 以内验证集 PSNR 不再上升即可停噪声σ范围[5, 25]覆盖绝大多数真实噪声场景训练和测试σ应尽量重叠PatchSize128过小会导致 BatchNorm 批内样本不充分L2Regularization1e-4防止深网络过拟合到训练噪声分布训练完成后推理代码并不复杂noisy im2double(imread(test.png)); noisy4 reshape(noisy, size(noisy, 1), size(noisy, 2), 1, 1); residual predict(net, noisy4); denoised noisys - squeeze(residual);注意DnCNN 的输出层没有激活函数所以它允许输出负数。测试时如果去噪图出现大面积暗斑先检查是否在最后一层误加了 ReLU如果残差图整体均值明显偏离 0说明训练响应的构造有误。4. 传统图像去噪与 DnCNN 的实验对比PSNR、SSIM 与运行耗时对比实验最容易出问题的地方不是算法本身而是评价指标的使用方式。PSNR 和 SSIM 在 MATLAB 里都是现成函数但值域不统一会直接得出虚假的高分结果。4.1 用 MATLAB 自带的 psnr、ssim 计算评价指标function [p, s] evalMetrics(denoised, clean) % denoised 和 clean 必须同为 double且值域都在 [0,1] p psnr(denoised, clean, 1); % peakval 必须显式传 1 s ssim(denoised, clean, DynamicRange, 1); % 指定动态范围 end % 计算运行耗时timeit 会执行多次取中位数比 tic/toc 稳定 f () dncnnDenoise(noisyImg); elapsed timeit(f);参数说明psnr函数默认把第三参数 peakval 当作 255如果输入已经是 [0,1] 的 double 而不传峰值算出来的 PSNR 会普遍虚高约 10 dB。ssim的DynamicRange参数同理。这是对比脚本里最常见的坑尤其当代码中混用im2double和uint8时最容易现。timeit 要求被测量函数不需要输入参数所以用匿名函数把 noyisyImg 和 net 捕获进去如果测量对象是批处理脚本直接用ts tic; ...; toc(ts)更直观。4.2 完整对比脚本与结果解读sigs [5 15 25 50]; clean im2double(imread(cameraman.tif)); for i 1:numel(sigs) noisy clean sigs(i) / 255 * randn(size(clean)); % 均值滤波 avg imfilter(noisy, fspecial(average, [5 5]), replicate); % 中值滤波 med medfilt2(noisy, [5 5]); % 维纳滤波 win wiener2(noisy, [5 5]); % 小波阈值 [c, s] wavedec2(noisy, 3, db4); cT wthresh(c, s, wthrmngr(dw2ddenoLVL, sqtwolog, c, s)); wav waverec2(cT, s, db4); % DnCNN 残差预测 dnc noisy - squeeze(predict(net, reshape(noisy, ... size(noisy,1), size(noisy,2), 1, 1))); results(i, :) table(sigs(i), ... psnr(avg, clean, 1), ssim(avg, clean, DynamicRange, 1), ... psnr(med, clean, 1), ssim(med, clean, DynamicRange, 1), ... psnr(win, clean, 1), ssim(win, clean, DynamicRange, 1), ... psnr(wav, clean, 1), ssim(wav, clean, DynamicRange, 1), ... psnr(dnc, clean, 1), ssim(dnc, clean, DynamicRange, 1)); end噪声σ更适合的传统方法是否值得切换 DnCNN5~10维纳滤波不值得DnCNN 提升有限且训练成本高15~25小波阈值值得DnCNN 在高噪区的边缘保持更明显30~50几乎无合适选项值得DnCNN 的优势显著传统方法已严重失真这只是工程经验判断实际差异受图像内容影响很大。对于纹理密集的遥感图或者医学影像DnCNN 在 σ15 时就能拉开 2~3 dB 差距对于大片平滑区域占比高的图像传统维纳滤波反而可能更稳因为深度网络会把纹理模式误当成噪声去掉。PSNR 反映像素级误差SSIM 反映结构相似性两者趋势通常一致但偶尔会出现 PSNR 高、SSIM 低的情况说明去噪图在边缘上出现了移位或伪结构这时应当以 SSIM 为准继续调参。4.3 对比测试中的三个常见坑第一值域不统一。整张图像如果一半是 double [0,1]一半是 uint8 [0,255]计算 PSNR 前必须统一到 [0,1] 并显式传 peakval。第二训练噪声强度和测试噪声强度不一致。DnCNN 在 σ25 上训练却拿 σ50 的测试图去推断残差预测会出现系统性偏差输出图像反而比传统算法更脏。解决方法是把训练σ范围扩展到测试场景或者推理前先估计当前图像的噪声等级再匹配到最近的训练场景。第三边界效应。imfilter默认对边界补零统计指标会把黑色边框算进去导致边缘像素误差被虚假放大DnCNN 的卷积层虽然用Padding, same但深层感受野跨出图像后也会产生边界伪影。对比时最好统一裁掉图像四周 10~15 个像素再计算 PSNR 和 SSIM这样对传统算法和深度网络都公平。5. 用噪声自适应与批处理把 MATLAB 去噪脚本变成可复用工具实验做通之后下一步是把它从交互式脚本变成可以自动处理一批图片的工具。落到实际场景里核心工作是三件事先估噪声等级再决定调用哪条路径最后验证模型没有跑偏。5.1 用噪声级估计自动选择传统方法还是 DnCNNfunction sigmaHat estimateNoiseLevel(y) % 用拉普拉斯算子提取高频细节稳健估计高斯噪声标准差 lap [0 -1 0; -1 4 -1; 0 -1 0]; detail imfilter(y, lap, replicate); sigmaHat median(abs(detail(:))) / 0.6745; % 0.6745 是标准正态分布中位数 end原理真实图像的高频细节在拉普拉斯响应中占比不大而高斯噪声在逐点变换后近似服从正态分布因此中位数再除以 0.6745 能估计出噪声标准差。阈值选择一般这样判断σ 低于 15 时维纳滤波足够σ 在 15 到 30 之间用小波阈值σ 高于 30 再调 DnCNN。function out smartDenoise(y, net) sigma estimateNoiseLevel(y); if sigma 15 out wiener2(y, [5 5]); elseif sigma 30 [c, s] wavedec2(y, 3, sym8); cT wthresh(c, s, wthrmngr(dw2ddenoLVL, minimaxi, c, s)); out waverec2(cT, s, sym8); else residual predict(net, reshape(y, size(y,1), size(y,2), 1, 1)); out y - squeeze(residual); end end5.2 批量处理文件夹中图像的脚本框架srcDir D:\project\noise_imgs; dstDir D:\project\denoised; if ~exist(dstDir, dir), mkdir(dstDir); end files dir(fullfile(srcDir, *.png)); for k 1:numel(files) im im2double(imread(fullfile(files(k).folder, files(k).name))); out smartDenoise(im, net); imwrite(out, fullfile(dstDir, files(k).name)); end脚本说明smartDenoise每次会先运行estimateNoiseLevel再决定走哪条分支这样不需要为每张图手动调参。如果图片总量很大把 for 换成 parfor 可以并行但net会作为广播变量复制到每个工作进程内存占用会上涨8 核以下的机器建议先用普通 for。5.3 验证训练后的 DnCNN 是否真正收敛% 在验证集上检查残差的统计特性 valResidual valNoisy - valDenoised; % valNoisy 是网络输入valDenoised 是输出 meanRes mean(valResidual(:)); stdRes std(valResidual(:)); fprintf(残差均值 %.4f标准差 %.4f\n, meanRes, stdRes); % 期望均值接近 0标准差接近注入的 sigma/255这个验证思路不依赖 PSNR直接看网络学到了什么。如果残差均值偏离 0 太远说明网络把一部分图像结构也当成了噪声如果标准差远小于注入的σ说明网络欠拟合需要增大 epoch 或提高学习率。配合训练时的training-progress曲线可以确认 loss 下降不是靠 LR 衰减假象撑起来的——曲线平滑下降并最终平稳比单点 PSNR 更有说服力。最后把验证集上收敛时对应的 epoch 记为最终训练轮数后续再训练同任务数据时直接沿用。本文还有配套的精品资源点击获取