别怕数学!用Python从零实现图像傅里叶变换(附完整代码与频谱图分析)
别怕数学!用Python从零实现图像傅里叶变换(附完整代码与频谱图分析)
当你第一次听说“傅里叶变换”时,脑海中是不是立刻浮现出一堆令人望而生畏的数学符号?别担心,这篇文章将用最直观的方式带你理解这个看似高深的概念。就像厨师通过品尝就能分辨出一道菜的配料比例,傅里叶变换让我们能够“品尝”图像的频率成分——这正是图像处理的魔法所在。
我们将完全从零开始,用Python和NumPy一步步构建自己的傅里叶变换实现。没有复杂的公式推导,只有清晰的代码和可视化的频谱图。读完本文后,你将能够:
- 理解傅里叶变换如何将图像分解为不同频率的波
- 手动实现一维和二维离散傅里叶变换(DFT)
- 使用频谱图直观分析图像特征
- 比较自实现与NumPy内置FFT的性能差异
1. 傅里叶变换的直观理解
想象你正在听一首交响乐。虽然听到的是整体声音,但实际上它由许多不同频率的乐器声组合而成。傅里叶变换就像是一个“音乐分解器”,能够将复杂的交响乐分解为各个乐器的单独频率成分。
对于图像处理也是如此。任何图像都可以看作是由不同频率的“波”组成的:
- 低频成分:对应图像中缓慢变化的区域,如大面积的天空或墙面
- 高频成分:对应图像中快速变化的边缘和细节
1.1 从正弦波到图像分解
让我们用简单的正弦波来建立直觉。下面这段代码生成三个不同频率的正弦波,并将它们相加:
import numpy as np
import matplotlib.pyplot as plt
# 生成三个不同频率的正弦波
x = np.linspace(0, 2*np.pi, 1000)
wave1 = 0.5 * np.sin(1 * x) # 低频
wave2 = 0.3 * np.sin(5 * x) # 中频
wave3 = 0.2 * np.sin(10 * x) # 高频
# 组合波
combined = wave1 + wave2 + wave3
# 可视化
plt.figure(figsize=(12, 8))
plt.subplot(4, 1, 1); plt.plot(x, wave1); plt.title('低频波')
plt.subplot(4, 1, 2); plt.plot(x, wave2); plt.title('中频波')
plt.subplot(4, 1, 3); plt.plot(x, wave3); plt.title('高频波')
plt.subplot(4, 1, 4); plt.plot(x, combined); plt.title('组合波')
plt.tight_layout()
plt.show()
傅里叶变换的逆过程就是:给定组合波,如何找出其中的wave1、wave2和wave3?这正是我们要实现的核心功能。
2. 一维离散傅里叶变换实现
现在让我们动手实现一维DFT。虽然NumPy已经有现成的fft函数,但自己实现一次会让你真正理解其工作原理。
2.1 DFT的矩阵形式
离散傅里叶变换可以用矩阵乘法表示:
F = W · f
其中:
f是输入信号(长度为N的向量)W是N×N的变换矩阵F是输出的频域表示
变换矩阵W的每个元素为:
W[m, n] = e^(-2πi * m*n / N)
这里i是虚数单位。用Python实现如下:
def dft_1d(signal):
N = len(signal)
n = np.arange(N)
k = n.reshape((N, 1))
# 创建变换矩阵
W = np.exp(-2j * np.pi * k * n / N)
# 矩阵乘法得到频域表示
freq_domain = np.dot(W, signal)
return freq_domain
2.2 测试一维DFT
让我们用这个函数分析一个简单信号:
# 创建测试信号:两个正弦波的组合
N = 1000
t = np.linspace(0, 1, N)
signal = 0.5*np.sin(2*np.pi*5*t) + 0.3*np.sin(2*np.pi*20*t)
# 计算DFT
freq_domain = dft_1d(signal)
# 计算频率轴
freqs = np.fft.fftfreq(N, d=1/N)
# 可视化
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(t, signal)
plt.title('时域信号')
plt.subplot(1, 2, 2)
plt.plot(freqs[:N//2], np.abs(freq_domain[:N//2]))
plt.title('频域表示')
plt.xlabel('频率(Hz)')
plt.tight_layout()
plt.show()
你会看到频域图中在5Hz和20Hz处有两个明显的峰值,这正是我们合成信号中使用的频率。
3. 二维图像傅里叶变换
图像是二维信号,因此我们需要扩展一维DFT到二维。幸运的是,二维DFT可以通过分别在行和列上应用一维DFT来实现。
3.1 可分离性实现
二维DFT的可分离性意味着我们可以:
- 对图像的每一行应用一维DFT
- 对中间结果的每一列再次应用一维DFT
实现代码如下:
def dft_2d(image):
M, N = image.shape
# 先对每一行做一维DFT
row_transformed = np.zeros_like(image, dtype=np.complex128)
for i in range(M):
row_transformed[i, :] = dft_1d(image[i, :])
# 再对每一列做一维DFT
dft_image = np.zeros_like(image, dtype=np.complex128)
for j in range(N):
dft_image[:, j] = dft_1d(row_transformed[:, j])
return dft_image
3.2 频谱中心化
原始DFT结果的低频成分位于四角,高频在中心。为了方便观察,我们通常将低频移到中心:
def fftshift(dft_image):
"""将频域图像中心化"""
M, N = dft_image.shape
# 沿两个方向循环移动一半尺寸
shifted = np.roll(dft_image, M//2, axis=0)
shifted = np.roll(shifted, N//2, axis=1)
return shifted
4. 图像频谱可视化与分析
现在我们可以加载真实图像并分析其频谱特性了。
4.1 加载图像并计算频谱
from skimage import data, color
# 加载示例图像并转为灰度
image = color.rgb2gray(data.astronaut())
M, N = image.shape
# 计算DFT
dft_result = dft_2d(image)
dft_shifted = fftshift(dft_result)
# 计算幅度谱(取对数便于显示)
magnitude_spectrum = np.log(np.abs(dft_shifted) + 1) # +1避免log(0)
4.2 可视化结果
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(image, cmap='gray')
plt.title('原始图像')
plt.subplot(1, 2, 2)
plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('幅度谱')
plt.tight_layout()
plt.show()
典型频谱图会显示:
- 中心亮区域:代表图像的低频成分(整体亮度)
- 从中心向外辐射的亮线:代表图像中的边缘和纹理(高频)
4.3 与NumPy FFT对比
为了验证我们的实现,可以将其与NumPy内置的FFT进行比较:
# 使用NumPy的FFT
np_fft = np.fft.fft2(image)
np_shifted = np.fft.fftshift(np_fft)
np_magnitude = np.log(np.abs(np_shifted) + 1)
# 计算差异
difference = np.abs(magnitude_spectrum - np_magnitude)
print(f"最大差异: {np.max(difference):.6f}")
你会发现两者结果几乎相同,最大差异通常小于1e-10,验证了我们实现的正确性。
5. 实际应用与性能优化
虽然我们的实现正确,但直接使用矩阵乘法的DFT计算复杂度为O(N²),而FFT(快速傅里叶变换)算法可以将其降低到O(N log N)。
5.1 理解FFT的优势
让我们比较两者的速度差异:
import time
# 测试不同尺寸下的计算时间
sizes = [32, 64, 128, 256]
dft_times = []
fft_times = []
for size in sizes:
test_img = np.random.rand(size, size)
# 测试DFT
start = time.time()
dft_2d(test_img)
dft_times.append(time.time() - start)
# 测试FFT
start = time.time()
np.fft.fft2(test_img)
fft_times.append(time.time() - start)
# 绘制比较图
plt.figure(figsize=(10, 5))
plt.plot(sizes, dft_times, 'o-', label='我们的DFT实现')
plt.plot(sizes, fft_times, 's-', label='NumPy FFT')
plt.xlabel('图像尺寸')
plt.ylabel('计算时间(秒)')
plt.legend()
plt.title('DFT与FFT性能比较')
plt.grid(True)
plt.show()
随着图像尺寸增大,DFT的计算时间呈平方级增长,而FFT仅呈线性增长(在对数尺度下)。
5.2 频域滤波示例
理解傅里叶变换的真正价值在于频域滤波。例如��我们可以尝试去除图像中的高频噪声:
# 添加高频噪声
noisy = image + 0.1 * np.random.randn(*image.shape)
# 计算噪声图像的DFT
noisy_dft = dft_2d(noisy)
noisy_shifted = fftshift(noisy_dft)
# 创建低通滤波器(保留中心区域)
crow, ccol = M//2, N//2
mask = np.zeros((M, N))
r = 30 # 保留半径
mask[crow-r:crow+r, ccol-r:ccol+r] = 1
# 应用滤波器
filtered_dft = noisy_shifted * mask
# 反变换回空间域
filtered_shifted = np.fft.ifftshift(filtered_dft)
filtered_image = np.fft.ifft2(filtered_shifted).real
# 可视化结果
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(noisy, cmap='gray')
plt.title('带噪声图像')
plt.subplot(1, 2, 2)
plt.imshow(filtered_image, cmap='gray')
plt.title('频域滤波后')
plt.tight_layout()
plt.show()
你会看到高频噪声被有效去除,虽然图像变得稍微模糊(丢失了一些细节)。
6. 完整代码与扩展建议
以下是本文涉及的所有核心函数整合:
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, color
def dft_1d(signal):
N = len(signal)
n = np.arange(N)
k = n.reshape((N, 1))
W = np.exp(-2j * np.pi * k * n / N)
return np.dot(W, signal)
def dft_2d(image):
M, N = image.shape
row_transformed = np.zeros_like(image, dtype=np.complex128)
for i in range(M):
row_transformed[i, :] = dft_1d(image[i, :])
dft_image = np.zeros_like(image, dtype=np.complex128)
for j in range(N):
dft_image[:, j] = dft_1d(row_transformed[:, j])
return dft_image
def fftshift(dft_image):
M, N = dft_image.shape
shifted = np.roll(dft_image, M//2, axis=0)
shifted = np.roll(shifted, N//2, axis=1)
return shifted
# 示例使用
image = color.rgb2gray(data.astronaut())[:256, :256] # 裁剪为256x256
dft_result = dft_2d(image)
dft_shifted = fftshift(dft_result)
magnitude_spectrum = np.log(np.abs(dft_shifted) + 1)
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1); plt.imshow(image, cmap='gray'); plt.title('原始图像')
plt.subplot(1, 2, 2); plt.imshow(magnitude_spectrum, cmap='gray'); plt.title('幅度谱')
plt.show()
扩展建议:
- 尝试实现快速傅里叶变换(FFT)的递归版本
- 探索不同的频域滤波器(高通、带通、陷波)
- 将傅里叶变换应用于音频信号处理
- 学习短时傅里叶变换(STFT)用于时频分析
通过这次从零实现傅里叶变换的旅程,你会发现那些看似复杂的数学概念,当用代码具象化后,其实非常直观和有趣。
更多推荐


所有评论(0)