☰
均值滤波与中值滤波全解析:原理、MATLAB实现与参数选择
2026/10/17 1:28:41 网站建设 项目流程

做图像处理的人,入门时最先接触的滤波多半就是均值滤波和中值滤波。这两个名字听起来简单,但我在实际工程项目里发现,很多人跑通代码之后只是看到图片变模糊了,并不清楚它们各自擅长处理什么噪声,也不清楚窗口大小、边界策略、数据类型这些细节为什么会直接影响结果。这篇博文我打算把均值滤波和中值滤波的原理、代码、参数选择和踩坑经历完整梳理一遍,直接给出可在MATLAB里跑的代码,同时解释每一步背后的逻辑,让你看完不只是会调用函数,还能在遇到具体图像时自己判断该用哪个。

1. 从一张带噪声的图说起:为什么要做滤波

1.1 噪声长什么样,为什么会破坏图像质量

图像噪声本质上就是像素值在真实场景亮度之外产生的随机波动。最常见的有高斯噪声和椒盐噪声:高斯噪声是每一点都加了一个服从正态分布的随机误差,整张图看起来像蒙了一层细小的雪花;椒盐噪声则是随机出现纯白或纯黑的像素点,图像上像撒了盐和胡椒粉。不管是哪种噪声,都会让图像的边缘变毛糙、细节被干扰,后续做边缘检测、特征提取、目标识别时,噪声会把算法结果带偏。我做过一个模拟项目X,前期因为噪声没处理好,边缘检测出来的轮廓全是断点,后来才意识到预处理步骤里滤波这块做得多草率,结果差得是数量级的差别。

1.2 滤波的本质:在像素邻居里找答案

滤波的本质可以理解成:当你怀疑某个像素的值被噪声污染了,你该怎么修正它?最自然的思路就是看一下它周围的邻居值,用邻居的信息来推断这个位置本该是什么值。均值滤波的逻辑是“我拿邻居的平均值来替代当前值”;中值滤波的逻辑是“我把邻居值排序后取中间那个数来替代当前值”。这两种思路都建立在同一个假设上:一幅真实图像里,相邻像素的灰度值通常变化不大,局部区域是相对平滑的,噪声只是局部区域里的离群点。

这个思想在数学上对应的是空间滤波——用一个小窗口(通常叫模板、掩膜或卷积核)滑过整幅图像,在每个像素位置做一次局部运算。窗口大小直接决定了参与运算的邻居范围:窗口越小,保留细节越多,去噪能力越弱;窗口越大,去噪越强,但细节也丢得越多。这个尺度关系是滤波里最核心的权衡,后面所有参数选择都绕不开它。

2. 均值滤波:最朴素的空间平滑方案

2.1 均值滤波的数学原理与卷积核

均值滤波属于线性滤波,它的做法是:计算窗口内所有像素灰度的平均值,再把当前像素值替换为该平均值。用3×3窗口举例,权值分布就九个1/9,等价于一个所有元素都为1、再除以9的卷积核。图像处理理论里这叫模板运算,写出来是:

g(x,y) = (1/(m*n)) * sum( f(x+i, y+j) )

其中m、n是窗口的行列数,f是原始图像,g是滤波后的图像。卷积核可以写为K = (1/9) * ones(3),卷积核的大小是奇数(3、5、7等)才能保证有唯一中心点,偶数窗口虽然也能算均值,但没有中心参考,实现起来容易产生半像素偏移,实际工程中很少用。

MATLAB里最直接的方式就是用filter2或者imfilter。filter2是二维相关滤波,imfilter则更适合按卷积方式理解,两者在对称核的处理结果上一致。我习惯用imfilter,因为它的边界选项更丰富,也更容易控制。

2.2 MATLAB代码实现与参数注意事项

先给一个最常规的均值滤波实现:

