昨天晚上数字信号处理粗略的复习了下,感觉快速傅里叶变换对于使用挺重要的,就想着编程实现它。 今天上午没课,想着一上午实现吧,结果到晚上才编出来。 反正是出结果了,我也不管结果对不对了,反正原理算是差不多了解了。

首先,什么是傅里叶变换,傅里叶变换最基本的就不赘述了。 主要说下傅里叶变换的速度以及快速傅里叶变换的原理。 我用的方法是时间抽选基2的算法(库里-图基算法)。 这个遵循时间偶奇分解,频率前后分解的规则。 核心思想是利用旋转因子的对称性和周期性,将其变成小的DFT来计算,降低运算复杂度。 最容易理解的就是蝶形图,下面的是蝶形图. 蝶形图 嗯,大体上就是上面这个图。 由于在进行运算的时候要进行偶奇分开,就得把正常的时间序列转换下。 主要方法是变址计算。比如8进制的如下表

自然顺序 二进制 码位倒置 倒置顺序
0 000 000 0
1 001 100 4
2 010 010 2
3 011 110 6
4 100 001 1
... ... ... ...

就是这个意思,不打啦。变址之后就进行正常的傅里叶计算,从最小的开始,逐步扩大。 这个地方有点像递归计算。不过递归的效率只能呵呵了。 在不同级进行傅里叶变换的长度间隔是一个具体的表达式,不愿意写啦,有个参考的写得挺详细。 再说下,代码我也没检查是不是执行正确的,就是这么个思想。 实在是不想再改了,数字信号处理复习的一塌糊涂,得复习了。 这个是主要的代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
public class FFT {
    public FFT() {
    }
    public static final int N = 8;
    public static final double PI = Math.PI;
    public static void main(String[] args) {
        FFT fft = new FFT();
        Complex time[] = new Complex[N];
        //创建个时域的序列
        for (int i = 0; i < time.length; i++) {
            Complex com = new Complex();
            com.re = Math.random() * 10;
            com.im = Math.random() * 10;
            time[i] = com;
        }
        fft.listNum(time);
        //进行二进制数倒置
        fft.changeOrder(time);
        fft.listNum(time);
        //进行快速傅里叶变换
        fft.trans(time);
        fft.listNum(time);
    }
    private void trans(Complex[] time) {
        int length = time.length;
        int m = log2(length);
        for (int i = 0; i < m; i++) {
            //间隔是2的i次幂,也代表了有几个数参与运算
            int l = (int) Math.pow(2, i);
            //在第i次有几组进行运算
            int g = length / (l * 2), t = 0;
            for (int j = 0; j < g; j++) {
                //计算每一组的变换值
                for (; t < 2 * l * (j + 1); t++) {
                    //前l个是加运算,后l是减运算
                    if (t < l * (j + 1)) {
                        Complex com = time[t];
                        Complex product = comMultiply(time[t + l], getWN(t));
                        time[t] = comPlus(com, product);
                    } else {
                        Complex com = time[t - l];
                        Complex product = comMultiply(time[t], getWN(t));
                        time[t] = comMinus(com, product);
                    }
                }
            }
        }
    }
    public void changeOrder(Complex time[]) {
        int length = time.length;
        //经过m次分解
        int m = log2(length);
        int k, tempNum;
        for (int i = 0; i < length; i++) {
            Complex com = new Complex();
            k = i;
            tempNum = 0;
            //进行二进制倒置,m就代表倒置几次
            for (int t = 0; t < m; t++) {
                //将低位的乘到高位
                tempNum *= 2;
                //取出k的低位,通过循环回来乘到高位
                tempNum += k % 2;
                //将已经取出来的数删除
                k /= 2;
            }
            //完成调换
            if (tempNum > i) {
                com = time[i];
                time[i] = time[tempNum];
                time[tempNum] = com;
            }
        }
    }
    public void listNum(Complex time[]) {
        System.out.println("结果如下");
        for (Complex com : time) {
            System.out.println(com.re + "+ j" + com.im);
        }
    }
    public int log2(int value) {
        return (int) (Math.log(value) / Math.log(2));
    }
    public Complex comPlus(Complex com1, Complex com2) {
        Complex com = new Complex();
        com.re = com1.re + com2.re;
        com.im = com1.im + com2.im;
        return com;
    }
    public Complex comMinus(Complex com1, Complex com2) {
        Complex com = new Complex();
        com.re = com1.re - com2.re;
        com.im = com1.im - com2.im;
        return com;
    }
    public Complex comMultiply(Complex com1, Complex com2) {
        Complex com = new Complex();
        com.re = com1.re * com2.re - com1.im * com2.im;
        com.im = com1.re * com2.im + com2.re * com1.im;
        return com;
    }
    public Complex getWN(int n) {
        Complex com = new Complex();
        com.re = Math.cos(2 * PI / N * n);
        com.im = Math.sin(2 * PI / N * n) * (-1);
        return com;
    }
}

我把复数写成一个单独的类了

1
2
3
4
5
6
7
8
9
public class Complex {
    double re,im;
    public Complex() {
    }
    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }
}

关于代码主要比较晦涩的地方我也注了注释,希望对理解有帮助。 如果还是看不懂,那就没法了,反正我写这段代码的时候也是晕头转向的。 说实话用这种面向对象的语言来编写这面向过程的程序,除了真爱想不到其他的了。

参考文献 1.学步园 2.百度文库 3.最重要的还是课本啦!