尧图精选

图像傅里叶变换:从频域滤波到机器视觉应用

🕒 发布时间:2026/9/28 2:04:04 📁 来源:尧图网络
傅里叶变换这个词很多人一听就想起数学课本里的积分号和欧拉公式然后下意识觉得“这玩意儿和我没什么关系”。但实际上你手机里的照片降噪、修图软件的锐化、CT成像、音频均衡器甚至电机振动诊断背后站着的都是傅里叶变换。我这几年的工作里机器视觉项目占了一大半图像傅里叶变换几乎是每天都要打交道的工具。与其说它是一个数学概念不如说它是一副“频域眼镜”——戴上之后你不再只看像素在空间里怎么排布而是看整幅图里有哪些“频率成分”哪些该留、哪些该砍一刀下去效果立竿见影。这篇文章我就用图像处理的实际例子把常用的傅里叶变换公式、常用傅里叶变换对和代码串起来讲目的是让你看完就能自己动手。1. 从公式到直觉傅里叶变换到底在算啥1.1 公式是地图不是天书先摆两个最基础的东西。连续傅里叶变换公式长这样F(ξ) ∫ f(x) e^(-j2πξx) dx离散版本也就是计算机里真正用的离散傅里叶变换DFT长这样F(k) Σ_{n0}^{N-1} f(n) e^(-j2πkn/N)很多初学者看到这个Σ和指数函数就头大。我换个说法这个公式其实是在问一个问题——“给我一段信号f(x)你来告诉我它里面包含了多少频率为ξ的纯正弦波分量”。数学上就是让f(x)和e^(-j2πξx)做内积。e^(-j2πξx)就是一个旋转的复指数展开之后是cos和sin的组合。算出来的F(ξ)是复数模代表“这个频率成分有多强”幅角代表“这个频率的相位”。对图像来说公式变成二维形式F(u,v) Σ_x Σ_y f(x,y) e^(-j2π(ux/M vy/N))这里的u、v不是像素坐标而是x方向和y方向的空间频率。u0v0那个点是直流分量也就是整幅图的平均亮度。越往外走频率越高对应像素值变化越剧烈的边缘、纹理、噪声。真正干活的时候不会有人手搓DFT都是直接调快速傅里叶变换FFT。FFT不是另一种变换它是DFT的一种高效算法能把O(N²)的暴力计算降到O(N logN)。图像是二维数据就沿行和列分别做一维FFT。写代码的时候你只需要记住一句话用np.fft.fft2做二维变换用np.fft.ifft2做逆变换不要自己去写循环。1.2 常用傅里叶变换对速查表搞应用不需要背一大堆推导但常用变换对的“直觉”一定要有。所谓变换对就是“时域/空域长这样频域长那样”的对应关系。空间/时间域信号频域结果应用直觉矩形脉冲sinc函数方波的频谱是sinc有旁瓣所以理想低通会振铃高斯函数高斯函数高斯做低通不会产生振铃因为它频域也是平滑衰减的正弦波一对冲激一个纯净的频率在频域里就是两个亮点冲激函数平坦常数一个极小的点包含所有频率所以椒盐噪声是宽带高频直流常数中心冲激频谱图中心的亮点就是图像的直流分量这张表特别实用。比如你在频谱图里看到两个对称的亮斑就知道原图里有一个特定频率的正弦纹理看到中心亮周围弥散就知道图像以低频为主看到十字交叉亮线说明图像存在水平或垂直方向的强边缘。后面滤波的时候这些对应关系就是你的直觉依据。2. 机器视觉里的图像傅里叶变换看清频域再动手2.1 给图像做傅里叶变换其实是在看它的“频谱指纹”先放一段最基础的代码把一张灰度图像变成频谱图看看。import cv2 import numpy as np import matplotlib.pyplot as plt img cv2.imread(demo.jpg, cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (512, 512)) # 转成float32否则fft后取log容易溢出 img_float np.float32(img) # 二维FFT f np.fft.fft2(img_float) # 频谱中心化把零频移到图像中心 f_shift np.fft.fftshift(f) # 幅度谱取对数压动态范围 magnitude np.abs(f_shift) log_magnitude np.log(1 magnitude) plt.imshow(log_magnitude, cmapgray) plt.title(Log Magnitude Spectrum) plt.show()这段代码里fftshift是一个特别容易忽略的步骤。原始FFT的结果中零频分量其实在左上角四周是高频直接可视化会看到四个角特别亮中心在角落看着很别扭。fftshift把四个象限对调让低频回到正中央外面的亮环代表高频这样才符合我们平时看的“中心是低频四周是高频”的频谱图。拿到频谱图之后怎么读我总结三个最实用的点中心区域亮度高说明图像本身比较平滑大块连续像素占主导。如果中心非常集中、四周几乎没有东西就是一张模糊的、变化缓慢的图。从中心向外延伸的直线亮纹对应图像里的直线边缘。横向边缘会在纵向频谱方向v轴形成亮线纵向边缘会在横向频谱方向u轴形成亮线。远离中心的位置出现孤立亮点说明图像里有周期性的纹理或噪声比如印刷品的网点、传感器的固定条纹。我自己看频谱图有个习惯先关掉彩色转灰度再分析。因为彩色图像的三个通道各自有频谱如果混在一起很多细节会被平均掉。机器视觉项目里我几乎只对单通道图做频域处理等频域加工完再合并或转回彩色。2.2 频域滤波的基本套路滤波在空间域是拿一个核比如高斯核和图像做卷积在频域只需要做乘法。这来源于卷积定理两个函数卷积后的傅里叶变换等于各自傅里叶变换的乘积。所以“空间域卷积”和“频域乘法”是等价的。在频域做滤波的完整套路固定为四步对图像做FFT得到复数频谱F。用fftshift把低频移到中心方便构造以中心为原点的滤波器。构造一个和图像尺寸相同的滤波器H每个位置的值是0到1之间的小数和频谱做逐元素乘法G F_shift * H。先ifftshift把频谱还原到原来象限位置再ifft2逆变换取实部得到滤波后的图像。为什么要先fftshift再构造滤波器因为如果频谱低频在左上角那你构造滤波器也得跟着把低频放在左上角否则两者对不上。而大多数滤波器公式都写成以中心为原点的形式用np.meshgrid生成坐标网格再算每个点到中心的距离D(u,v)这样直接在中心化的频谱上操作最自然。这一步也解释了为什么图像傅里叶变换很适合做机器视觉预处理。比如有些工业零件表面的纹理缺陷在空间域可能只是几个像素的灰度波动但它在频谱上位于某个特定扇形区域内。只要构造一个带通或带阻滤波器把那块频率“掐掉”再反变换回空间域纹理被抹平缺陷反而更清晰了。这种操作在空间域几乎做不到因为纹理和缺陷混在一起很难用固定核分离。3. 动手做一次频域滤波从带噪图片里捞回细节3.1 低通滤波去掉噪点先造一张带噪声的图。我用经典Lena图如果手头没有随便找一张纹理适中的照片加一点高斯噪声或椒盐噪声。rng np.random.default_rng(42) noise rng.normal(0, 30, img.shape).astype(np.float32) img_noisy np.clip(img_float noise, 0, 255) # 高斯低通滤波器 rows, cols img.shape crow, ccol rows // 2, cols // 2 D0 30 # 截止频率单位是像素频率半径 u np.arange(rows) - crow v np.arange(cols) - ccol U, V np.meshgrid(u, v, indexingij) D np.sqrt(U**2 V**2) H_low np.exp(- (D**2) / (2 * D0**2)) # 频域滤波 f_noisy np.fft.fft2(img_noisy) f_noisy_shift np.fft.fftshift(f_noisy) G_shift f_noisy_shift * H_low G np.fft.ifftshift(G_shift) filtered np.real(np.fft.ifft2(G))这段代码里的D0是截止频率直观理解就是“保留到多大半径的低频”。D0越小保留的低频越少图像越模糊D0越大保留下来的高频越多去噪能力越弱。为什么不选理想低通滤波器因为理想低通的H在截止处从1突然跳成0对应空间域的核是sinc函数会在边缘附近产生振荡图像出现一圈一圈的“振铃”。高斯低通在频域是平滑衰减到0没有这种硬边界所以视觉上好得多。跑完效果应该是噪声明显变少同时图像也变柔了。如果D0取得太激进比如设成10图会糊成一片连轮廓都看不清。实际项目中我一般先取图像短边的1/20到1/10作为初始D0然后看频谱图里噪声亮斑大概占据哪个半径再精确调整。3.2 高通滤波提取轮廓低通是保低频去高频高通反过来保高频去低频。写起来只改一个地方H_high 1 - H_low。H_high 1.0 - np.exp(- (D**2) / (2 * D0**2)) f_shift2 np.fft.fftshift(np.fft.fft2(img_float)) G_shift2 f_shift2 * H_high G2 np.fft.ifftshift(G_shift2) img_high np.real(np.fft.ifft2(G2))得到的高通结果会直接去掉大面积亮度差留下边缘和细节。注意这里输出可能有负值直接用plt.imshow会截断最好先归一化或者取绝对值img_high_norm cv2.normalize(img_high, None, 0, 255, cv2.NORM_MINMAX)高通滤波在机器视觉里有一个很实用的场景光照不均的校正。比如生产线上拍金属表面环境光从一侧打过来左边亮右边暗。如果直接做阈值分割背景亮度变化会把目标淹没。先用高通把低频背景去掉让图像灰度分布在0附近再做后续检测稳定性会高很多。和Sobel这类空间域边缘算子相比频域高通更“全局”。Sobel核只有3x3反应的是局部梯度频域高通可以精确指定“从哪个频率开始加强”对噪声的压制更可控。代价是计算稍重、参数不如Sobel直观。所以我通常把频域高通用在离线标定或效果验证阶段在线实时部署如果硬件能跑FFT也可以用。3.3 滤波器尺寸、坐标映射和数据类型三个坑一个都不能踩频域滤波写起来容易但踩坑也容易。最常见的三个问题第一滤波器尺寸必须和原图像完全一致。如果你对一张512x512的图做FFT滤波器也必须是512x512。有人图省事用cv2.resize把滤波器缩小或放大搞出来的结果莫名其妙因为频谱空间和像素空间是一一对应的。第二fftshift和ifftshift必须成对出现、顺序不能错。正确的顺序是fft2-fftshift- 滤波 -ifftshift-ifft2。有人先fftshift再滤波最后忘记ifftshift直接ifft2结果图像变成四块拼图四个象限颠倒了。记住ifftshift不是把中心移回去就完事它是fftshift的精确逆操作两者配合才等于什么都没改。第三数据类型要冷静。图像读进来是uint8FFT要转成float32或float64。滤波结果逆变换后可能有负值和超出255的值不能直接转回uint8会截断出异常黑点。正确做法是先用np.clip(res, 0, 255)限制范围再astype(np.uint8)或者像我上面那样用cv2.normalize归一化到0-255再转类型。4. 常见问题与排查技巧实录4.1 频谱图中心一团白四周什么都看不见这个我一开始也遇到过。原因很简单频谱动态范围太大直流分量和低频成分的幅度可能上百万而高频噪声幅度只有几百。直接显示会把高频细节压成纯黑只有中心一个亮点。解决办法是取对数用np.log(1 magnitude)。我习惯用np.log(1 mag)加1是为了避免log(0)。如果想让对比更强可以试试20 * np.log10(1 mag)效果和np.log差不多只是刻度不同。4.2 滤波后图像出现规律的网格状伪影网格状伪影的常见元凶有三个。第一是用了理想低通滤波器振铃造成的同心圆或格子花纹解决方法是换高斯或巴特沃斯滤波器让H平滑过渡。第二是fftshift位置弄错频谱象限没有对齐反变换后图像被拆成四份或错位检查一下ifftshift有没有加回来。第三是滤波器在边界处有突变比如把外围频率全部置0而边界恰好落在某个规则的位置上通过查看你的H的可视化结果看它是不是一个连续变化的圆形能快速判断。4.3 FFT和空间域卷积到底哪个更快这个问题在算法选型时绕不开。空间域卷积的复杂度是O(N x M)假设图是N x N卷积核是K x K就是N × N × K × K。FFT的复杂度是O(N² logN)而且和卷积核大小几乎无关。所以卷积核小3x3、5x5的时候空间域直接算更快FFT还要做两次正变换一次逆变换开销更大。卷积核大比如30x30、50x50的时候FFT优势非常明显。尤其在做大尺度高斯模糊时我实测512x512图像、50x50核空间域慢了一倍以上核越大差距越离谱。如果只需要做一次频域处理比如高通、带通那几乎总是选频域因为你本来就要看频谱。机器视觉实时项目里有个折中在分辨率不高时用空间域核做卷积因为嵌入式设备上FFT库的性能不一定好在PC端做实验时优先频域代码短参数直观。4.4 图像傅里叶变换的“复数恐惧症”很多新手看到FFT结果是一堆复数就不知道下一步干啥。记住幅度决定“有没有这个频率”相位决定“这个频率长在哪里”。滤波操作通常只改幅度不碰相位因为相位破坏后图像结构会崩掉。所以反向变换之前只乘一个实数的H原本频谱的相位成分保留在复数结构里没问题。如果你把频谱的幅度取出来把相位扔掉再逆变换图像会变成一团乱糟糟的纹理没有实际几何结构。这个实验我做过印象深刻建议你亲自试一次能瞬间理解相位的作用。5. 训练“频域手感”几个多年来我觉得很好用的习惯5.1 拿到图像先想成分而不是急着调参我现在处理图像的习惯是先跑一行np.fft.fft2看频谱再决定用什么手段。比如零件表面是反光材质背景有规律纹理频谱里肯定有一对对称亮点那就可以用带阻滤波器把它消掉如果图像整体偏暗又有噪声先别做对比度增强用低通把噪声压一压再拉伸亮度效果稳定得多。这种“从成分出发”的思考方式是傅里叶变换带给我的最大转变。5.2 小图试参数、大图跑效果我自己调试时永远先用128x128或256x256的裁剪图试滤波器参数。小图FFT快可视化也直观D0、带宽只要在小图上定个范围再按比例到大图上微调。为什么同样结构的内容分辨率变了边缘频率分布也会变。从128放大到1024频率半径近似等比缩放所以截止频率D0一般也按比例乘上去。别在大图上反复试浪费时间还容易把眼睛看花。5.3 可视化滤波器本身调试时把H也用plt.imshow(H, cmapgray)画出来确认它是不是一个位置正确、过渡平滑的圆盘。很多时候图像效果不对回看H才发现滤波器中心偏移或者边界截断。滤波器和频谱必须严丝合缝地对齐这是所有频域操作的地基。傅里叶变换的内容实在太多这篇文章主要讲的是图像处理和机器视觉里最常用的图像傅里叶变换、频域滤波和对应的公式直觉。我个人体会是真正掌握它的标志不是你背下了多少变换对而是你能在拿到一个问题时下意识地想这个现象对应频谱上的哪一块去噪、增强、分离纹理都能从这个角度重新解释。下一篇文章我会接着写音频和振动信号的处理比如怎么用傅里叶变换识别电机故障频率、怎么滤掉交流“嗡嗡”声那是另一个一样香的应用场景。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →