傅里叶变换在织物检测中的应用
1. 傅里叶变换的基本原理
傅里叶变换是一种将时域信号转换为频域信号的数学工具,其核心思想是:任何周期性信号(甚至非周期性信号)都可以表示为不同频率正弦波的叠加。通过傅里叶变换,我们可以将复杂的时域信号分解为一系列简单正弦波和余弦波的叠加,从而分析信号的频率成分。
理解傅里叶变换,最关键的是理解两个“世界”:
-
时域(Time Domain): 这是我们日常观察世界的视角。比如音乐,是一段随时间波动的声压;示波器上的图像,是随时间变化的电压,描述信号随时间的变化。
-
频域(Frequency Domain): 这是一个类似“食谱”的视角。傅里叶变换就像是一个转换器,它能告诉你这个复杂的信号是由哪些频率的“基本波”组成的,以及每个频率的“分量”有多大。其描述信号的频率成分,如声音的音调、图像的纹理
傅里叶变换实现了时域与频域的转换,使我们能够从不同角度分析信号。
2. 傅里叶级数
这是傅里叶变换的基础。傅里叶发现,如果你有一个周期性的波形(比如方波、三角波),你可以通过无限多个正弦波叠加来逼近它。
-
基波: 频率与原信号一致的波。
-
谐波: 频率是基波整数倍的波。
当这些频率不同、相位不同、振幅不同的正弦波组合在一起时,它们相互叠加或抵消,就能拼凑出各种形状。
3. 数学定义
对于非周期信号,我们使用连续傅里叶变换,连续傅里叶变换的公式为:

- 物理意义:任何一个复杂的连续信号,都可以分解成无数个不同频率的正弦波(或复指数波)的叠加。
- 结果:是复数,包含幅度(反映频率成分的强度)和相位(反映频率成分的相位偏移)。
是公式的核心,为欧拉公式定义的复指数,根据欧拉公式有
本质上是正弦和余弦的数学统一表达,在物理上,它代表一个以频率w逆时针旋转的单位向量(或者说是一个标准频率的探测信号)通俗来讲我们可以理解为其为检测信号中是否包含特定频率w的探测器,也就是我们只需要遍历w,一个个去尝试,如果信号分解不包含该w,那么投影f(t)·e-iwt相乘后结果就会正负抵消,趋近于0,积分结果的振幅就很小或接近0,如果包含有特定频率w,那么乘积结果累计(积分)后.就会很大。关于证明过程可以参考后面的第3点。
振幅(模)|F(w)|:告诉我们频率为W的波在原信号中占有多大比重。振幅越大,说明这个频率成分越明显。
上式子中,f(t)是时域信号,F(W)是频域信号(描述的时信号中包含有哪些频率成分),w是角频率(w=2πf)。
逆变换公式为:

通过逆变换,可以将频域信号还原为时域信号。
这个公式告诉我们:把所有频率的正弦波eiwt按照它们各自的权重(F(ω)重新叠加起来,就能把原来的信号 f(t)拼凑回来。前面那个系数是一个归一化系数(为了抵消角频率带来的倍数关系),确保能量守恒。
4. 频率为w0的信号f(t)与探针频率为w是否正交证明
证明前先看两个概念(内积与正交)
- 在数学中,两个函数的乘积再积分
被称为这两个函数的内积。 - 如果内积为 0,说明这两个函数正交(互不相关)。
傅里叶变换其实就是在计算f(t)与“标准频率波”
的相关性。
为了简化证明,我们先不使用复杂的复数,直接看最基础的三角函数正交性。假设信号如下:
频率为w0的单纯波。我们要用频率W去探测它。根据傅里叶变换的原理,我们要计算它们的乘积积分:

利用三角恒等式

积分变为:

当 w≠w0时: 这两个余弦波项都在进行完整的周期性波动。在一个完整的周期(或足够长的时间)内,余弦函数在 x$轴上方的面积和下方的面积是相等的。

这就是趋近于零”,意味着信号中不含这个频率。
当 w=w0时:第一项变成了cos(0)=1。结果为:

此时不再是 0,而是一个与时间T成正比的数值!这代表我们找到了那个频率。
接下来,我们再看复杂一点的一般性证明:
假设信号f(t)是由很多不同频率的成分组成的。根据傅里叶逆变换的定义,任何信号f(t)都可以写成:

这里w0代表f(t)内部实际拥有的所有频率。在数学上,任何复杂的信号 f(t),都可以看作是无数个不同频率的纯净振荡波
的叠加。F(w0)为复数,包含了振幅与相位信息,这里不再作公式展开解释了。我们将上式代入到傅里叶变换公式中,去探测某个特定的频率w:

交换积分顺序(数学上处理这类问题的常用技巧):

我们看方括号中的内容:

即使不用复数,只看实数部分,它也是在计算:

若w≠w0:这是一个一直在震荡的波(正弦或余弦)。在无限的时间轴上,它的正半周面积和负半周面积完全相等,会完美抵消。从几何上讲,一个不为 0 的频率在时间轴上匀速摆动,其总和(积分)必然为 0。
若 w=w0:指数项变成了e0=1。∫1dt在无限空间会变成一个“无穷大”的脉冲(即狄拉克δ函数)。
狄拉克σ函数:

当 w=w0时:出现一个极强的脉冲,代表信号中找到了这个频率。
当 w≠w0时:值永远为0
傅里叶变换本质上是将原始信号投影到一组正交的基函数(复指数波)上。
正交在数学上的含义就是:不同频率的波相乘后再积分,结果一定是 0。这就好比在空间坐标系中,X 轴方向的分量在 Y轴上投影永远是 0 一样。只有当探测器的频率和信号的频率完全一致时,投影才会有值。
5. 离散傅里叶变换(DFT)与快速傅里叶变换(FFT)
在实际应用中,信号通常是离散的,因此需要使用离散傅里叶变换(DFT)。DFT的公式为:

其中,x[n]是离散时域信号,N是信号长度。
- 物理意义:将长度为个离散频率的复指数波的叠加。
- 频率分辨率:相邻频率间隔为是采样频率(图像中通常为空间采样率)。
FFT(Fast Fourier Transform): 这是 DFT 的一种高效算法核心思想是利用正弦波的对称性和周期性,把一个大问题拆成两个小问题。。如果没有 FFT,我们现在的数字通讯、mp3 压缩、核磁共振成像等都将变得极度缓慢甚至无法实现。
最常用的“基-2 FFT”逻辑:
拆分:把长度为 N 的序列,拆成两个长度为 N/2的序列(一个是所有偶数点,一个是所有奇数点)。
递归:对这两个小序列分别做傅里叶变换。
合并(蝴蝶运算):利用复指数项(旋转因子)的对称性,只需简单的加减法就能把小问题的结果合并成大问题的结果
计算复杂度从 O(N2) 降到了 O(Nlog2N)
6. 傅里叶变换在织物脏污检测中的应用
在织物脏污检测中,傅里叶变换主要用于将图像从时域(空间域)转换到频域,通过分析频域特性来检测脏污区域。
6.1织物图像的频域特性
- 织物纹理:织物表面通常具有周期性的纹理,如经纬线的交织。这些纹理在频域中表现为高频成分。
- 脏污区域:脏污通常表现为与周围像素灰度值差异较小的区域,且脏污区域内的像素灰度值变化较大。这些特性在频域中表现为低频成分或特定频率的异常。
6.2 频域滤波的原理
通过傅里叶变换将织物图像转换到频域后,可以设计频域滤波器来滤除特定频率的成分,从而突出脏污区域。
- 低通滤波:滤除高频成分(织物纹理),保留低频成分(脏污区域)。
- 高通滤波:滤除低频成分(均匀背景),保留高频成分(边缘和脏污细节)。
- 带通滤波:选择特定频率范围进行滤波,以突出脏污区域的特征。
6.3具体检测步骤
- 图像预处理:对织物图像进行灰度化、去噪等预处理,提高后续处理的准确性。
- 傅里叶变换:将预处理后的图像进行傅里叶变换,得到频域图像。
- 频域滤波:根据脏污区域的频域特性,设计合适的滤波器(如低通滤波器)对频域图像进行滤波。
- 逆傅里叶变换:将滤波后的频域图像进行逆傅里叶变换,得到时域图像。此时,脏污区域将更加突出。
- 阈值分割:对逆变换后的图像进行阈值分割,提取脏污区域。
- 形态学处理:对提取的脏污区域进行形态学处理(如膨胀、腐蚀),以消除噪声和填充空洞。
实际应用案例
- 织物脏污检测系统:某织物脏污检测系统采用傅里叶变换和频域滤波技术,成功实现了对织物表面脏污的自动检测。该系统首先对织物图像进行傅里叶变换,然后通过低通滤波器滤除高频纹理成分,最后通过阈值分割和形态学处理提取脏污区域。实验结果表明,该系统能够准确检测出织物表面的脏污,且检测速度较快。
- 基于Halcon的污染区域检测:Halcon是一款常用的机器视觉软件,其提供了傅里叶变换和频域滤波的相关函数。通过Halcon,可以方便地实现织物脏污检测。具体步骤包括:读取织物图像、进行灰度值取反、生成正弦带通滤波器、对图像进行傅里叶变换和滤波、使用阈值和形态学分割图像以提取缺陷区域。
7. 二维图像傅里叶变换
前面介绍了织物检测过程中使用傅里叶变换可以检测出脏污,并没有详细数学公式说明,所以这里看具体的二位图像是怎么变换的,对于图像,其是二维离散信号,其傅里叶变换需对行和列分别进行一维DFT。
7. 1二维DFT公式

- 物理意义:将图像分解为不同空间频率 的二维复指数波的叠加。
- 频率分量:
- 对应水平方向频率,对应垂直方向频率。
- 低频接近0)反映图像整体灰度变化(如均匀区域)。
- 高频较大)反映图像细节(如边缘、纹理)。
7.2 二维逆DFT(IDFT)
从频域恢复空间域图像的公式为:

8. 图像傅里叶变换的步骤详解与脏污检测流程
以图像为例,逐步说明计算过程。
图像矩阵表示
假设图像 为:

对每一行进行一维DFT
以第一行为例:

计算结果:对所有行重复此操作,得到中间结果(未对列变换)。
列为例:

为便于观察,通常使用下列公式将低频分量移到频域中心:

现在以下方这个 4 x4 的矩阵为例。为了让计算过程清晰,我们不需要去算复杂的 16 个频点,只需要通过 “直流分量(平均值)” 和 “高频探测(纹理/异常检测)” 两个关键点的计算,你就能明白傅里叶变换是怎么“揪出”那个 250 的。
原始图像矩阵 f(x,y)如下:

第一步:计算“整体背景” —— 直流分量 F(0,0)
当行频率u=0与列频率v=0时候,F(0,0) 代表图像的总体亮度(除以总像素就能得到平均亮度。)公式简化为所有像素求和:

当 u=2, v=2时,复指数项
的取值规律非常简单:
如果 x+y 是偶数,结果是1。如果 x+y是奇数,结果是-1。
我们将矩阵点与这个棋盘格权重相乘:

计算 F(2,2):
第三步:分析结果(缺陷是怎么暴露的?)
场景 A:如果没有脏污(假设 250 也是 10 左右的正常纹理)
如果全是 8, 9, 10, 11 这种波动:
你会发现计算 F(2,2)时,正负号会把这些相近的数字抵消掉。
例如:(9-11) + (9-10) + (8-10) ...结果会是一个非常小的数(接近 0)。
结论: 正常的、重复的织物纹理在这些高频点上产生的能量很低。
场景 B:存在脏污(那个 255)
因为 250 实在太大了,它打破了平衡。即使其他数字都在努力抵消,250 乘以它的权重后依然留下了一个巨大的余额(我们在上面算出了 241)。
结论: 在频域的图谱中,原本应该是黑暗的高频区域,突然出现了一个异常亮的点。
第四步:缺陷检测的最后闭环
-
频域观察:计算机看到F(2,2)这种高频位置竟然有 241 这么大的值,而正常织物应该只有个位数。
-
掩模滤波:计算机知道 8, 9, 10 这些“背景杂音”在频谱中也有微弱的固定位置。它把那些代表“正常节奏”的位置抹黑(置零)。
-
逆变换 (IFFT):
-
被抹黑的背景纹理(8, 9, 10)因为失去了频率支撑,变回空间域时变成了平坦的 0。
-
而脏污(250)的能量依然存在。
-
最终重建图像 :

