☰
MATLAB图像去噪实战:椒盐与高斯噪声滤波对比
2026/10/17 3:33:21 网站建设 项目流程

数字图像处理这门课,很多人第一次真正动手写代码,就是从给图像加噪声、再想办法把噪声滤掉开始的。我当年做课程设计的时候,助教扔过来一句话:“自己生成一幅灰度图,加椒盐和高斯,再用几种滤波器对比一下。”听起来简单,但真做起来,参数怎么选、滤波器窗口开多大、为什么中值滤波对椒盐效果好却对高斯一般,这些问题不搞清楚,出来的结果图要么糊成一团,要么噪声纹丝不动。这篇就围绕 MATLAB 实现椒盐噪声、高斯噪声以及对应的滤波处理,把整个流程从原理到代码到踩坑经验完整讲一遍。适合正在做图像处理课程设计、毕业设计,或者刚接触 MATLAB 图像工具箱、想搞明白“加噪—滤波”这条链路到底怎么回事的朋友。读完你至少能自己搭出一套可复现的对比实验,知道每种滤波器的脾气,而不是对着别人的代码抄一遍交差。

1. 整体设计思路与方案选型

1.1 为什么选 MATLAB 而不是别的工具

做图像处理的实验平台选择其实不少,Python 加 OpenCV、NumPy 也能干,甚至用 C++ 手写卷积核更能锻炼人。但课程场景下 MATLAB 有几个绕不开的优势。第一是它的矩阵语义天然贴合图像——一幅灰度图在 MATLAB 里就是一个二维矩阵,加噪声、做卷积、看直方图,全是矩阵运算,代码短到离谱。第二是图像处理工具箱里现成的函数密度极高,imnoise、medfilt2、wiener2、fspecial这些函数拿来就能用,省去了自己造轮子的时间,能把精力放在理解算法本身。第三是可视化方便,subplot一摆,原图、加噪图、各滤波结果并排显示,对比一目了然,写报告的时候截图直接能用。

当然 MATLAB 也有代价,它是商业软件,正版授权不便宜,学生一般靠学校提供的校园授权。如果你手头没有授权,Octave 是个兼容度不错的替代品,大部分基础函数能跑,但工具箱函数覆盖不全,wiener2这类可能就得自己实现。我个人的建议是:课程实验优先用 MATLAB,把算法逻辑吃透;如果要做工程落地或者部署,再考虑迁移到 Python 生态。

1.2 噪声模型的选择逻辑

图像噪声的种类很多,但教学和实验里最常出现的就是椒盐噪声和高斯噪声这一对。这不是随便挑的,它们代表了两种截然不同的噪声性质,正好用来对比不同滤波器的适用边界。

椒盐噪声,英文叫 salt-and-pepper noise,也叫脉冲噪声。它的表现是图像里随机冒出一些纯黑(胡椒,像素值 0)和纯白(盐,像素值 255)的点。注意,这些点的值是被“替换”掉的,不是叠加上的。它的数学描述通常用两个概率参数,一个控制变成白点的概率,一个控制变成黑点的概率,两者之和就是总的噪声密度。椒盐噪声的典型来源是传感器坏点、传输过程中的突发错误、模数转换的瞬时干扰。

高斯噪声则完全不同,它是叠加性的。每个像素上叠加一个服从正态分布的随机值,均值通常取 0,标准差控制噪声强度。因为正态分布的尾巴会延伸到正负两侧,所以高斯噪声让整幅图看起来像蒙了一层细密的颗粒,每个像素都轻微偏离了真实值。它的来源多是传感器热噪声、低光照条件下的电子噪声。

把这两种噪声放在一起做实验,核心目的就是让你亲眼看到:针对脉冲型噪声,非线性滤波器(中值)碾压线性滤波器;针对高斯型噪声,线性滤波器(均值、高斯)反而更合适。这个结论光背是记不住的,必须自己跑一遍图才能形成直觉。

1.3 滤波方案的对比框架

滤波这块,实验里一般会摆上这么几个选手:均值滤波、中值滤波、高斯滤波,有时候还会加上维纳滤波作为进阶对比。它们的设计哲学不一样。

均值滤波是最朴素的线性滤波,用一个窗口(比如 3×3)滑过图像,每个位置取窗口内所有像素的平均值作为输出。它的本质是低通滤波,把高频的噪声和边缘一起抹平。优点是简单、快,缺点是把边缘也糊掉了,图像变模糊。

中值滤波是典型的非线性滤波,同样用窗口滑动,但输出取窗口内像素的中位数,而不是平均值。这个“取中位数”的操作对脉冲噪声有奇效——因为椒盐噪声的极值点(0 或 255)在排序后会被排到两端,中位数自然把它们排除掉了。所以中值滤波去椒盐几乎是标配。