I = imread('test_image.png'); if size(I,3) == 3 I = rgb2gray(I); end I = im2double(I); % 转成double,避免uint8溢出 % 3x3均值滤波 h = fspecial('average', 3); J_mean = imfilter(I, h, 'replicate'); % 5x5均值滤波 h5 = fspecial('average', 5); J_mean5 = imfilter(I, h5, 'replicate'); figure; subplot(1,3,1); imshow(I); title('Original'); subplot(1,3,2); imshow(J_mean); title('Mean 3x3'); subplot(1,3,3); imshow(J_mean5); title('Mean 5x5');

这里有几个关键点值得反复强调。第一,im2double这一步不要省。uint8图像在做均值计算时,如果直接相加会溢出(255+255=254这种错误在uint8里会发生),MATLAB虽然内部会处理一部分,但自己写卷积核时最容易踩这个坑。第二,imfilter的边界选项默认是零填充,这会导致滤波后图像边缘出现一圈黑边,尤其是噪声大时更明显。我一般用'replicate'(复制边缘像素),它让边界处理看起来自然得多。第三,fspecial('average',N)生成的核是当前工具箱的标准写法,如果你不想用fspecial,直接写h = ones(3)/9; 也是完全一样的。

2.3 均值滤波的优缺点分析

均值滤波最大的优点是简单、计算速度快,线性特性让它在数学上容易分析。但它的问题同样突出:由于是取平均值,它对离群值很敏感——椒盐噪声里的纯白点或纯黑点会把平均值拉偏,导致噪声不仅没去掉,反而在局部区域扩散成灰斑。

更麻烦的是,均值滤波在平滑噪声的同时会模糊边缘。边缘在图像里是灰度突变的位置,平均值会把突变抹平,于是轮廓变糊。窗口越大,边缘越糊。所以均值滤波适合的场景是:图像里噪声比较均匀(如轻微高斯噪声),或者不需要保留特别精细的纹理细节,优先追求平滑和低计算成本。我做某些预处理管线时,会把均值滤波作为下采样前的平滑步骤,因为它能帮助减少锯齿混叠,这时候它比中值滤波更合适。

3. 中值滤波:非线性的鲁棒方案

3.1 中值滤波原理与排序思想

中值滤波属于非线性滤波。它的处理方式是:把窗口内所有像素值排序,去中间那个值作为当前像素的新值。比如3×3窗口内9个值,按从小到大排列后第5个就是中值。

为什么排序取中值比取平均值更抗噪声?假设窗口中心是一个被椒盐噪声污染的纯白点(255),周围8个正常像素的灰度值在100左右,取平均可能变成120左右,确实把白点拉下来了,但还是比真实背景偏亮;而取中值的操作会把255当作离群点,排序后它在最大值那一端,中值依然是周围正常像素中比较典型的值,大约在100附近。这意味着中值滤波可以几乎完全消除孤立的椒盐噪声,同时不会改变边缘两侧的灰度值。这个性质让中值滤波在图像处理中有着不可替代的地位。

3.2 MATLAB代码实现与窗口选择

MATLAB里直接用medfilt2函数即可:

I = imread('test_image.png'); if size(I,3) == 3 I = rgb2gray(I); end % 添加椒盐噪声(可选) I_noise = imnoise(I, 'salt & pepper', 0.05); % 3x3中值滤波 J_median = medfilt2(I_noise, [3 3]); % 5x5中值滤波 J_median5 = medfilt2(I_noise, [5 5]); figure; subplot(2,2,1); imshow(I_noise); title('Noisy'); subplot(2,2,2); imshow(J_median); title('Median 3x3'); subplot(2,2,3); imshow(J_median5); title('Median 5x5');

medfilt2的窗口参数是一个二维向量,[3 3]表示3行3列。窗口形状也可以选择圆形,但默认的正方形在绝大多数情况下够用。窗口越大,对噪声的抑制越彻底,但同时也会抹掉细线、尖角等细节。我在实际项目里有一个经验:对于密度为5%的椒盐噪声,3×3窗口基本能清掉大部分噪声;对于10%以上的强噪声,至少要用5×5窗口,但图像细节会有明显牺牲。如果图像尺寸特别大,还可以考虑更大的窗口,但要注意运行时间会成倍增加。

