coverPiccoverPic

理解快速傅里叶变换

🎈前言

傅立叶变换(Fourier Transform,FT)可以将时域上的信号转变为频域上的信号,使用者从而获得感兴趣的变量,在音视频、图像、神经影像等信号处理领域均有广泛的应用。而快速傅立叶变换(Fast Fourier Transform,FFT)是一种离散信号的傅立叶变换的的快速计算方法。

在上篇博客我复习了一下傅立叶变换的数学推导,这里就回顾一下快速傅立叶变换的实现。

✏算法原理

这里记原信号为 x[n]x[n],傅立叶变换的结果为 x[k]x[k],信号的周期为 TT,在一个周期内采样 NN 次,离散傅立叶变换(DFT)为

X[k]=k=0N1x[n]e2πink/NX[k]=\sum_{k=0}^{N -1}x[n]e^{-2\pi in k/N}

直接计算 DFT 需要 N2N^2 次乘法。FFT 利用 eiθe^{i\theta} 的周期性和分治的思想减少了需要的计算。

当采样信号个数 NN 为偶数时,有

X[k]=n=0N1x[n]e2πink/N=n=0N/21x[2n]e2πi2nkN+n=0N/21x[2n+1]e2πi(2n+1)kN=n=0N/21x[2n]e2πi2nkN+e2πikNn=0N/21x[2n+1]e2πi2nkNX[k]=\sum_{n=0}^{N -1}x[n]e^{-2\pi in k/N}\\ =\sum_{n=0}^{N/2 -1}x[2n]e^{-2\pi i\frac{2n k}{N}}+\sum_{n=0}^{N/2 -1}x[2n+1]e^{-2\pi i\frac{ (2n+1) k}{N}}\\ =\sum_{n=0}^{N/2 -1}x[2n]e^{-2\pi i\frac{2n k}{N}}+e^{-2\pi i\frac{ k}{N}}\sum_{n=0}^{N/2 -1}x[2n+1]e^{-2\pi i\frac{2nk}{N}}

E[k]=n=0N/21x[2n]e2πi2nkNO[k]=n=0N/21x[2n+1]e2πi2nkNW[k]=e2πikNX[k]=E[k]+W[k]O[k]E[k]=\sum_{n=0}^{N/2 -1}x[2n]e^{-2\pi i\frac{2n k}{N}}\\O[k]=\sum_{n=0}^{N/2 -1}x[2n+1]e^{-2\pi i\frac{2nk}{N}}\\W[k]=e^{-2\pi i\frac{k}{N}}\\ X[k]=E[k]+W[k]O[k]

注意到 eiθ=ei(θ+2π)e^{i\theta}=e^{i(\theta+2\pi)}eiθ=ei(θ+π)e^{i\theta}=-e^{i(\theta+\pi)},有

E[k]=E[k+N/2]O[k]=O[k+N/2]W[k]=W[k+N/2]X[k+N/2]=E[k]W[k]O[k]E[k] = E[k+N/2]\\ O[k] = O[k+N/2]\\ W[k] = -W[k+N/2]\\ X[k+N/2]=E[k]-W[k]O[k]

这样子,这里只需计算一半的 X[k]X[k]。进一步地,可以发现 E[k]E[k]O[k]O[k] 中也可以利用 eiθe^{i\theta} 的周期性减少计算,只要 NN 是 2 的正整数次幂(不是的话后面补 0),就可以把 E[k]E[k]O[k]O[k] 的计算细分为更小的子问题,这就是所谓的蝶形变换。

X0[k]=X[k],k=0,1,...N1X_0[k]=X[k],k=0,1,...N-1\\