此时,原本干扰视线的织物纹理不见了,那个 230 的亮点在黑漆漆的矩阵里像灯塔一样明显,也就说脏污的能量平均存在于几乎所有频率中,我们把属于背景纹理的频率抹去,其脏污能量也只会降低一点点,逆变换后脏污就能突出了。
那些最突出的、成对对称的亮点,一定是背景纹理。如果你把它们抹掉,你只是“静音”了背景音乐。脏污的信息因为分布在全频带,即便你抹掉这几个点,它绝大部分的信息依然保留在其他频率里。最后再看下面两个表格,看了以后相信对傅里叶检测脏污原理更能理解。


9. 图像傅里叶变换的物理意义与可视化
- 频谱的物理分布规律:
- 低频分量:对应图像中整体灰度变化缓慢的区域(如均匀背景、大面积脏污),在频谱中心聚集。
- 高频分量:对应图像中灰度变化剧烈的区域(如纹理边缘、噪声),在频谱四周分布。
- 方向性:水平方向的纹理(如织物经线)在频谱中表现为垂直方向的高频分量轴),垂直方向的纹理(如织物纬线)表现为水平方向的高频分量轴。
- 可视化对比:
- 未中心化的频谱:零频率在左上角,低频与高频混杂,难以直观分析频率分布。
- 中心化的频谱:低频在中心,高频在四周,形成“同心圆”分布,符合人眼对频率空间的认识(如“中心亮、四周暗”表示图像整体均匀,“中心暗、四周亮”表示纹理富)。
10. 傅里叶变换作用?
傅里叶变换之所以强大,是因为很多在时域里很难处理的问题,到了频域会变得异常简单:
-
降噪/滤波: 如果你的声音里有高频的杂音,你可以通过傅里叶变换找到高频部分并切掉它,再转回时域,杂音就消失了。
-
数据压缩: 比如 JPEG 图片压缩,利用了二维傅里叶变换(实际上是其变体 DCT),通过去掉人类眼睛不敏感的高频细节来减少存储空间。
-
音频处理: 均衡器(调音台)调节高低音,本质上就是改变不同频率分量的增益。
-
通讯技术: 手机、Wi-Fi 信号的调制与解调。
11. 傅里叶变换织物脏污检测代码实战
import cv2 import numpy as np import matplotlib.pyplot as plt def detect_fabric_clean_with_spectrum(image_path): # 1. 预处理 img = cv2.imread(image_path, 0) if img is None: print("图像加载失败!") return # 2. 傅里叶变换 f = np.fft.fft2(img) #标准的傅里叶变换算法 fshift = np.fft.fftshift(f) #频谱平移,它将 FFT 结果中的四个象限进行对角交换,把坐标原点(零频率点)从左上角移动到矩阵的几何中心 mag = np.abs(fshift) # 幅度谱 |F(u,v)| rows, cols = img.shape crow, ccol = rows // 2, cols // 2 # ========== 频域可视化准备 ========== # 对数变换便于显示(因为直流分量太大,会压过其他细节) mag_log = np.log1p(mag) # log(1+|F|) # 3. 动态寻找纹理特征点并使用"软掩模"抑制 fshift_filtered = fshift.copy() # 排除中心低频区域 mask_center = np.ones((rows, cols), np.uint8) cv2.circle(mask_center, (ccol, crow), 15, 0, -1) potential_peaks = mag * mask_center # 锁定最强的特征频率点(纹理) thresh = np.percentile(potential_peaks, 97) peaks = (potential_peaks > thresh).astype(np.uint8) # 核心改进:使用高斯模糊处理掩模 peaks_soft = cv2.GaussianBlur(peaks.astype(float), (9, 9), 0) fshift_filtered = fshift_filtered * (1.0 - peaks_soft * 0.9) # 抑制90%纹理能量 # ========== 滤波后的频域图 ========== mag_filtered = np.abs(fshift_filtered) mag_filtered_log = np.log1p(mag_filtered) # 4. 逆变换回空间域 f_ishift = np.fft.ifftshift(fshift_filtered) img_back = np.fft.ifft2(f_ishift) img_back = np.abs(img_back) # 5. 后处理增强 img_back = cv2.normalize(img_back, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) # 6. 二值化 _, binary = cv2.threshold(img_back, 80, 255, cv2.THRESH_BINARY_INV) # 7. 去除零星杂点 binary = cv2.medianBlur(binary, 3) num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary, connectivity=8) clean_binary = np.zeros_like(binary) for i in range(1, num_labels): area = stats[i, cv2.CC_STAT_AREA] if area > 10: clean_binary[labels == i] = 255 # ========== 可视化展示(2行4列) ========== fig, axes = plt.subplots(2, 4, figsize=(16, 10)) # 第一行:空间域和频域处理过程 # 1. 原图 axes[0, 0].imshow(img, cmap='gray') axes[0, 0].set_title('1. Original Image\n(Spatial Domain)') axes[0, 0].axis('off') # 2. 原始频谱(对数幅度) im1 = axes[0, 1].imshow(mag_log, cmap='jet') axes[0, 1].set_title('2. Original Spectrum\n(log|F(u,v)|)') axes[0, 1].axis('off') # 添加颜色条 plt.colorbar(im1, ax=axes[0, 1], fraction=0.046, pad=0.04) # 3. 检测到的纹理峰值(硬掩模) axes[0, 2].imshow(peaks, cmap='gray') axes[0, 2].set_title(f'3. Detected Texture Peaks\n(Top 3%, threshold={thresh:.1f})') axes[0, 2].axis('off') # 4. 软掩模(高斯平滑后) im2 = axes[0, 3].imshow(peaks_soft, cmap='hot') axes[0, 3].set_title('4. Soft Mask\n(Gaussian Blurred)') axes[0, 3].axis('off') plt.colorbar(im2, ax=axes[0, 3], fraction=0.046, pad=0.04) # 第二行:频域滤波效果和空间域结果 # 5. 抑制后的频谱(对数幅度) im3 = axes[1, 0].imshow(mag_filtered_log, cmap='jet') axes[1, 0].set_title('5. Filtered Spectrum\n(Texture Suppressed)') axes[1, 0].axis('off') plt.colorbar(im3, ax=axes[1, 0], fraction=0.046, pad=0.04) # 6. 频谱差异图(原始-滤波后) diff_log = mag_log - mag_filtered_log im4 = axes[1, 1].imshow(diff_log, cmap='RdBu_r') axes[1, 1].set_title('6. Spectrum Difference\n(Removed Energy)') axes[1, 1].axis('off') plt.colorbar(im4, ax=axes[1, 1], fraction=0.046, pad=0.04) # 7. 重建图像(逆变换后) axes[1, 2].imshow(img_back, cmap='gray') axes[1, 2].set_title('7. Reconstructed\n(Texture Cleaned)') axes[1, 2].axis('off') # 8. 最终缺陷检测结果 axes[1, 3].imshow(clean_binary, cmap='gray') axes[1, 3].set_title('8. Final Defect Mask') axes[1, 3].axis('off') plt.tight_layout() plt.savefig('fabric_defect_with_spectrum.png', dpi=150, bbox_inches='tight') plt.show() # ========== 打印统计信息 ========== print("=" * 60) print("频域处理统计信息") print("=" * 60) print(f"图像尺寸: {rows} x {cols}") print(f"总频点数: {rows * cols}") print(f"97%分位数阈值: {thresh:.2f}") print(f"检测到的峰值点数: {np.sum(peaks)} ({100 * np.sum(peaks) / (rows * cols):.2f}%)") # 能量统计 total_energy = np.sum(mag ** 2) peak_energy = np.sum((mag * peaks) ** 2) remaining_energy = np.sum(mag_filtered ** 2) removed_energy = total_energy - remaining_energy print(f"\n能量统计:") print(f" 原始总能量: {total_energy:.2e}") print(f" 峰值区域能量: {peak_energy:.2e} ({100 * peak_energy / total_energy:.1f}%)") print(f" 抑制后剩余能量: {remaining_energy:.2e}") print(f" 被抑制能量比例: {100 * removed_energy / total_energy:.1f}%") print("=" * 60) return clean_binary # 使用示例 image_path = r'images/17.png' image_path = r'images/1.png' detect_fabric_clean_with_spectrum(image_path)



