快速入門 FFT

快速入門 FFT

本文適合不想完全瞭解 FFT 原理不過想實現 FFT 算法的人

FFT 簡介

FFT 也就是常說的快速傅里葉變換,其實本質上和傅里葉變換沒有什麼不同,也就是通過算法減少了一些乘法和加法的運算;DFT 的時間複雜度是 N² 級別的,而 FFT 的卻是對數級別的複雜度,從而在 FFT 在數據量大的時候運算時間並不會高太多,這就使得 FFT 具有了在工程上的使用價值

FFT 算法簡介

在這裏插入圖片描述

上面這張圖是來自維基的介紹,簡單來說的話,FFT 將數據按照索引地址按奇數和偶數分爲兩半,這兩半數據還可以繼續抽取,直到最後分解爲單獨的數據;然後利用了單位根的一些特性,減少了一些乘加步驟

但是順序輸入的數據,最後得到的結果是亂序的;爲了得到順序的輸出,一般我們會將輸入數據提前進行整理;因爲 FFT 的輸出索引地址會位反轉,可以利用這個特性提前整理好輸入數據,例如:輸入 [1 2 3 4 5 6 7 8] 就整理爲 [1 5 3 7 2 6 4 8]

FFT 第一個版本實現

最開始我爲了能弄懂 FFT ,我就通過手算來了解 FFT 的過程,也就是通過上面那張圖中的公式,一級級計算 FFT 的結果

最後根據手算的步驟實現了下面這個版本,其中單位根的復指數計算就直接手動計算完存表表了,在實現後發現只能夠適用 4 個數的輸入

import cmath

table = [1+0j,2+0j,3+0j,4+0j]
rotate_table = [1+0j,0-1j,-1+0j,0+1j]
result = []

N = 4
k = 0
r = 0
tmp = 0
while k < len(table):
    while r < len(table)/2:
        tmp = tmp + table[2*r]*rotate_table[(2*r*k)%4] + table[2*r+1]*rotate_table[((2*r+1)*k)%4]
        r = r + 1
    result.append(tmp)
    tmp = 0
    k = k + 1
    r = 0

print(result)

運行結果如下

PS D:\Files\dsp\python> python.exe .\my_fft.py
[(10+0j), (-2+2j), (-2+0j), (-2-2j)]

matlab fft 的結果如下

>> x = [1 2 3 4];X = fft(x)
X =

   10 +  0i   -2 +  2i   -2 +  0i   -2 -  2i

FFT 第二個版本的實現

在這裏插入圖片描述

最開始看到書上的 FFT 特點的時候,還不是太瞭解,所以上面的那個實現非常的簡陋

原址運算的特點使得 FFT 佔用空間爲 N,每個蝶形運算結束後,輸出節點的兩個值直接放到原輸入的兩個節點中;這個特點給了我對算法的基本思路,算法是從整理輸入數據順序開始的

不滿足 2 的 N 次方的情況直接不考慮了

通過觀察,可以看出每層蝶形運算的規律:如果將每層的蝶形運算分組的話,每層的蝶形運算的組數和每組的蝶形運算次數的乘積是一定的,爲 N/2。具體可以參考這篇文章

基於這些特點就有了下面這個第二個版本的 FFT

import cmath
import math

def reverseBits(n, bits): # 整理數據子函數
    return int(bin(n)[2:].rjust(int(bits), '0')[::-1], base=2)

table = [1,2,3,4,5,6,7,8] # 輸入數據
result = [] # 存儲結果
L = math.log(len(table), 2) # 蝶形運算的層數
N = len(table) # 輸入數據的長度

tmp = 0
tmp_i = 0
while tmp_i < N/2: # 整理數據過程
    tmp = table[tmp_i]
    table[tmp_i] = table[reverseBits(int(tmp_i), int(L))]
    table[reverseBits(tmp_i, L)] = tmp
    tmp_i = tmp_i + 1

print(table)

k = 0 # 一組裏的 k 次蝶形
m = 1
i = len(table)/2 # i 組蝶形
j = 0 # 第 j 組蝶形

while m <= L:
    while j < i: # i組蝶形 4/2=2
        while k < N/2/i: # k次蝶形運算
            r = (j*(2**m) + k)*(2**(L-m))
            tmp_1 = table[j*(2**m) + k]
            tmp_2 = table[j*(2**m) + k+2**(m-1)] * cmath.exp(-1j*2*cmath.pi*r/N)
            table[j*(2**m) + k] = tmp_1 + tmp_2
            table[j*(2**m) + k+2**(m-1)] = tmp_1 - tmp_2
            k = k + 1
        j = j + 1
        k = 0
    i = i/2
    j = 0
    m = m+1

print(table)

運行結果如下:

PS D:\Files\dsp\python> python.exe .\my_fft_2.py
[1, 5, 3, 7, 2, 6, 4, 8]
[(36+1.3471114790620885e-14j), (-4.000000000000009+9.656854249492383j), (-4.000000000000006+3.9999999999999982j), (-4.000000000000005+1.656854249492377j), (-4-1.0042103753008295e-14j), (-3.9999999999999942-1.6568542494923832j), (-3.999999999999994-4.000000000000002j), (-3.999999999999991-9.656854249492376j)]

matlab 中的結果如下:

>> x = [1 2 3 4 5 6 7 8];X = fft(x)
X =

 Columns 1 through 4:

   36.0000 +  0.0000i   -4.0000 +  9.6569i   -4.0000 +  4.0000i   -4.0000 +  1.6569i

 Columns 5 through 8:

   -4.0000 +  0.0000i   -4.0000 -  1.6569i   -4.0000 -  4.0000i   -4.0000 -  9.6569i

和 matlab 中的對比還存在精度的問題,這個與 python 的 cmath 庫提供的指數運算有關,我覺得可以考慮像第一版的實現一樣,將指數運算優化掉,不過暫時不打算不去考慮這個問題了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章