高斯滤波是加权版的均值滤波,窗口内不同位置的权重不一样,中心权重大、边缘权重小,权重分布服从二维高斯函数。它比均值滤波更“温柔”,在平滑噪声的同时对边缘的破坏小一些,但对椒盐这种极值噪声基本无能为力,因为极值点依然会被加权平均进去。

维纳滤波是自适应滤波,它试图在频域上根据信噪比做最优估计,对高斯噪声效果不错,但计算复杂,且需要估计噪声功率谱,实验里一般作为选做内容。

2. 核心原理与关键参数解析

2.1 椒盐噪声的生成机制与密度控制

在 MATLAB 里生成椒盐噪声,最直接的方式是调用imnoise(I, 'salt & pepper', d),其中d是噪声密度,取值范围 0 到 1。比如d = 0.05表示大约 5% 的像素会被污染。这个函数内部的处理逻辑是:对每个像素生成一个均匀随机数,如果落在某个区间就置为 0 或 255。

但如果你想更精细地控制黑白点的比例,或者想理解底层逻辑,可以自己写。思路是这样:先生成一个和图像同尺寸的随机矩阵R = rand(M, N),然后设定两个阈值。比如要让 2% 的像素变白、3% 变黑,可以这样操作——R < 0.02的位置置 255,R >= 0.02 & R < 0.05的位置置 0。这样黑白比例就是可控的。

这里有个容易忽略的细节:imnoise的'salt & pepper'模式默认黑白概率相等,各占d/2。如果你做实验时发现黑白点数量看起来不对称,先检查是不是自己手动生成时阈值设偏了。另外,噪声密度这个参数对结果影响极大,0.01 的时候图像只是零星几个点,0.1 的时候已经惨不忍睹,做对比实验一般取 0.02 到 0.05 之间比较合适,既能看出效果又不至于完全破坏图像结构。

2.2 高斯噪声的均值方差与叠加方式

高斯噪声用imnoise(I, 'gaussian', m, v)生成,m是均值,v是方差。注意这里是方差不是标准差,很多人第一次用会搞混。如果你想要标准差为 0.05 的噪声(注意imnoise处理的是归一化到 [0,1] 的 double 图像),那方差应该是 0.05 的平方,即 0.0025。

高斯噪声是叠加性的,imnoise内部会把正态随机值加到原像素上,然后做截断(clip)到 [0,1] 范围。这个截断操作会带来一个副作用:当噪声方差较大时,原本接近 0 或 1 的像素被截断,导致实际分布偏离理想正态。做定量分析(比如算 PSNR)的时候要意识到这一点。

自己实现的话,用randn生成标准正态分布,再乘以标准差加上均值即可。比如noisy = I + sigma * randn(size(I)),然后noisy = min(max(noisy, 0), 1)做截断。这种手动方式的好处是你能完全掌控噪声矩阵,方便复现实验——只要固定随机种子rng(42),每次跑出来的噪声都一样,写报告时数据可复现。

2.3 滤波器窗口大小对结果的影响

窗口大小是滤波实验里最关键的参数,没有之一。3×3、5×5、7×7 的窗口,效果差异非常明显。

窗口越大,平滑能力越强,噪声去除越彻底,但代价是图像细节丢失越严重。3×3 的均值滤波对轻度噪声够用,边缘还能保住一些;5×5 就开始明显模糊了;7×7 基本就是把图像糊成水彩画。中值滤波也是同理,窗口越大去噪越狠,但细小结构(比如文字、细线)会被吃掉。

这里有个经验规律:窗口大小一般取奇数,因为要有一个明确的中心像素。3 是最小实用值,再小(比如 2×2)就没有中心点了,处理起来边界很麻烦。实际实验中,我建议至少跑 3×3 和 5×5 两组,对比着看,你才能体会到“去噪”和“保边”之间的权衡不是一句空话。

还有一个边界处理的问题。滤波窗口滑到图像边缘时,窗口会超出图像范围。MATLAB 的imfilter默认用 0 填充边界,这会导致边缘出现暗边。更好的做法是用'replicate'(复制边缘像素)或'symmetric'(镜像填充)。medfilt2默认也是 0 填充,可以通过参数指定。这个细节在实验报告里经常被忽略,但实际看图的时候边缘那一圈明显不对,就是边界填充没处理好。

3. 完整实操流程与代码实现

3.1 实验环境准备与图像读取

