快速卷积是一种使用快速傅里叶变换(FFT)来有效计算两个序列(信号、函数等)卷积的方法。快速卷积对于数字信号处理、图像处理、音频处理等领域至关重要,因为它大大提高了计算卷积的效率。
卷积的概念
卷积是一种数学运算符,用于两个函数(信号)的合成。在信号处理中,卷积可以表示为:
( f ∗ g ) [ n ] = ∑ m = − ∞ ∞ f [ m ] ⋅ g [ n − m ] (f * g)[n] = \sum_{m=-\infty}^{\infty} f[m] \cdot g[n - m] (f∗g)[n]=∑m=−∞∞f[m]⋅g[n−m]
其中, f f f 和 g g g 是两个序列,( * ) 表示卷积运算。这种计算对于序列的每一个点,需要将一个序列翻转并且平移,然后与另一个序列相乘,最后将乘积求和得到结果序列的对应点的值。
快速傅里叶变换(FFT)
快速傅里叶变换是一种算法,能够快速计算序列的离散傅里叶变换(DFT)及其逆变换。DFT将时域(或空间域)信号转换为频域信号,而逆DFT(IDFT)则将频域信号转换回时域。FFT的一个关键特点是其复杂度是 O ( N log N ) O(N \log N) O(NlogN),相比直接计算的 O ( N 2 ) O(N^2) O(N2) 要小得多。
快速卷积的步骤
下面是执行快速卷积的具体步骤:
- 零填充(Zero-padding):
- 给定两个序列 f [ n ] f[n] f[n] 和 g [ n ] g[n] g[n],我们首先要将它们扩展到至少 N + M − 1 N + M - 1 N+M−1的长度,其中 N N N和 M M M 分别是 f f f 和 g g g 的长度。
- 这样做是为了避免卷积时的循环效应,并且能够在FFT中使用2的幂次大小,提高计算效率。
- 计算FFT:
- 对扩展后的序列 f [ n ] f[n] f[n] 和 g [ n ] g[n] g[n] 分别计算FFT,得到它们的频域表示 F [ k ] F[k] F[k] 和 G [ k ] G[k] G[k])。
- 频域乘法:
- 在频域中,将 F [ k ] F[k] F[k]和 G [ k ] G[k] G[k] 进行逐点乘法,得到 H [ k ] = F [ k ] ⋅ G [ k ] H[k] = F[k] \cdot G[k] H[k]=F[k]⋅G[k]。这个步骤等价于时域中的卷积操作。
- 计算逆FFT(IFFT):
- 对 H [ k ] H[k] H[k] 进行IFFT,将频域的乘积结果转换回时域,得到序列 h [ n ] h[n] h[n],这是 ( f [ n ] ( f[n] (f[n] 和 g [ n ] g[n] g[n]的卷积结果。
- 裁剪(Trimming):
- 如果需要,对 h [ n ] h[n] h[n]进行裁剪,只保留有效的卷积结果,即前 ( N + M − 1 ( N + M - 1 (N+M−1 个样本。
每一步操作的具体情况
- 零填充 是为了满足FFT算法的需要,同时确保卷积结果不受边缘效应影响。
- FFT 是快速卷积中最核心的步骤,它将大规模计算的复杂度从 O ( N 2 ) O(N^2) O(N2) 降低到 O ( N log N ) O(N \log N) O(NlogN)。
- 点乘 在频域中执行是因为它等同于时域中的卷积,根据卷积定理,两个信号的卷积等于它们傅里叶变换的乘积。
- IFFT 是必要的因为我们最终需要的是时域信号,而不是频域信号。
- 裁剪 是因为FFT操作中引入的零填充并不是卷积的一部分,因此需要去除。
示例
让我们通过一个具体的例子来说明快速卷积的过程。假设我们有两个序列,分别是 ( f[n] ) 和 ( g[n] ),它们的值如下:
f [ n ] = 1 , 2 , 3 f[n] = {1, 2, 3} f[n]=1,2,3
g [ n ] = 4 , 5 g[n] = {4, 5} g[n]=4,5
为了进行快速卷积,我们将遵循以下步骤:
- 零填充(Zero-padding):
首先,确定两个序列的长度,这里 f [ n ] f[n] f[n] 的长度为3, g [ n ] g[n] g[n]的长度为2。根据卷积的性质,卷积操作结果的长度将是 N f + N g − 1 = 3 + 2 − 1 = 4 N_f + N_g - 1 = 3 + 2 - 1 = 4 Nf+Ng−1=3+2−1=4。所以我们至少需要对序列进行零填充以达到长度为4。但是,为了使用FFT的优势(特别是当序列长度为2的幂时),我们选择下一个更大的2的幂的长度,这里为4(已经是2的幂)。
所以零填充后的序列为:
f [ n ] = 1 , 2 , 3 , 0 f[n] = {1, 2, 3, 0} f[n]=1,2,3,0
g [ n ] = 4 , 5 , 0 , 0 g[n] = {4, 5, 0, 0} g[n]=4,5,0,0 - 快速傅里叶变换(FFT):
接下来,我们对两个序列进行FFT,得到它们的频域表示。这里使用 F [ k ] F[k] F[k] 和 G [ k ] G[k] G[k] 表示FFT结果。
使用某个FFT算法或者库函数得到了以下结果:
F [ k ] = [ 6. + 0. j , − 2. − 2. j , 2. + 0. j , − 2. + 2. j ] F[k] = [ 6.+0.j, -2.-2.j, 2.+0.j, -2.+2.j] F[k]=[6.+0.j,−2.−2.j,2.+0.j,−2.+2.j]
G [ k ] = [ 9. + 0. j , 4. − 5. j , − 1. + 0. j , 4. + 5. j ] G[k] = [ 9.+0.j, 4.-5.j, -1.+0.j, 4.+5.j] G[k]=[9.+0.j,4.−5.j,−1.+0.j,4.+5.j] - 频域乘法:
现在,我们在频域中对 F [ k ] F[k] F[k] 和 G [ k ] G[k] G[k] 进行逐点乘法。
结果为: H [ k ] = F [ k ] ⋅ G [ k ] = [ 54. + 0. j , − 18. + 2. j , − 2. + 0. j , − 18. − 2. j ] H[k] = F[k] \cdot G[k] = [ 54.+0.j, -18.+2.j, -2.+0.j, -18.-2.j] H[k]=F[k]⋅G[k]=[54.+0.j,−18.+2.j,−2.+0.j,−18.−2.j] - 计算逆FFT(IFFT):
然后,我们对 ( H[k] ) 进行逆FFT以转换回时域: h [ n ] = IFFT ( H [ k ] ) h[n] = \text{IFFT}(H[k]) h[n]=IFFT(H[k])
得到的结果是 h [ n ] = [ 4. + 0. j , 13. + 0. j , 22. + 0. j , 15. + 0. j ] h[n] = [ 4.+0.j, 13.+0.j, 22.+0.j, 15.+0.j] h[n]=[4.+0.j,13.+0.j,22.+0.j,15.+0.j]。 - 裁剪:
由于我们知道最终的卷积长度应该为 N f + N g − 1 = 4 N_f + N_g - 1 = 4 Nf+Ng−1=4,我们可以直接取 h [ n ] h[n] h[n] 的前4个值 [ 4. , 13. , 22. , 15. ] [ 4., 13., 22., 15.] [4.,13.,22.,15.]作为最终结果。
这里是一个使用Python中的numpy
库来执行快速卷积操作的例子。numpy
提供了FFT功能,使得这个过程相对直接。
import numpy as np# 定义两个序列
f = np.array([1, 2, 3])
g = np.array([4, 5])# 1. 零填充
# 序列的长度分别是3和2,卷积后的长度应该是3+2-1=4
# 我们对两个序列进行零填充以匹配这个长度
N = len(f) + len(g) - 1
f_padded = np.pad(f, (0, N - len(f)))
g_padded = np.pad(g, (0, N - len(g)))print(f'零填充后的 f: {f_padded}')
print(f'零填充后的 g: {g_padded}')# 2. 计算FFT
F = np.fft.fft(f_padded)
G = np.fft.fft(g_padded)print(f'FFT后的 F: {F}')
print(f'FFT后的 G: {G}')# 3. 频域乘法
H = F * Gprint(f'频域乘法后的 H: {H}')# 4. 计算逆FFT(IFFT)
h = np.fft.ifft(H)print(f'逆FFT后的 h: {h}')# 5. 裁剪 (由于我们使用了合适的零填充,所以这里不需要裁剪)
# 但是由于IFFT的结果可能包含非常小的虚部(由于计算误差),我们取实部
h_real = np.real(h)print(f'裁剪(取实部)后的 h: {h_real}')'''
零填充后的 f: [1 2 3 0]
零填充后的 g: [4 5 0 0]
FFT后的 F: [ 6.+0.j -2.-2.j 2.+0.j -2.+2.j]
FFT后的 G: [ 9.+0.j 4.-5.j -1.+0.j 4.+5.j]
频域乘法后的 H: [ 54.+0.j -18.+2.j -2.+0.j -18.-2.j]
逆FFT后的 h: [ 4.+0.j 13.+0.j 22.+0.j 15.+0.j]
裁剪(取实部)后的 h: [ 4. 13. 22. 15.]
'''
这段代码首先导入numpy
库,然后定义了两个序列f
和g
。接下来,我们对这两个序列进行零填充以满足FFT计算,并且保证卷积结果的长度正确。然后,我们对这两个序列进行FFT,将它们转换到频域。在频域中,我们进行点乘操作,这相当于时域中的卷积。接着,我们对乘积进行逆FFT操作,将结果转换回时域。最后一步,我们通常需要裁剪掉结果中的额外零,但由于我们初始时就使用了正确的长度,这里不需要额外裁剪。我们只取实部,因为IFFT可能会产生非常小的虚数部分,这通常是由于计算中的舍入误差。
结论
快速卷积是一个在工业和科研中非常有用的工具。它利用FFT大大加速了卷积计算,特别是对于大规模数据。然而,它也引入了需要考虑的因素,比如零填充和边界效应,以及在某些实时系统中FFT可能引入的延迟。尽管如此,快速卷积在许多领域仍然是首选的计算方法。