3.3 为什么中值滤波能扛住椒盐噪声

关键在于中值对“极端值”不敏感。均值是被所有值拉动的,一个255的极端值能对平均值产生1/9的影响,而中值只看排序后位置中间的那个数,极端值只在排序里占一个位置,完全不影响中间位置的取值,除非极端值数量超过窗口内像素数量的一半。这个性质被称为“鲁棒性”,统计学里叫中位数比均值更抗异常值。

但中值滤波也有代价。它不再是一个线性移不变系统,频率响应的分析没那么方便;它处理细线、细纹理时会把宽度小于窗口一半的线给删掉。比如图像里有一根1像素宽的白线,用3×3中值滤波,排序后处于中间位置的很可能是周围的暗色像素,于是白线消失。这是中值滤波最典型的副作用。在需要保留线状结构的图像(比如血管造影、卫星道路提取)里,我会尽量避免直接使用普通中值滤波,而考虑自适应中值滤波。这个后面再说。

4. 一网打尽:完整MATLAB代码与效果对比

4.1 完整测试脚本(含噪声生成与三种滤波对比)

下面给出一段可以直接复制的完整脚本。它做的事情是:读图→转灰度→分别加高斯噪声和椒盐噪声→用均值滤波和中值滤波分别处理→把六张结果放在一张图上对比。这个脚本我用了很多次,稍加修改就能用于批量验证不同窗口尺寸和噪声强度。

%% 图像均值滤波与中值滤波对比脚本 clear; close all; clc; % 读取并预处理图像 I = imread('cameraman.tif'); % 可换你自己的图 if size(I,3) == 3 I = rgb2gray(I); end I = im2double(I); % 添加两种典型噪声 I_gauss = imnoise(I, 'gaussian', 0, 0.01); I_sp = imnoise(I, 'salt & pepper', 0.05); % 均值滤波(3x3) h = fspecial('average', 3); J_mean_gauss = imfilter(I_gauss, h, 'replicate'); J_mean_sp = imfilter(I_sp, h, 'replicate'); % 中值滤波(3x3) J_med_gauss = medfilt2(I_gauss, [3 3]); J_med_sp = medfilt2(I_sp, [3 3]); % 多图展示 figure('Position', [100 100 1100 600]); subplot(2,3,1); imshow(I_gauss); title('高斯噪声'); subplot(2,3,2); imshow(J_mean_gauss); title('高斯+均值滤波'); subplot(2,3,3); imshow(J_med_gauss); title('高斯+中值滤波'); subplot(2,3,4); imshow(I_sp); title('椒盐噪声'); subplot(2,3,5); imshow(J_mean_sp); title('椒盐+均值滤波'); subplot(2,3,6); imshow(J_med_sp); title('椒盐+中值滤波'); % 保存结果(可选) % imwrite(J_med_sp, 'result_median.png');

跑完这个脚本,你大概会看到两个很明显的现象。高斯噪声下,均值滤波和中值滤波的差异没那么悬殊,均值略微平滑得干净一丢丢,但二者都会软化细节;椒盐噪声下,均值滤波会在黑白噪点周围形成灰色的晕影,而中值滤波几乎能把纯黑纯白的颗粒完全清除,图像干净程度肉眼可见地高出一截。这就是我常跟人说的:做实验先不要谈理论,直接跑一次,感受是最直观的。

4.2 量化评价指标:PSNR与SSIM

肉眼主观判断很容易误判,尤其是当你不知道原图长什么样时。所以我在对比滤波效果时一定会加两个量化指标:峰值信噪比(PSNR)和结构相似性(SSIM)。PSNR值越高代表失真越小;SSIM取值范围在0到1之间,越接近1代表结构保留得越好。MATLAB里直接调用:

psnr_mean = psnr(J_mean_sp, I); psnr_med = psnr(J_med_sp, I); ssim_mean = ssim(J_mean_sp, I); ssim_med = ssim(J_med_sp, I); fprintf('PSNR_Mean = %.2f dB, PSNR_Median = %.2f dB\n', psnr_mean, psnr_med); fprintf('SSIM_Mean = %.3f, SSIM_Median = %.3f\n', ssim_mean, ssim_med);

注意PSNR要输入两张灰度图或者两张彩色图,尺寸必须一致。SSIM也是同理。计算前最好确保类型一致,我习惯都用im2double。实测中,对于椒盐噪声密度5%的测试图,中值滤波的PSNR通常会比均值滤波高5到10dB,SSIM高0.1到0.3不等。对于高斯噪声,则取决于具体图像和窗口尺寸,有时均值略胜,有时中值略胜,但整体差别不大。所以结论很明确:确定噪声类型之前,不要盲目选滤波器。

4.3 不同噪声场景下的选择策略

我根据自己的实际经验整理了一个简单粗暴的选择策略表格,放这里供你参考。

噪声类型建议方法窗口尺寸建议注意事项
低强度高斯噪声(σ<0.02)均值滤波 / 高斯滤波3×3先用均值够用,追求更平滑可加大到5×5
高强度高斯噪声均值或高斯滤波5×5 或更大细节损失大,考虑先降噪再锐化
轻度椒盐噪声(密度<0.05)中值滤波3×3基本能完全消除
重度椒盐噪声(密度>0.1)中值滤波5×5 或自适应中值噪声密度过高时普通中值也会失效
混合噪声先中值后均值/高斯3×3 + 3×3分两步处理,避免一次窗口过大

这个表是一个经验起点,具体参数要依据图像尺寸和内容微调。比如1000×1000的大图,3×3和5×5的差异没有在300×300小图上那么显眼,但如果是打印出来做视觉分析,细节差别依然看得出来。我的习惯是:先看直方图和局部放大图判断噪声类型,再选滤波器,最后用PSNR/SSIM选窗口。

5. 实战踩坑记录:常见问题与排查技巧

5.1 边界处理与滤波结果发黑

用imfilter做均值滤波时,默认的边界处理是补零,也就是把图像扩展一圈0。这样滤波后边缘一圈的像素会明显偏暗,形成黑框。特别是当图像边缘原本就很亮或很暗,黑框会特别明显。解决办法是给imfilter提供边界选项,比如'replicate'复制边缘、'symmetric'镜像扩展、'circular'周期扩展。我在纹理图上试过,'replicate'对大多数照片类图像最自然,'symmetric'在周期性纹理上表现更好,'circular'适合无缝拼贴。使用medfilt2时,它默认会自动做边界填充,方式类似复制填充,通常不会出现黑框,但如果你发现边缘有异常,可以手动padarray之后再裁掉。

5.2 数据类型与显示陷阱

我在指导A同学做课程实验时发现,最常见的问题不是代码写错,而是显示出来全白或全黑。原因基本都是数据类型不一致。imnoise输出double型图像,imfilter输出double型,但如果直接用imshow(imfilter(I,h)),imshow会自动把double型数据映射到[0,1]区间显示,OK没问题。但如果你先读了uint8图,没有转double就做运算,uint8算术溢出会导致结果一团糟。另外,如果你保存图像用imwrite,输出double数据会自动截断到[0,1],超过1的像素会变成1(纯白),对比度不够的图就会发白。所以一条铁律:所有滤波操作都在double域做,显示时用imshow,保存前要么转回uint8(乘255后取整),要么直接用im2uint8。

% 从double转uint8的安全写法 I_uint8 = im2uint8(I_double); imwrite(I_uint8, 'output.png');

5.3 性能优化与应对大图