先把环境理清楚。MATLAB 版本建议 R2016b 以上,图像处理工具箱是必须的。检查工具箱是否安装,可以在命令行敲ver看列表里有没有 Image Processing Toolbox。没有的话,imnoise、medfilt2、fspecial这些函数都用不了,得换 Octave 或者自己实现。

读图像用imread,但要注意读进来的类型。彩色图是三维矩阵,做噪声实验一般先转灰度:I = rgb2gray(imread('xxx.jpg'))。转完之后是 uint8 类型,值域 0 到 255。而imnoise对 uint8 和 double 的处理方式不同——对 uint8,它内部会先归一化再处理;对 double,它直接按 [0,1] 处理。为了避免混淆,我习惯统一转成 double 并归一化:I = im2double(I)。这样后续所有操作都在 [0,1] 范围内,参数设置直观,也不会有类型转换的坑。

选图也有讲究。做噪声实验最好选一张有丰富细节、同时有大片平滑区域的图,比如标准测试图里的 cameraman、lena 这类。细节区域用来看滤波对边缘的破坏,平滑区域用来看噪声去除效果。如果随便拿一张纯色图,加完噪声滤波完看不出差别,实验就白做了。

3.2 椒盐噪声的添加与可视化

代码部分我直接给一套可跑的。先读图、转灰度、归一化:

rng(42); % 固定随机种子,保证可复现 I = im2double(rgb2gray(imread('test.jpg')));

然后加椒盐噪声,密度取 0.05:

I_sp = imnoise(I, 'salt & pepper', 0.05);

如果你想手动控制黑白比例,可以这样:

d_white = 0.025; d_black = 0.025; R = rand(size(I)); I_sp = I; I_sp(R < d_white) = 1; I_sp(R >= d_white & R < d_white + d_black) = 0;

可视化用subplot并排显示:

figure; subplot(1,2,1); imshow(I); title('原图'); subplot(1,2,2); imshow(I_sp); title('椒盐噪声 密度0.05');

跑完你会看到图上散布着黑白点。如果密度设成 0.2,基本就面目全非了。这里提醒一句,imshow对 double 类型默认按 [0,1] 显示,如果你不小心传了 uint8 的图进去,可能显示全白或全黑,检查数据类型是排查显示问题的第一步。

3.3 高斯噪声的添加与参数调节

高斯噪声类似:

I_g = imnoise(I, 'gaussian', 0, 0.01);

这里均值 0、方差 0.01,对应标准差 0.1。这个强度已经比较明显了,图像看起来像蒙了一层沙。如果想温和一点,方差取 0.005 甚至 0.001。

手动实现版本:

sigma = 0.1; I_g = I + sigma * randn(size(I)); I_g = min(max(I_g, 0), 1);

对比imnoise和手动版本,你会发现结果不完全一样,原因就是imnoise内部对 uint8 和 double 的处理路径不同,以及截断时机的差异。做定量对比实验时,建议统一用一种方式,别混着来。

3.4 三种滤波器的实现与对比

均值滤波:

h_mean = fspecial('average', 3); I_mean = imfilter(I_sp, h_mean, 'replicate');

中值滤波:

I_median = medfilt2(I_sp, [3 3]);

高斯滤波:

h_gauss = fspecial('gaussian', [3 3], 0.5); I_gauss = imfilter(I_g, h_gauss, 'replicate');

注意fspecial('gaussian', [3 3], 0.5)里的 0.5 是标准差,不是方差。这个参数控制高斯核的“胖瘦”,标准差越大,核越平缓,平滑效果越强。一般取 0.5 到 1.5 之间。

把原图、加噪图、三种滤波结果摆成 2×3 的 subplot,一眼就能看出差异。椒盐噪声下,中值滤波结果明显干净且边缘保留好,均值和高斯滤波则留下了一片模糊的灰斑——因为极值被平均进去了。高斯噪声下,均值和高斯滤波都能有效平滑,中值滤波效果一般,因为它只是取中位数,对连续分布的噪声没有特别的压制作用。

3.5 定量评价指标的计算

光看图不够,得有数字。最常用的两个指标是 PSNR(峰值信噪比)和 SSIM(结构相似性)。

PSNR 计算:

function p = psnr_calc(A, B) mse = mean((A(:) - B(:)).^2); p = 10 * log10(1 / mse); end

注意这里假设图像归一化到 [0,1],所以峰值是 1。如果图像是 uint8,峰值是 255,公式要相应调整。PSNR 越高说明失真越小,一般 30dB 以上算不错,20dB 以下就明显能看出问题了。

SSIM 用 MATLAB 自带的ssim函数(需要工具箱支持):

s = ssim(I_median, I);