X1[k]=E[k]X1[k+N/2]=O[k]X0[k]={X1[k]+W[k]X1[k+N/2],k<N/2X1[kN/2]+W[k]X1[k],elsek=0,1,...N/21...X_1[k]=E[k]\\X_1[k+N/2]=O[k]\\ X_0[k] = \left\{ \begin{array}{lr} X_1[k]+W[k]X_1[k+N/2],k<N/2\\ X_1[k-N/2]+W[k]X_1[k],else \end{array} \right. \\ k=0,1,...N/2-1\\ ...

同样地,X1X_1(如果有的话)也可以根据 eiθe^{i\theta} 周期性分解为更小的子问题:

X1[k]=E1[k]+W2[k]O1[k]X1[k+N/4]=E1[k]W2[k]O1[k]X_{1}[k]=E_{1}[k]+W^2[k]O_{1}[k]\\ X_{1}[k+N/4]=E_{1}[k]-W^2[k]O_{1}[k]\\

N=4N=4 时,如下图所示,


(开始胡言乱语…)以此类推,可以注意到以下规律:

Xd[k]={Xd+1[k]+Wd+1[k]Xd+1[k+(N/2d)], if !(1&[2d+1k/N]) Xd+1[k(N/2d)]+Wd+1[k]Xd+1[k], elsed=0,1,2...log2NX_d[k] = \left\{ \begin{array}{lr} X_{d+1}[k] +W^{d+1}[k]X_{d+1}[k+(N/2^d)],\\ \text{ if }!(1\&[2^{d+1}k/N])\\ \text{ }\\ X_{d+1}[k-(N/2^d)] +W^{d+1}[k]X_{d+1}[k],\\\text{ else} \end{array} \right.\\ d=0,1,2...log_2N

此时,计算 FFT 只需要 O(nlogn)O(n\log{n}) 的时间复杂度。

再观察最左边的步骤,Xlog2NX_{log_2N} 所需计算的 xx 的下标为 reverse(N)reverse(N)reversereverse 表示位数为 log2Nlog_2N 时,1 到 N 的二进制翻转。

对于 N=4N=4reverse(N)reverse(N)[0,1,2,3]的二进制表示[00,01,10,11]的左右翻转[00,10,01,11],即为[0,2,1,3]

现在已经知道初始状态和状态转移方程,在分治算法中直接向上归并即可。

🎁Code

这里直接给出代码,(虽然刚学的 rust,写起来一点都不优雅)

rust
  1. use num_complex::Complex;
  2. use std::f64::{self, consts::{E, PI}};
  3. fn binary_reversed(n: usize) -> Vec<usize> {
  4. (0..n).map(|t: usize| {
  5. let mut res: usize = 0;
  6. let mut x = t;
  7. for _i in 0..(n as f64).log(2.0) as usize {
  8. res = (res << 1) | (x & 1);
  9. x >>= 1;
  10. }
  11. res
  12. }).collect()
  13. }
  14. fn w(n: f64, k: f64, count: f64) -> Complex<f64> {
  15. Complex::new(E, 0.0).powc(Complex::new(-2.0_f64.powf(n) * PI * k / count, 0.0) * Complex::I)
  16. }
  17. fn fft(mut x: Vec<Complex<f64>>) -> Vec<Complex<f64>> {
  18. let len_of_x: usize = x.len();
  19. let depth = (len_of_x as f64).log2().ceil();
  20. let expected_len = (2.0_f64).powf(depth);
  21. for _n in 0..(expected_len - len_of_x as f64) as usize {
  22. x.push(Complex::new(0.0, 0.0))
  23. }
  24. let expected_len_usize = expected_len as usize;
  25. let mut pre: Vec<Complex<f64>>;
  26. let mut ans: Vec<Complex<f64>> = vec![];
  27. let inverted_indexex: Vec<usize> = binary_reversed(expected_len_usize);
  28. for i in 0..expected_len_usize {
  29. ans.push(x[inverted_indexex[i]]);
  30. }
  31. pre = ans.clone();
  32. let mut temp = 2.0_f64.powf(depth);
  33. for d in 0..depth as i32 {
  34. for k in 0..expected_len_usize {
  35. let mut _o: Complex<f64>;
  36. let mut _e: Complex<f64>;
  37. let _w = w(depth - d as f64, k as f64, expected_len);
  38. if 1 & (temp as usize * k / expected_len_usize) == 1 {
  39. _e = pre[k - (expected_len / temp) as usize];
  40. _o = pre[k];
  41. } else {
  42. _o = pre[k + (expected_len / temp) as usize];
  43. _e = pre[k];
  44. }
  45. ans[k] = _e + _o * _w;
  46. }
  47. pre = ans.clone();
  48. temp = temp / 2.0;
  49. }
  50. ans
  51. }
  52. fn main() {
  53. let data: Vec<_> = vec![
  54. Complex::new(4.0, 0.0),
  55. Complex::new(3.0, 0.0),
  56. Complex::new(2.0, 0.0),
  57. Complex::new(1.0, 0.0),
  58. ];
  59. let ans = fft(data);
  60. for complex_num in ans.iter() {
  61. println!("{:?}", complex_num);
  62. };
  63. }
  64. // Complex { re: 10.0, im: 2.4492127076447545e-16 }
  65. // Complex { re: 1.9999999999999998, im: -2.0000000000000004 }
  66. // Complex { re: 2.0, im: -7.347638122934264e-16 }
  67. // Complex { re: 2.0, im: 1.9999999999999998 }

🗑结语

终于写完这个博客了。这个博客简析了 FFT 的数学推导,并且实现了一个简单的 FFT 程序,虽然感觉自己单纯在写着玩,对工作什么的完全没有任何帮助就是了…

0 条评论未登录用户
Ctrl or + Enter 评论
🌸 Run