前言
最近复现音乐驱动舞蹈的文章《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的蝶形算法^_^