前言
最近復現音樂驅動舞蹈的文章《Dancing-to-Music Character Animation》,用到了與傅里葉變換很相似的稱爲常Q變換的方法去分割音樂,所以對傅里葉變換做了一個小了解,本文不深入各種亂糟糟的理論,比如什麼蝶形算法啥的,單純討論離散傅里葉變換(DFT),我們常見的是快速傅里葉變換(FFT),其實FFT是對DFT的一個計算優化,主要是剔除DFT裏面有週期性之類的被冗餘計算,但是FFT的算法有點小複雜,有興趣深入理論的請移步如下幾篇博文:
An Introduction to the Fast Fourier Transform
如果想仔細學習FFT,最好是看完上述推薦的博文並額外找資料,我是不想看了,因爲我看着看着發現自己掉頭髮了o(╯□╰)o
簡介
傅里葉變換意思就是任何一個輸入信號都可以使用多個餘弦波疊加而成,簡單的說就是把時序信號轉換成頻域信息。我們最終需要的就是找到這些餘弦波的相關參數:幅值,相位。
複習一下三角函數的標準式:
代表振幅,函數週期是 ,頻率是週期的倒數 , 是函數初相位, 在信號處理中稱爲直流分量。
在很多工具的實現中,餘弦波的個數就是信號長度,但是在理論公式中會出現一個參數N,是採樣點,通常稱爲N點FFT。
上述公式就是DFT的求解方法了,不考慮它的優化方法FFT,我們直接在
matlab
中碼公式,最後與FFT
的結果做對比即可驗證寫的算法對不對。
實例
假設我們的輸入信號是函數是
可以發現直流分量是0.2,以及兩個餘弦函數的疊加,餘弦函數的幅值分別爲0.7和0.2,頻率分別爲50和100,初相位分別爲20度和70度。
畫出信號圖:
Fs = 1000; % Sampling frequency
T = 1/Fs; % Sampling period
L = 1000; % Length of signal
t = (0:L-1)*T; % Time vector
S = 0.2+0.7*cos(2*pi*50*t+20/180*pi) + 0.2*cos(2*pi*100*t+70/180*pi) ;
plot(1000*t(1:50),S(1:50))
title('Signal Corrupted with Zero-Mean Random Noise')
xlabel('t (milliseconds)')
ylabel('X(t)')
FFT變換
先使用matlab
默認的快速傅里葉變換函數FFT
求解疊加餘弦的各參數
Y = fft(S);
P2 = abs(Y/L);
P1 = P2(1:L/2+1);
P1(2:end-1) = 2*P1(2:end-1);
首先直接對原始信號進行傅里葉變換得到 ,它包含實部與虛部,然後求解歸一化後 各項的模得到 ,由於matlab
求解的結果包含對稱的兩個頻譜,稱爲雙側頻譜,我們只需要取一半得到 ,此時只需要將除第一個元素外的元素乘以2即可得到幅值信息,其實求解的根本就是來源於 , 有多少項,就說明求解了多少個疊加的餘弦波,接下來解釋如何求解各參數:
- 直流分量:就是Y的第一個值除以採樣頻率,一般來說是非複數
- 頻率: ,本例中採樣頻率是1000,長度也是1000,那麼 的第二項頻率就是1,第三項頻率是2,其實最終情況下,我們選取頻率不接近0的值。
- 幅值:
- 初相位: 轉角度製表示
從 的圖中,我們很容易看出來幅值不接近0的位置分別是0,50,100附近,其實我們去看具體的位置發現是1,51,101,此三個位置的 值分別爲:
>> Y(1)
ans =
200.0000
>> Y(51)
ans =
3.2889e+02 + 1.1971e+02i
>> Y(101)
ans =
34.2020 +93.9693i
那麼按照描述,我們得到:
直流分量:
頻率:第51項和101項的頻率分別爲50和100
幅值: 同理
初相位:
>> rad2deg(atan2(imag(Y(51)),real(Y(51)))) ans = 20.0000 >> rad2deg(atan2(imag(Y(101)),real(Y(101)))) ans = 70.0000
【注1】: 在實際應用中,一般讓餘弦波的的數量與信號長度相同,如果不相同,那就是理論中常說的N點FFT
【注2】:幅值是通過模求解的,但是模一般都是正數,如果疊加信號的幅值是複數呢?不要忘記了 ,也就是說如果幅值是複數,那麼只需要在正數幅值的基礎上變化一初相位。比如把例子的函數換成:
那麼求解頻率50對應餘弦波的賦值爲+0.7,初相位爲-160
IFFT變換
顧名思義,IFFT就是逆傅里葉變換,用Y重構信號,其實我們通過Y都已經分析出了原始信號所需的餘弦波的各參數,肯定能重構原始數據,這裏還是做個實驗吧,用IFFT
函數:
figure
pred_X=ifft(Y);
plot(pred_X,'r-')
hold on
plot(S,'b*')
DFT變換
按照公式手擼DFT,看看計算得到 與matlab
自帶的FFT
結果是否一致
%% DFT變換
% N
% X(k) = sum x(n)*exp(-j*2*pi*(k-1)*(n-1)/N), 1 <= k <= N.
% n=1
DFT_X=zeros(1,L);
N=L;
for k=1:L
for n=1:N
DFT_X(k)=DFT_X(k)+S(n)*exp(-1j*2*pi*(k-1)*(n-1)/N);
end
end
P2=abs(DFT_X/L);
P1 = 2*P2(1:L/2+1);
f = Fs*(0:(L/2))/L;
figure
plot(f,P1)
xlabel('頻率')
ylabel('振幅')
title('DFT變換')
再計算與FFT
求解的結果的誤差
%% FFT變換
FFT_X=fft(S);
figure
plot(abs(FFT_X-DFT_X))
title('DFT和FFT誤差')
IDFT
同樣按照公式手擼逆離散傅里葉變換
%% IDFT變換
% N
% x(n) = (1/N) sum X(k)*exp( j*2*pi*(k-1)*(n-1)/N), 1 <= n <= N.
% k=1
DFT_rec_x=zeros(1,k);
for n=1:L
for k=1:L
DFT_rec_x(n)=DFT_rec_x(n)+DFT_X(k)*exp( 1j*2*pi*(k-1)*(n-1)/N);
end
end
DFT_rec_x=DFT_rec_x./N;
rec_err=real(DFT_rec_x)-S;
figure
plot(rec_err)
title('IFFT數據重構誤差')
與IFFT
的結果對比一下:
%% IFFT變換
FFT_rec_x=ifft(FFT_X);
figure
plot(abs(DFT_rec_x-FFT_rec_x))
title('IDFT和IFFT誤差')
後記
折騰了這麼多,其實就是爲了一個字:懶。爲了避免複雜的FFT的理論理解,我直接按照DFT的公式碼代碼,取得了與FFT一樣的結果,對於不喜歡複雜理論的同志,可以在代碼實現FFT的時候直接採用DFT的代碼作爲替代品,雖然時間複雜度增大很多,但是理論理解起來簡單很多倍不是。
貼一下文章代碼,此外還有一個FFT的手動實現:鏈接:https://pan.baidu.com/s/1mUdclEQ3tgQUvZQ4XffN3g 密碼:08tg
等我不掉頭髮了,再去糾結FFT的蝶形算法^_^