本文章参考:http://tieba.baidu.com/p/2513502552
那一天,作者终于想起了他CSDN的密码,填坑完毕:)
不想看理论想直接看代码的同学,请到我的第二篇博客拉到最底。点击传送门传送
由于需要在Arduino上进行声音处理,需要用到FFT变换,查找了相关库过后发现FFT的库比较少,而且版本比较老了,挑编译器,也因为种种个人原因,索性自己来实现一个FFT变换。
FFT(快速傅里叶变换)变换本质就是DFT(离散傅里叶变换),但是对DFT进行了优化,减少了计算量并能够比较方便地用程序实现。这里是在Arduino平台上实现了一个基2的FFT算法,大致需要如下预备知识:
因为是用程序实现数学算法,要完全理解程序,肯定是会涉及到公式推导,但是这里的推导很简单,大概需要高中的数学知识即可。首先上DFT的公式,下面将由这个公式一步步推出FFT的算法。
X[k]=∑n=0N−1x[n]e−i2πkNn X [ k ] = ∑ n = 0 N − 1 x [ n ] e − i 2 π k N n
学过傅里叶变换的话应该比较熟悉这个公式,它其实就是针对离散的采样点 x[n] x [ n ] 的傅里叶变换,其意义还是将信号从时域转换到频域。那么这里的 X[k] X [ k ] 表示的就是频域信号了,下标k表示信号的频率, X[k] X [ k ] 的值就是该频率信号的幅值。这里可以看到,当k固定的时候,对应于频率为k的信号幅值是与 x[n] x [ n ] 有关的一个级数的和,因此每一个频域的点实际上包含了所有时域点的信息。
有两点需要注意,一是因为 x[n] x [ n ] 的长度为 N N ,因此 DFT D F T 后的频域信号 X[k] X [ k ] 的长度也是 N N ;二是为了进行FFT变换, N N 的取值一般是2的整数次幂。
将下标n分解为奇偶两部分,然后分别求和
X[k]=∑r=0N2−1x[2r]e−i2πkN2r+∑r=0N2−1x[2r+1]e−i2πkN(2r+1) X [ k ] = ∑ r = 0 N 2 − 1 x [ 2 r ] e − i 2 π k N 2 r + ∑ r = 0 N 2 − 1 x [ 2 r + 1 ] e − i 2 π k N ( 2 r + 1 )
这里进行一次代换,用 x0[n] x 0 [ n ] 表示 x[n] x [ n ] 中的偶数项,用 x1[n] x 1 [ n ] 表示 x[n] x [ n ] 中的奇数项
X[k]=∑n=0N2−1x0[n]e−i2πkN/2n+∑n=0N2−1x1[n]e−i2πkN/2n−i2πkN X [ k ] = ∑ n = 0 N 2 − 1 x 0 [ n ] e − i 2 π k N / 2 n + ∑ n = 0 N 2 − 1 x 1 [ n ] e − i 2 π k N / 2 n − i 2 π k N
第二项求和中, e−i2πNk e − i 2 π N k 与 n n 无关,因此可以单独提出
X[k]=∑n=0N2−1x0[n]e−i2πkN/2n对x0[n]进行DFT+e−i2πNk∑n=0N2−1x1[n]e−i2πkN/2n对x1[n]进行DFT X [ k ] = ∑ n = 0 N 2 − 1 x 0 [ n ] e − i 2 π k N / 2 n ⏟ 对 x 0 [ n ] 进 行 D F T + e − i 2 π N k ∑ n = 0 N 2 − 1 x 1 [ n ] e − i 2 π k N / 2 n ⏟ 对 x 1 [ n ] 进 行 D F T
X[k]=DFT(x0)+e−i2πNkDFT(x1) X [ k ] = D F T ( x 0 ) + e − i 2 π N k D F T ( x 1 )
利用欧拉公式,将: e−i2πNk=cos2πkN−isin2πkN e − i 2 π N k = cos 2 π k N − i sin 2 π k N 代入:
X[k]=DFT(x0)+(cos2πkN−isin2πkN)DFT(x1) X [ k ] = D F T ( x 0 ) + ( cos 2 π k N − i sin 2 π k N ) D F T ( x 1 )
设: X0[k]=DFT(x0),X1[k]=DFT(x1) X 0 [ k ] = D F T ( x 0 ) , X 1 [ k ] = D F T ( x 1 )
X[k]=X0[k]+(cos2πkN−isin2πkN)X1[k] X [ k ] = X 0 [ k ] + ( cos 2 π k N − i sin 2 π k N ) X 1 [ k ]
因为这里 x0[n] x 0 [ n ] 和 x1[n] x 1 [ n ] 分别是 x[n] x [ n ] 的偶数项和奇数项,因此它们的长度都是 N2 N 2 ,所以 X0[k] X 0 [ k ] 和 X1[k] X 1 [ k ] 的长度也是 N2 N 2 ,而 X[k] X [ k ] 是 X0[k] X 0 [ k ] 和 X1[k] X 1 [ k ] 的线性组合,因此其长度也是 N2 N 2 。
前面已经提到,原式的变换中 X[k] X [ k ] 的长度为 N N ,经过一轮变换之后其长度变为了 N2 N 2 ,那岂不是意味着丢失了一半的频率信息吗?这肯定是不允许的,下面就利用 DFT D F T 的对称性找回这一半丢失的信息。
在此之前先引入旋转因子的概念:
旋转因子表示为:
WN=e−i2πN=cos2πN−isin2πN W N = e − i 2 π N = cos 2 π N − i sin 2 π N
旋转因子的对称性:
Wk+N2N=e−i2πN(k+N2)=e−i2πN∗e−iπ=−e−i2πN=−WkN W N k + N 2 = e − i 2 π N ( k + N 2 ) = e − i 2 π N ∗ e − i π = − e − i 2 π N = − W N k
使用旋转因子表达将更简洁:
X[k]=X0[k]+WkNX1[k] X [ k ] = X 0 [ k ] + W N k X 1 [ k ]
上面已经说到, X0[k] X 0 [ k ] 和 X1[k] X 1 [ k ] 的长度为 N2 N 2 ,那么超出 N2 N 2 的部分会出现什么情况呢?事实上超出的部分将会呈周期性变化,即: X0[k+N2]=X0[k] X 0 [ k + N 2 ] = X 0 [ k ] ,这一点将 k+N2 k + N 2 代入 X0[k] X 0 [ k ] 的表达式即可证明,这里就省去了。知道这一点后,我们用在上式中用 k+N2 k + N 2 代替 k k :
X[k+N2]=X0[k+N2]+Wk+N2NX1[k+N2]=X0[k]−WkNX1[k] X [ k + N 2 ] = X 0 [ k + N 2 ] + W N k + N 2 X 1 [ k + N 2 ] = X 0 [ k ] − W N k X 1 [ k ]
至此已经得出了整个频域上的表达式:
X[k]=X0[k]+WkNX1[k] X [ k ] = X 0 [ k ] + W N k X 1 [ k ]
X[k+N2]=X0[k]−WkNX1[k] X [ k + N 2 ] = X 0 [ k ] − W N k X 1 [ k ]
写了这么多公式,还没有讲到FFT,其实算到这里,已经完成了FFT的关键部分了。
下一篇中将完成算法的具体实现。