简介:这是一份面向本硕博及科研人员、用于学习低通滤波与最优陷波滤波图像去噪的MATLAB仿真资源。资源通过Butterworth低通滤波和optimum notch滤波两组可运行脚本,配以不同参数下的去噪效果对比图、频谱图及原图/噪声图等图表,直观展示两类算法的性能差异,适合算法原理验证、课程实验与论文复现。压缩包共84个文件,包含2个m程序文件、70张png结果图、10张jpg对比图、1个avi操作录像和1个txt说明,整体约59.48MB,目录按算法与参数组合分类清晰。其中操作录像覆盖完整仿真流程,txt标注了运行注意事项与版本要求,可帮助初学者快速复现并调整参数。目前已有780人学习使用,适合图像处理方向的本硕博学生与工程师参考。
1. 图像去噪里被低估的频域分界线
拿到一张被周期性纹理污染的照片,先跑一版巴特沃斯低通滤波,结果边缘糊成一片,噪声还在;换成最优陷波滤波,只在噪声频率点上下刀,细节纹丝不动。这个对比在数字图像处理课程设计里几乎每次都会出现,但很少有人把两种方法的频域逻辑讲透。这份资源的核心是两份可直接运行的 Matlab 脚本——Runme1_ButterworthLowPassFilter.m和Runme2_optimumNotchFilter.m,配套的几十张ONFa*b*l*.png结果图完整记录了陷波滤波在不同参数组合下的去噪表现,另外还带操作录像。适合正在做图像去噪课题的本硕博学生,或者想快速评估两种滤波方案在实际图像上差异的工程师。低通滤波是"全局一刀切",陷波滤波是"定点清除",这正是理解频域去噪的分水岭。
2. 低通滤波与最优陷波滤波的理论分岔
2.1 频域里两类噪声的分布差异
图像噪声在频域中的位置决定了滤波策略。高斯白噪声散布在整个频谱,能量没有明显集中点;而周期性噪声(比如扫描仪条纹、传感器工频干扰)的能量集中在几个离散的频率坐标上,在频谱图上表现为对称的亮点。这两类噪声如果都交给低通滤波器处理,结果就是:白噪声被压制的同时,高频细节也被削弱;周期性噪声虽然也被削弱,但低频部分仍然残留,图像整体变得模糊。
这就是为什么需要在频域里做"定点清除"。图 2 中的noise1FT.png和origin1FT.png两张频谱图可以直观看出,加入周期噪声后,频谱上出现了明确的冲击点。对这类噪声,低通滤波器并不是最优选择。最优陷波滤波的思想是:先检测频谱里异常突出的频率点,然后在对应位置构造窄带陷波器,只把噪声频率分量衰减掉,其他频率成分原样保留。
2.2 Butterworth 低通滤波的截止频率取舍
Butterworth 低通滤波器在图像处理中的标准形式为:
% 构建频域滤波器,D0 为截止频率,n 为阶数 [M, N] = size(I); [u, v] = meshgrid(-floor(N/2):floor((N-1)/2), ... -floor(M/2):floor((M-1)/2)); D = sqrt(u.^2 + v.^2); H = 1 ./ (1 + (D ./ D0) .^ (2 * n));代码逻辑不复杂:先对图像做傅里叶变换并中心化,然后构造一个以中心为原点、半径D的频率坐标矩阵,最后按 Butterworth 公式计算每个频率点的增益。D0是截止频率,n控制过渡带的陡峭程度。n越大,通带到阻带的过渡越陡,但也越接近理想低通,振铃效应越明显;D0越小,保留的低频越少,图像越平滑。
在Runme1_ButterworthLowPassFilter.m中,需要重点调节的就是这两个值。常见的做法是先观察频谱图的能量分布,取总能量 90% 对应的半径作为D0的参考值。如果输出图像还有明显条纹,说明噪声频率点距离中心较远,单纯加大D0不会改善,反而会把噪声一起放进来。
2.3 最优陷波滤波的三个核心参数
最优陷波滤波在实现上比 Butterworth 多了一个关键步骤——噪声频率点的定位与抑制。最优陷波器在设计上不只是挖掉一个点,而是围绕噪声频率构造一个椭圆形的凹陷区域,椭圆的半轴长度和方向角决定了陷波的形状。
资源中ONFa*b*l*.png的命名规则直接对应了三组关键参数:
| 参数 | 含义 | 典型取值范围 | 影响 |
|---|---|---|---|
a | 陷波区域主轴半轴长度 | 1~20 | 决定衰减椭圆的长轴半径,过小覆盖不了噪声点,过大损伤邻近细节 |
b | 陷波区域副轴半轴长度 | 1~20 | 决定短轴半径,影响陷波的方向性 |
l | 陷波深度/衰减系数 | 5~55 | 衰减强度,数值越大对噪声频率的抑制越彻底 |
从ONFa20b20l5.png到ONFa20b20l55.png的对比可以看出,l从 5 提升到 55,噪声条纹逐渐消失,但图像整体亮度也会下降。这说明l不是越大越好,它其实是在噪声能量和图像能量之间做一个权衡。a和b的取值则决定了陷波器的选择性和方向性——ONFa1b1l5.png几乎是点状陷波,而ONFa20b20l45.png则是大范围椭圆凹陷。
3. 两份核心脚本的工程实现路径
3.1 主程序入口与子函数的边界
工程里有Runme1_ButterworthLowPassFilter.m和Runme2_optimumNotchFilter.m两个主脚本,文件名中的Runme表明它们是程序入口。很多同学一上来直接双击子函数文件运行,结果报错"未定义函数或变量",原因就是子函数依赖主脚本预先加载的路径和变量。运行时的原则是:当前文件夹窗口必须指向整个工程根目录,只运行Runme开头的脚本,子函数由主脚本自动调用。
% Runme2_optimumNotchFilter.m 主脚本片段 clc; clear; close all; % 读取原始图像 img = imread('origin1.png'); if size(img, 3) == 3 img = rgb2gray(img); end img = im2double(img); % 生成带周期噪声的图像(演示用) noise_img = imnoise(img, 'gaussian', 0, 0.01); % 叠加正弦周期噪声,模拟实际场景中的条纹干扰 [A, B] = meshgrid(1:size(noise_img, 2), 1:size(noise_img, 1)); periodic_noise = 0.1 * sin(2 * pi * A / 30) + 0.08 * cos(2 * pi * B / 45); noisy_img = noise_img + periodic_noise;这段代码做了三件事:读取并灰度化图像、加高斯噪声、再加周期性条纹噪声。im2double把像素值归一化到 0~1,避免后续 FFT 计算出现数值溢出。这里的sin和cos周期设置为 30 和 45 像素,对应频域中两个不同的冲击点位置,实际处理时应该根据频谱图中的亮斑位置反推周期。如果噪声不是纯周期性的,比如是斜向条纹,就需要构造二维正弦波并调整方向角。
3.2 低通滤波脚本的参数设置与频谱处理流程
Runme1_ButterworthLowPassFilter.m的完整流程包含了从频域变换到空间域还原的全部环节。核心步骤是先对含噪图像做 FFT,然后与 Butterworth 滤波器逐元素相乘,最后做逆变换取实部:
% 对含噪图像做二维傅里叶变换并中心化 F = fftshift(fft2(noisy_img)); % 构造 Butterworth 低通滤波器 D0 = 50; % 截止频率,单位是"像素/周期" n = 2; % 滤波器阶数 % ...(构造 D 和 H 的代码见 2.2 节) % 频域滤波 G = F .* H; % 逆傅里叶变换并取实部 filtered_img = real(ifft2(ifftshift(G))); % 对比:显示原图、含噪图、滤波结果 figure('Name', 'Butterworth Lowpass Result'); subplot(1,3,1); imshow(img); title('Original'); subplot(1,3,2); imshow(noisy_img); title('Noisy'); subplot(1,3,3); imshow(filtered_img); title('Filtered');fftshift和ifftshift是一对互逆操作,前者把零频分量从左上角移到中心,后者在逆变换前还原位置。real取实部是因为浮点运算会引入极小的虚部残差,直接显示会导致部分像素变成复数而报错。D0 = 50是一个经验值,对 256×256 的图像来说,50 大约保留了一半的频率成分。实际调参时,如果输出图像太模糊,就调大D0;如果噪声残留明显,就调小D0或者增大n。
3.3 最优陷波滤波的频域定位与掩膜构造
最优陷波滤波相比低通滤波的复杂度主要在于噪声频率的自动或半自动定位。在Runme2_optimumNotchFilter.m中,常见的做法是直接读取频谱图的局部极大值:
% 计算含噪图像的幅度谱 F_noisy = fftshift(fft2(noisy_img)); mag = log(1 + abs(F_noisy)); % 定位局部极大值(噪声频率点) % 这里使用 imregionalmax 找局部峰,mindistance 控制峰间距 peak_threshold = 0.5 * max(mag(:)); peaks = imregionalmax(mag) & (mag > peak_threshold); % 用陷波掩膜乘以频谱:掩膜在峰值位置设为衰减系数 % 构造一个全 1 矩阵,然后在噪声点处乘以 (1 - l/100) notch_mask = ones(size(mag)); stats = regionprops(peaks, 'Centroid', 'BoundingBox'); for k = 1:length(stats) cx = round(stats(k).Centroid(1)); cy = round(stats(k).Centroid(2)); % 对每个噪声点周围 a×b 区域施加衰减 [X, Y] = meshgrid(cx-a:cx+a, cy-b:cy+b); X = X(:); Y = Y(:); idx = sub2ind(size(notch_mask), Y, X); notch_mask(idx) = 1 - l / 100; end % 频域滤波 F_filtered = F_noisy .* notch_mask; filtered_img = real(ifft2(ifftshift(F_filtered)));这段代码的关键在于imregionalmax的阈值和regionprops的质心提取。peak_threshold设为幅度谱最大值的 50%,可以把大部分非噪声频率排除掉;如果噪声点不明显,可以手动指定已知的干扰频率坐标,避免自动检测漏掉低幅度的周期噪声。notch_mask初始化为全 1,只在检测到的噪声点周围 a×b 的矩形区域内乘以衰减系数1 - l/100。这个设计的巧妙之处在于:l是百分比衰减,55 表示削弱 55% 的能量,而不是直接置零,这样避免完全消除噪声频率时产生的振铃伪影。
4. 结果图对比体系与性能评估方法
4.1 从输出文件名反推实验设计逻辑
工程里几十张ONFa*b*l*.png结果图构成了一个完整的参数扫描矩阵。a和b分别取了 1、3、5、7、10、15、20,l取了 5、15、25、35、45、55,覆盖了小到大两个维度上的全部组合。这种全因子实验设计的价值在于:可以通过横向对比快速找到最优参数区间,而不是靠运气调参。
ONFa1b1l5.png —— 极小的陷波区域 + 弱衰减:噪声基本没被消除 ONFa20b20l55.png —— 大范围陷波 + 强衰减:噪声消失但细节也丢了 ONFa5b5l25.png —— 中等区域 + 中等衰减:细节保留较好,噪声基本消除从compare.png、compare2.png到compare11.png这组命名来看,脚本每运行一次就生成同屏对比图,左侧是低通滤波结果,右侧是陷波滤波结果。不同compare编号对应不同的(a, b, l)参数组,用来直接目测两种方法在同一条噪声图像上的差异。
4.2 Butterworth 与最优陷波的定量评价指标
光靠肉眼无法严谨评价去噪性能。资源中的other1.jpg和other2.png两组额外测试图像,恰好为定量分析提供了素材。评价时建议同时算三个指标:
% 计算 PSNR 和 SSIM original = img; restored = filtered_img; % PSNR(峰值信噪比),单位 dB,越大越好 mse = mean((original(:) - restored(:)).^2); psnr_val = 10 * log10(1 / (mse + eps)); % SSIM(结构相似性),越接近 1 越好 ssim_val = ssim(restored, original); % 噪声抑制比:滤波前后高频能量之比 F_orig = fftshift(fft2(original)); F_rest = fftshift(fft2(restored)); high_energy_orig = sum(abs(F_orig(:)).^2 .* (D > 30)); high_energy_rest = sum(abs(F_rest(:)).^2 .* (D > 30)); suppression_ratio = high_energy_rest / high_energy_orig;psnr对像素级误差敏感,ssim对结构保留敏感,两者必须同时看:PSNR 高但 SSIM 低,说明图像被过度平滑了(噪声没了但纹理也没了);PSNR 中等但 SSIM 接近 1,说明去噪后视觉质量更好。suppression_ratio的意义在于验证陷波滤波是否真的只削掉了目标频率——如果这个值明显低于 1,说明滤波器把不该削的高频也削掉了。在compare.m中,把三组指标打印在图像标题上,就可以直观比较ONFa20b20l45和ONFa3b3l25谁更优。
4.3 频谱图对比的判读要点
资源里的origin1FT.png、noise1FT.png、other2FT.png是分析去噪效果的另一直观依据。判读频谱图的要点有三个:一是看中心十字亮线是否被削弱,它代表低频背景;二是看对称分布的亮点是否消失,它对应周期噪声的基频和倍频;三是看高频区域的纹理是否保留,它决定图像的细节清晰度。
origin1FT.png(原始频谱) —— 亮点少,能量集中在中心 noise1FT.png(含噪频谱) —— 中心周围出现成对亮点 other2FT.png(其它样本频谱) —— 亮点位置不同,说明干扰频率与图像内容相关注意noise1.png与noise1FT.png是一对素材,前者是空域图,后者是前者的频谱。在 MATLAB 中按顺序执行fft2、fftshift、log(1+abs(...))三个操作后显示,能得到与之一致的频谱。如果直接用imshow(abs(fft2(img)))显示,会因动态范围过大而看到一片漆黑,在做频域分析时务必先取对数压缩动态范围。
5. 运行环境、路径依赖与高频报错排解
5.1 Matlab 版本与当前文件夹路径的硬性约束
这份代码的兼容性要求很明确:Matlab 2021a 或更高版本。版本限制主要来自imregionalmax和regionprops两个函数的行为变化,以及新版图像处理工具箱对ssim函数的参数格式调整。2021a 以下版本运行Runme2_optimumNotchFilter.m时,可能出现regionprops返回值结构体字段不一致的问题,最简单的规避方法是升级版本。
路径问题排在报错原因第一位。Matlab 的工作目录与脚本所在目录不一致时,imread('origin1.png')会直接报错"文件不存在"。原因是相对路径的解析依赖当前文件夹窗口,而不是脚本所在位置。推荐在脚本开头加上路径保护:
% 自动切换到脚本所在目录,避免路径错误 script_dir = fileparts(mfilename('fullpath')); cd(script_dir);这段代码用mfilename('fullpath')获取当前脚本完整路径,再cd切换过去。加在Runme脚本的最顶部,配合操作视频里的演示,可以彻底绕开路径问题。注意如果脚本是通过run命令执行的,mfilename可能返回空值,此时可以用pwd手动确认当前路径并cd到工程目录。
5.2 陷波滤波常见报错的逐条定位
根据资源中视频录像 0024.avi 的操作过程,最常遇到的有三个报错信息。
% 错误 1:索引超出矩阵维度 % 原因:regionprops 检测到的坐标 cx, cy 在图像边界附近,a 或 b 的范围超出图像幅面 % 处理方法:在构造网格前加边界约束 cx = max(a+1, min(cx, size(mag, 2)-a)); cy = max(b+1, min(cy, size(mag, 1)-b));% 错误 2:未定义函数或变量 'ssim' % 原因:缺少图像处理工具箱(Image Processing Toolbox) % 处理方法:改用 PSNR 或自写结构相似度函数,或者用 license('test', 'image_toolbox') 先检测% 错误 3:复数数据直接显示报错 % 原因:ifft2 结果仍有虚部,直接 imshow 会报错 % 处理方法:用 real() 取实部后显示,不要用 abs(),因为 abs 会把负值翻转,产生伪像第二个报错的ssim函数属于图像处理工具箱,如果学校机房安装的是基础版 Matlab,需要更换评价指标或申请工具箱授权。license('test', 'image_toolbox')返回 1 表示可用,0 表示未安装,一行代码即可完成检测。第三个报错最隐蔽,因为 Matlab 在某些情况下不会直接报错,而是显示一片白色或黑白噪点,本质是复数虚部被当成灰度值显示。
5.3 与操作录像对应的复现流程
资源中的操作录像0024.avi是完整演示范例,建议按以下步骤复现,而不是直接快进到结果:
- 打开 Matlab 2021a 及以上版本,等待路径初始化完成
- 将当前文件夹窗口切换至工程根目录(含所有
.m和.png文件) - 先运行
Runme1_ButterworthLowPassFilter.m,观察低通滤波结果和butterworth.png是否生成 - 再运行
Runme2_optimumNotchFilter.m,观察命令窗口是否输出频谱峰值坐标 - 查看生成的
notch.png和optimumNotch.jpg,与目标的goal1.png和goal1BW.png做形态对比
录像中有一处关键操作:在运行第二个脚本前,会先执行一个clear并重新加载图片。这是因为前一个低通滤波脚本在figure中显示的图像还占用内存,并且全局变量D被重新赋值后,D的尺寸和分辨率可能与新图像不匹配。如果两个脚本连续运行后出现矩阵维度不一致的报错,回到clear all重新跑通常能解决。
6. 用参数响应面快速锁定最优去噪组合
陷波滤波调参最忌逐个参数单点尝试,效率低且容易陷入局部最优。更高效的做法是先跑一组小规模的参数扫描,画出(a, b, l)与 PSNR 的响应面,再在响应面上找最高点附近精细调节。工程里已有的ONFa*b*l*.png结果图已经包含了横跨 1 到 20 的粗粒度扫描,可以直接在其基础上做一次双线性插值。
% 读取已生成的 PSNR 数据(需要在对比脚本中输出并保存) % 假设已保存 psnr_table,格式为:行是 a,列是 b,页是 l % 选取 l = 25 的切片绘制二维响应面 [X, Y] = meshgrid(1:20, 1:20); Z = squeeze(psnr_table(:, :, 25)); % 双线性插值细化网格 [Xq, Yq] = meshgrid(1:0.5:20, 1:0.5:20); Zq = interp2(X, Y, Z, Xq, Yq, 'linear'); % 绘制热力图并标记最大值 figure('Name', 'PSNR Response Surface at l=25'); imagesc(Xq(1,:), Yq(:,1), Zq); colorbar; colormap('hot'); axis xy; [~, idx] = max(Zq(:)); [max_x, max_y] = ind2sub(size(Zq), idx); hold on; plot(Xq(1,max_y), Yq(max_x,1), 'bo', 'MarkerSize', 10); text(Xq(1,max_y)+1, Yq(max_x,1), ... sprintf('Max PSNR = %.2f dB', max(Zq(:))));这段代码把散落的参数组合映射为连续曲面。interp2的linear方法在相邻参数间距不超过 5 时有足够的精度,间距更大时建议改用spline。imagesc后必须加axis xy把纵轴方向翻转为常规坐标系,否则热力图的 y 轴默认向下递增,坐标和实际参数对应不上。最大值点的气泡图标记能让最优参数一目了然——通常这个点出现在a和b介于 5~10、l介于 25~35 的区域内,这是周期噪声频率点直径和能量强度的典型匹配结果。
如果 PSNR 响应面出现多个局部峰值,说明噪声频谱存在多个分离的冲击点。这时不要用单个陷波器统一覆盖,而是回到频谱图上测量每个峰点之间的距离和方向,分组构造多个小尺寸陷波器。相比一个大范围陷波器,多个小陷波器的叠加能保留更多有效细节——这正是工程里生成多张ONF系列结果图的意义,先用足够精细的参数网格找到每个噪声点的最优覆盖半径,再合并为最终的陷波掩膜。最后验证方式很简单:把最优参数代入Runme2_optimumNotchFilter.m生成结果,和低通滤波输出并排比较,compare*.png里结构边缘保留更完整的那一侧,就是最符合实际需求的方案。
本文还有配套的精品资源,点击获取