SSIM 越接近 1 越好,它考虑了亮度、对比度和结构三个维度,比 PSNR 更贴近人眼感受。

把不同滤波器、不同窗口大小、不同噪声密度下的 PSNR 和 SSIM 列成表格,你的实验报告就有了扎实的数据支撑。我当年就是靠这张表,把“中值滤波去椒盐最优”这个结论从定性变成了定量。

4. 常见问题与排查技巧实录

4.1 图像显示全黑或全白怎么办

这是新手最高频的问题。九成以上的原因是数据类型和显示函数的匹配问题。imshow对 double 类型默认按 [0,1] 显示,对 uint8 按 [0,255] 显示。如果你把一个值域在 [0,255] 的 double 矩阵传给imshow,超过 1 的部分全被当成白色,结果就是一片白。反过来,uint8 的图如果被错误地归一化过,可能全黑。

排查步骤很简单:在imshow前加一句whos或者max(I(:))、min(I(:)),看清楚数据类型和值域。统一转成 double 并归一化到 [0,1] 是最省心的做法。

4.2 滤波后图像边缘出现暗边

这是边界填充方式导致的。imfilter默认用 0 填充,窗口滑到边缘时,外面补的 0 被平均进去,边缘就变暗了。解决办法是加参数'replicate'或'symmetric'。medfilt2也有类似问题,可以通过'symmetric'参数指定填充方式。

这个细节在实验报告里值得单独提一句,因为它体现了你对边界条件的理解,不是只会调函数。

4.3 噪声密度和方差到底怎么选

这个问题没有标准答案,取决于你的实验目的。如果是为了展示滤波器的效果差异,噪声要足够明显但不能完全破坏图像。我的经验值:椒盐密度 0.02 到 0.05,高斯方差 0.005 到 0.02。低于这个范围,滤波前后差别不明显;高于这个范围,图像结构被破坏太严重,PSNR 低到没有比较意义。

另外,做对比实验时,噪声强度要固定。不要一个滤波器用 0.02 的噪声,另一个用 0.05,那样比出来的结果没有说服力。

4.4 中值滤波窗口开多大合适

3×3 是默认起点,对大多数轻度椒盐噪声够用。如果噪声密度超过 0.1,3×3 可能去不干净,需要上 5×5。但 5×5 对细节的破坏也明显,尤其是文字、细线这类结构。折中方案是先用 3×3,看残留噪声量,如果还有明显黑白点再考虑加大窗口,或者用自适应中值滤波(可以根据局部噪声情况动态调整窗口)。

自适应中值滤波实现起来复杂一些,但效果确实好,适合作为进阶内容写进报告。

4.5 PSNR 数值异常怎么排查

PSNR 算出负数或者特别大,通常是这几个原因:一是图像值域没统一,一个 [0,1] 一个 [0,255],MSE 算出来差了几个数量级;二是图像尺寸不匹配,A(:) - B(:)直接报错或者广播出意外结果;三是除零问题,如果两幅图完全一样,MSE 为 0,PSNR 趋于无穷,代码里要加个判断。

排查的时候先把两幅图的size、class、max、min都打印出来,基本一眼就能定位。

问题现象可能原因排查方法
图像全黑/全白数据类型与显示范围不匹配检查 class 和值域,统一归一化
边缘暗边边界填充为 0改用 replicate 或 symmetric
滤波后噪声仍在窗口太小或噪声太强加大窗口或换自适应方法
PSNR 异常值域不统一或尺寸不匹配打印 size、class、max、min
结果不可复现未固定随机种子开头加 rng(固定值)

4.6 实验可复现性的保障

最后强调一个容易被忽视但极其重要的点:固定随机种子。rng(42)这行代码看着不起眼,但它决定了你每次跑出来的噪声是不是一样。写报告、做对比、给别人复现你的结果,都依赖这一点。我见过太多人跑完实验截图交报告,结果老师让他再跑一遍,出来的图完全不一样,因为没固定种子。

另外,把关键参数(噪声密度、方差、窗口大小、高斯核标准差)都定义成变量放在脚本开头,而不是散落在各处。这样调参的时候改一处就行,也方便别人看懂你的实验设置。

整套流程跑下来,从读图、加噪、滤波到定量评价,代码量其实不大,核心就那么几十行。但要把每个参数为什么这么设、每种滤波器为什么有这样的表现讲清楚,需要你对背后的原理有真正的理解。我建议你别只跑一遍标准流程就完事,试着改改噪声密度、换换窗口大小、对比不同边界填充方式,看看结果怎么变。这种“折腾”的过程,比看十篇教程都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询