大图滤波最怕的就是时间爆炸。均值滤波是线性运算,可以用可分离卷积加速——把二维卷积拆成两个一维卷积,先按行平滑再按列平滑。MATLAB的imfilter内置了加速策略,3×3窗口一般表现很好,但如果你自己写了嵌套for循环来做均值滤波,那碰上2000×2000的图会慢到怀疑人生。中值滤波没有可分离性,专门的快速中值滤波算法能提高速度,MATLAB的medfilt2已经用了一些优化,但仍比均值慢。对大图来说,我的建议是:

  • 尽可能使用imfilter和medfilt2而不是自写循环;
  • 如果内存不足,分块处理:用blockproc对每个小块滤波,注意块边缘加重叠区(比如窗口半径大小);
  • 如果只是预实验,先缩小图像看效果,确定参数后再跑原图。

另外,如果你真的非要用自写循环,至少把图像转成double后使用单精度single,内存占用减半,速度也会有提升。

5.4 快速速查表

最后把几个常用函数和参数整理成表,方便你随时查:

函数作用常用写法要点
fspecial('average', N)生成均值滤波核h = fspecial('average', 3);N必须为正整数,一般为奇数
imfilter线性滤波J = imfilter(I, h, 'replicate');边界选项很关键,不可省
medfilt2中值滤波J = medfilt2(I, [3 3]);窗口大小用行和列向量
imnoise添加噪声I_n = imnoise(I, 'gaussian', 0, 0.01);产生的是double型
psnr / ssim质量评价p = psnr(J, I); s = ssim(J, I);输入需同尺寸同类型
blockproc分块处理J = blockproc(I, [256 256], @(x) medfilt2(x.data, [3 3]), 'BorderSize', [1 1]);BorderSize要覆盖窗口半径

6. 个人经验与扩展思路

6.1 我在实际项目中怎么组合使用

滤波很少单独用一次就结束,我处理真实图像时通常是一个流程。比如某次要提取零件表面划痕,原图里有细砂噪声,又有少量椒盐颗粒。如果只用中值滤波,划痕细线会被弄断;只用均值滤波,椒盐颗粒会变成一片灰斑污染细节。后来我的做法是先做一次3×3中值滤波清掉颗粒,再做一次3×3高斯滤波(均值滤波的加权变体)压掉细砂噪声,最后用高提升滤波增强划痕对比度。效果比单独用任何一种滤波都好。滤波不是一锤子买卖,组合拳才是日常工程常态。

6.2 更进阶的方向:自适应中值与双边滤波

如果你已经能熟练使用均值和中值滤波,接下来值得学两个更现代的工具:自适应中值滤波和双边滤波。自适应中值会根据当前窗口内像素的灰度分布动态调整窗口尺寸,在去除强化椒盐噪声的同时能更好地保留细节,MATLAB里没有直接函数,但可以自己用padarray和排序写;双边滤波在进行空间平滑时加入了灰度值权重,能同时做到去噪和保边,是均值滤波的“高级版”。这些算法的核心思想仍然是窗口和权重,只不过从固定权重变成了由数据驱动的权重。理解好本篇的基础,进阶时就不会迷路。

6.3 最后一个技巧:先观察再动手

每次拿到新图像,我建议大家先做三件事:看直方图、看局部块、看傅里叶频谱。直方图可以快速判断有没有极端灰度峰值(对应椒盐噪声),局部块放大能直接看到噪声形态,频谱则能区分高低频噪声。这些观察花不了30秒,但能帮你少走半天弯路。滤波选错方案带来的连锁反应,往往比噪声本身更让人头疼。

我自己在调参时习惯把原图、噪声图、滤波图放在同一个figure里,边调边看,顺手记录PSNR和SSIM指标,这样既能看到主观效果,又用客观指标验证判断。图像处理不是玄学,每一笔参数背后都对应着可解释的数学和可验证的实验。希望这篇总结能让你以后看到“均值滤波”和“中值滤波”这两个词时,脑子里浮现的不只是一个函数名,而是一整套选择、实现、验证的方法。

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

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

立即咨询