本篇文章主要介绍快速傅里叶变换(FFT)的优化原理,基-2FFT算法的推导、实现及用FFT实现的线性卷积。
主要参考知乎[精品讲义]—快速傅里叶变换(Fast Fourier Transformation)以及一些数字信号处理的书籍整理而成,参考引用在文末。
目录
- 1. 快速傅里叶变换(FFT)的优化原理
- 1.1 从表达式入手进行优化
- 1.2 优化举例
- 2. 基-2FFT算法的推导
- 3. 基-2FFT算法的实现
- 4.用FFT实现的线性卷积
1. 快速傅里叶变换(FFT)的优化原理
1.1 从表达式入手进行优化
FFT是从DFT中优化而来的,首先,给出对一个长度为N的离散信号的DFT表达式:
我们来研究一下它的计算过程,首先对于一个值来说,表示要做次的加法,而每一次的加法中都需要做的乘法运算,总计为次乘法运算。这就意味着这个取值范围中个的值共需要次的乘法和次加法。这样的算法复杂度当取值很大的时候是不可接受的,而优化的出发点就落在这一项的性质上。
首先定义旋转因子:
将(2)式代入(1)式中得
旋转因子的周期性:
或表示为
以及对称性:
有了这两个性质之后,设并将方程(3)分解为两部分:
其中,偶数序列为:
奇数序列为:
利用周期性与对称性以及把序列分解为奇偶序列(分治及递归的思想),可以将大部分计算(那些反复计算的部分)消除掉,从而能降低与的二次依赖关系,下面的1.2小节通过例子来初步说明优化的原理。
1.2 优化举例
现在,利用一个长度为的信号进行优化举例,通过式(3)可以得到:
将该式子展开可以得到:
将(11)写为矩阵形式为:
现在可以使用式(5)和式(6)来简化式(12)中的矩阵。
利用式(5)能推导得到:
这样式(12)中有:
经过简化后式(12)的矩阵为:
利用周期性式(5)推导可得:
再利用对称性式(6)推导得到:
由式(16)及(17)化简式(15)得:
现在调换矢量的第二行和第三行,与此同时调换矩阵的第二列和第三列,得到
现定义
对进行分块:
对矢量进行分块:
将式(21)和(22)代入式(19)中有:
显然,矩阵只与矢量有关,而矩阵只与矢量有关。
根据式(23),将矩阵方程式(19)写成方程组的形式:
现在定义
将式(25)代入到式(24)可以得到:
由式(25)我们可以知道,中间变量都是通过一次加法计算得到,而通过式(26)又可以知道,各是通过一次加法得到的。简化后,一个长度的序列的加法次数为次,而简化之前则要次加法。见式(26)的右边,乘法只需要做次分别如下所示:
可见,获得中间变量的过程中是没有进行乘法运算的,相比于简化之前的显著是减少的。
从以上的例子可以知道,优化的原理主要是①通过周期性和对称性减少旋转因子的计算次数;②通过奇偶分解的分治思想以及设置中间变量减少计算次数。以下第二节是基-2FFT算法的推导。
2. 基-2FFT算法的推导
此次的推导过程主要参考《数字信号处理——原理、算法与应用(第四版)》的P385-388。若对推导的过程没有兴趣或存在疑惑,可以直接看第3节的实现代码。推导的是按时间抽取的FFT(DIT - FFT)算法,另外一种实现的角度则是按频率抽取( DlF-FFT )算法。
首先证明一个恒等式:
并把点数据序列抽取分成两个长度为的奇偶子序列和:
把式(28)和式(29)代入式(7)中得到:
其中,和分别是序列和的长度为的DFT,这样的取值范围可以从缩小一半为。
因为和是周期性的,周期为,所以我们有和。此外,由于旋转因子,k的取值范围可以从缩小一半为。所以式(30)可以表示为
现在算一下分解后所需的总乘法数:通过式(31)可以观察到计算与各需要次乘法。此外,计算需要次复数乘法。所以,计算共需要次复数乘法。由此可见,第一步就将算法中的乘法的次数由降到。
与前面1.2小节中的式(26)一样,定义中间变量:
由此式(31)可以改写为
以此类推,对与分别重复上述原序列的抽取过程,则会产生两个长度为的序列:
同理,将产生
同式(31),可以分别计算点DFT和
其中,{}序列是{}序列的点DFT。
观察式(35)可知{}序列的计算共需次乘法,故计算和的计算共需次复数乘法,而利用和来计算需额外的次复数乘法。可见总需要的复数乘法次数为,比上一次抽取所需的总乘法数少了一半。
数据序列的抽取可以不断重复,直到导致的序列简化为1点序列,根据式(3)计算可得,1点序列的DFT结果为本身,故抽取到只有1点的序列时直接可以当成DFT结果并进行递归计算。对于的序列,这种抽取可执行次。
对于第i次抽取后,计算抽取后各子序列的DFT的通式。(其中代表第次抽取或称为第级,代表某次抽取中第个子序列),比如对应式(35)中的第一次抽取的子序列就可以表示为以及第二次抽取:。由此统一符号后各子序列的DFT的通式为
通过式(28)恒等式可以得到
将式(37)代入带式(36)中得
其中,和分别是第个子序列分解后下一次抽取的两个子序列,注意到每一次的取值范围总是每次抽取的两倍。整个过程就叫做按时间抽取的FFT(DIT - FFT)算法。
观察通式(38),可以定义以下的基本运算,如下图所示:
其中,中间变量以及,所得的结果,,正好与式(38)对应上了。这种运算称为蝶形运算,因为它的流图看起来像是一只蝴蝶。可以看见重复出现了两次,故在蝶形运算输入后直接乘上(相等于提前计算好该项)当成一端的输入,这样可以减少一次复数乘法的计算,由此每一次抽取即每一级的乘法运算次数由减少一半变为。此外,一般旋转因子可以提前算好存在存储中,用到时直接给出索引值即可调用。
蝶型单元具有以下特点:
- 每一个蝶型单元都是独立的;
- 每一级都有个蝶形单元,其中为序列长度;
- 长度为的序列具有级的蝶形,且每级可以分为小组的蝶形单元,代表级数。
以的有限长序列举例(1.2小节中序列的抽取过程也类似),其抽取的整个过程如下图所示:
上图中,每一个红框表示一个抽取出来的子序列。对每一个序列进行抽取时序号都要重新从0开始数起,再进行奇偶分解。比如对于序列{}分解为相较于它的奇序列{}及偶序列{}。其往回递归计算过程如下图所示:
如果我们将未抽取之前的序号和抽取完成之后的序号进行二进制编码可以得到:
我们可以看出用红色标记出的二进制编码实际上是进行了码位倒置(实际上没用红色标记的二进制编码也进行了码位倒置,之后倒置前后结果一致)。所以说,对于一个
一般来说,每次蝶形运算包括一次复数乘法和两次复数加法,对于的序列,每一个阶段有次蝶形运算,其有个阶段,所以,正如上面指出的那样,其需次复数乘法以及次复数加法。
一旦对复数执行了产生的蝶形运算,就无须再保存。所以,我们可以将结果保存在与相同的位置。从而,为了存储每个阶段的计算结果(个复数),我们需要一个固定大小的存储空间,如个寄存器。因为在计算点FFT的过程中均使用了相同的个存储位置,所以我们说计算在原位进行。
3. 基-2FFT算法的实现
FFT算法实现的流程图:
FFT算法的matlab实现代码如下,实现的各个变量主要根据式(38)的逆过程进行设置。此外,一般旋转因子可以提前算好存在存储中,用到时直接给出索引值即可调用,但本次实现为了实现简便并没有提前算好旋转因子,而是在循环中实时计算,在一定程度上降低了实现的FFT的运行效率。
function [re_vector] = my_FFT(vector)
%my_FFT 快速傅里叶变换(时域抽取算法)
% 此处显示详细说明
N = length(vector); %获得数据点的个数
%%以下是位反转的实现(时域分解)
re_vector = zeros(1,N); %创建零向量来接收数据
Nbit = log2(N); %二进制位数
for n=1:N
tmp=bit_reverse(n-1,Nbit); %对每一个数进行反转
re_vector(n)=vector(tmp+1);%反转后赋值
end
%%以下实现FFT中的三个循环(频域合成)
step = log2(N); %分解的步数
for n=1:step
group = N/2^n; %分组数
butterfly = N / (group * 2); %每一组要进行蝶形运算的次数
index = 1; %索引初始化
for b=1:group
for k=1:butterfly
r = 2^(step-n)*(k-1); %旋转相位因子
W_r = exp(i*(-2*pi*r/N)); %旋转因子
even = re_vector(index); %偶分组部分
odd = re_vector(index+butterfly)*W_r; %奇分组部分
%赋值
re_vector(index) = even + odd;
re_vector(index+butterfly)= even - odd;
index = index + 1; %分组内索引递增
end
index = 1 + 2 * b * butterfly; %索引组号递增
end
end
end
对应的IFFT(逆快速傅里叶变换)的matlab是实现代码如下,其流程与上述代码一样,只是旋转因子要改为以及最后得到的结果要乘上。
function [re_vector] = my_IFFT(vector)
%my_IFFT 快速傅里叶变换(时域抽取算法)
% 此处显示详细说明
N = length(vector); %获得数据点的个数
%%以下是位反转的实现(时域分解)
re_vector = zeros(1,N); %创建零向量来接收数据
Nbit = log2(N); %二进制位数
for n=1:N
tmp=bit_reverse(n-1,Nbit); %对每一个数进行反转
re_vector(n)=vector(tmp+1);%反转后赋值
end
%%以下实现IFFT中的三个循环(频域合成)
step = log2(N); %分解的步数
for n=1:step
group = N/2^n; %分组数
butterfly = N / (group * 2); %每一组要进行蝶形运算的次数
index = 1; %索引初始化
for b=1:group
for k=1:butterfly
r = 2^(step-n)*(k-1); %旋转相位因子
W_r = exp(i*(2*pi*r/N)); %旋转因子
even = re_vector(index); %偶分组部分
odd = re_vector(index+butterfly)*W_r; %奇分组部分
%赋值
re_vector(index) = even + odd;
re_vector(index+butterfly)= even - odd;
re_vector(index) = even + odd;
re_vector(index+butterfly)= even - odd;
index = index + 1; %分组内索引递增
end
index = 1 + 2 * b * butterfly; %索引组号递增
end
end
re_vector = re_vector/N; %最后的结果乘上缩减因子1/N(不要在上面循环里面乘,这样就会乘多次了)
end
4.用FFT实现的线性卷积
与数字信号处理知识点总结(三):离散傅里叶变换(DFT)第5节中实现的方法一样,使用重叠保留法实现线性卷积。唯一不同的是,频域方法中的DFT换成FFT实现,可大大提高线性卷积的速度。实现的代码如下:
function [y] = hsolpsav( x,h,N)
%High-speed O飞rerlap-Save method of block convolutions using FFT
% -------一- ---------- -一- -- - ---------------
%[y]= hsolpsav(x,h,N)
% y = output sequence
% x = input sequence
% h = impulse r esponse
% N = block length (must be a power of two)
N = 2^(ceil(log10(N)/log10(2)));
Lenx = length(x);M = length(h);M1 = M - 1;L = N-M1;
h = fft(h,N); %先做好FFT变换
%
x = [zeros(1,M1),x,zeros(1,N-1)];
K = floor((Lenx+M1-1)/L);
Y = zeros(K+1,N);
% convolution wi th succesive blocks
for k=0:K
xk = fft(x(k*L+1:k*L+N));
Y(k+1,:) = ifft(xk.*h);
end
Y = Y(:,M:N)';
y = (Y(:))';
end
1.[知乎[精品讲义]—快速傅里叶变换(Fast Fourier Transformation)]:https://zhuanlan.zhihu.com/p/110897470?ivk_sa=1024320u
2.John G. Proakis, Dimitris G. Manolakis. 数字信号处理——原理、算法与应用(第四版)[M]. 电子工业出版社,2014.8
3.Steven W. Smith. 实用数字信号处理[M]. 人民邮电出版社,2010.12
4.[美]纳•K•英格尔,[美]约翰•G•普罗克斯. 数字信号处理(MATLAB版)(第3版)[M].西安交通大学出版社,2013.7