子图解释如下:

上面表格对每幅图进行了解释,这里我们看下频谱图(其实就是F(w)的在不同频率下的幅度强度谱)中的亮点
-
纹理的亮点:是因为能量高度集中。织物成千上万根线都在以同一个频率摆动,所有的能量都汇聚在两个点上,所以它们极亮。
-
脏污的能量:是因为能量高度分散。脏污通常只有一块,它没有重复的节奏。在频域里,它的能量像泼出去的水一样,均匀地洒在了几乎所有的频率坐标上。
-
结果:脏污在频谱中虽然也是对称的,但它表现为一层薄薄的、肉眼几乎看不见的“雾”,而不是那几个耀眼的“星点”。
我们发现图中频谱图似乎是对称的,是因为使用了np.fft.fftshift(f) 图像中心点的坐标变成了 (0,0)(0,0),它代表的是直流分量 (DC Component),即图像的平均亮度。
- 横轴 u:代表水平方向的频率。
中心右侧 (+u):代表从左到右变化的波。
中心左侧 (−u):代表从右到左变化的波。
- 纵轴 v:代表垂直方向的频率。
中心下方 (+v):代表从上到下变化的波。
中心上方 (−v):代表从下到上变化的波。
为什么我们要抹去“亮点”?
在代码逻辑中,我们敢于抹去最亮的点,是因为:
-
织物的能量高度集中:因为织物纹理在全图到处都是,这种巨大的“整体力量”在频域汇聚成了几个极亮的点。
-
脏污的能量高度分散:脏污只占图像的一小块。它虽然在空域很亮(比如前面那个例子250),但在频域里,它的能量被分摊到了所有的频率点上。
-
织物纹理: 在频域里表现为位置固定的“孤岛”(亮点)。就像乐谱上一个持续不变的高音。
-
脏污: 脏污通常是成块的、突发的,在频域里它表现为“宽带信号”,即能量分散在很多频率上。
由于脏污不是“节奏”的一部分,它无法被完全静音。当声音重新响起来时(逆变换),剩下的唯一声音就是那个脏污的“杂音”。
小结: 本文结构顺序可能不够严谨,但是对傅里叶变换做了还算详细的解释,最后使用傅里叶变换对织物的脏污进行了检测。总结看来,就是我们使用"探针"复指数的欧拉公式,去一个个尝试频率w,如果积分结果很大,说明该信号分解的成分中包含有w这个频率。我们检测脏污时,我们要注意,脏污的能量是分布在几乎所有频率中的,我们对主要的亮点进行抹除,也就是抹去了背景纹理,再还原时,纹理相对脏污而言变暗淡了,脏污相对变清晰了(抹去最亮的点,也只是抹去了脏污少部分能量),然后对还原的灰度图(在绝大多数经典的数字图像处理算法中,傅里叶变换处理的是灰度图。)阈值二值化就能区分背景与脏污了,这样我们就找到了脏污了。最后一点,我们要明白傅里叶变换的本质是坐标系变换,它把喜好以时间为基底的坐标系(每秒是一个维度),变换到了以频率为基底的正交坐标系(每一个频率是一个维度),对脏污的亮点进行抹除可以看成对纹理轴的的投影给抑制了。
参考资料:
https://www.bilibili.com/video/BV1eUHjzgEAd/?spm_id_from=333.337.searchcard.all.click&vd_source=0c4e721fe7e2114cadd1fdd8304f0167

被称为这两个函数的内积。
浙公网安备 33010602011771号