傅里叶变换 一维快速傅里叶变换(快速的一维离散傅里叶变换、分治法)

一、介绍

1、一维离散傅里叶变换DFT。

        DFT:(Discrete Fourier Transform)离散傅里叶变换是傅里叶变换在时域和频域上都呈离散的形式,将信号的时域采样变换为其DTFT的频域采样。在形式上,变换两端(时域和频域上)的序列是有限长的,而实际上这两组序列都应当被认为是离散周期信号的主值序列。即使对有限长的离散信号作DFT,也应当将其看作其周期延拓的变换。

        根据欧拉公式e^{\theta i}=cos \theta +(sin \theta )i,其中W_{N}^k=e^{-\frac{2\pi k }{N}i}=cos(\frac{2\pi k }{N})-sin(\frac{2\pi k }{N})i,i为虚数单位,即i^2=-1

        公式:F(u)=\sum_{x=0}^{N-1}f(x)e^{-\frac{2\pi ux}{N}i},可看作:F(u)=\sum_{x=0}^{N-1}f(x)[cos \frac{2\pi ux}{N}-sin \frac{2\pi ux}{N}i]

 

2、一维逆离散傅里叶变换IDFT。

        公式:f(x)=\frac{1}{N}\sum_{u=0}^{N-1}F(u)e^{\frac{2\pi ux}{N}i},可看作:f(x)=\frac{1}{N}\sum_{u=0}^{N-1}F(u)[cos \frac{2\pi ux}{N}+sin \frac{2\pi ux}{N}i]

   

3、一维快速傅里叶变换FFT。

        1)FFT(Fast Fourier Transformation)是离散傅氏变换(DFT)的快速算法,即利用计算机计算离散傅里叶变换(DFT)的高效、快速计算方法的统称。快速傅里叶变换是1965年由J.W.库利和T.W.图基提出的。采用这种算法能使计算机计算离散傅里叶变换所需要的乘法次数大为减少,特别是被变换的抽样点数N越多,FFT算法计算量的节省就越显著。即为快速傅氏变换。它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。

        \begin{bmatrix}F(0)\\F(1)\\ F(2)\\ \vdots\\ F(N-1) \end{bmatrix} =\begin{bmatrix} 1&1&1&\cdots &1\\ 1&W_{N}^1&W_{N}^2&\cdots &W_{N}^{(N-1)}\\ 1&W_{N}^2&W_{N}^4&\cdots &W_{N}^{2(N-1)}\\ \vdots&\vdots&\vdots&\vdots &\vdots\\ 1&W_{N}^{(N-1)}&W_{N}^{2(N-1)}&\cdots &W_{N}^{(N-1)(N-1)}\\ \end{bmatrix} + \begin{bmatrix}f(0)\\f(1)\\ f(2)\\ \vdots\\ f(N-1) \end{bmatrix}

        2)因为根据欧拉公式e^{\theta i}=cos \theta +(sin \theta )i和一维离散傅里叶公式,可以将欧拉公式中的\theta看作是\frac{2\pi }{N}k,而且可以是要将其代入到cos和sin函数中,所以其实可以看作是一个半径为1的圆。而\frac{2\pi }{N}k可以看作将圆分成N份,即上面那个矩阵中的W_{N}^k,而第k份的那个点就是那个值。

        3)可以推导出关系。

               周期性:W_{N}^{k+nN}=W_{N}^k+W_{N}^{nN}=W_{N}^k  ,  W_{nN}^{nk}=W_{N}^k

               原点对称:W_{N}^{k+\frac{N}{2}}=W_{N}^k+W_{N}^{\frac{N}{2}}=W_{N}^ke^{-j\frac{2\pi }{N}\frac{N}{2}}=W_{N}^ke^{-j\pi}=W_{N}^k(cos\pi-sin\pi)=-W_{N}^k

        4)要让DFT的长度等于2的n次幂,其中n是正整数。

        \tiny F(k)=\sum_{x=0}^{\frac{N}{2}-1}f(2x)W_{N}^{(2x)k}+\sum_{x=0}^{\frac{N}{2}-1}f(2x+1)W_{N}^{(2x+1)k}=\sum_{x=0}^{\frac{N}{2}-1}f(2x)W_{N}^{(2x)k}+W_{N}^{k}\sum_{x=0}^{\frac{N}{2}-1}f(2x+1)W_{N}^{(2x)k}

G(k)=\sum_{x=0}^{\frac{N}{2}-1}f(2x)W_{N}^{(2x)k}  ,  P(k)=\sum_{x=0}^{\frac{N}{2}-1}f(2x+1)W_{N}^{(2x)k}

F(k)=G(k)+W_{N}^{k}P(k)

        但是这里的k是小于\frac{N}{2}的,但是可以推导出

F(k+\frac{N}{2})=G(k+\frac{N}{2})+W_{N}^{k+\frac{N}{2}}P(k+\frac{N}{2})=F(x)=G(x)-W_{N}^kP(x)  ,  其中x=k+\frac{N}{2}

最后将G(x),P(x)按照这个方式,用分治法不停的拆分出来,剩下最后俩个\begin{bmatrix}F(0)\\F(1) \end{bmatrix}= \begin{bmatrix}1&1\\1&W_{2}^1\end{bmatrix}* \begin{bmatrix}f(0)\\f(1) \end{bmatrix}= \begin{bmatrix}1&1\\1&-1\end{bmatrix}* \begin{bmatrix}f(0)\\f(1) \end{bmatrix}= \begin{bmatrix}f(0)+f(1)\\f(0)-f(1)\end{bmatrix} ,然后返回计算的结果。

 

4、一维逆快速傅里叶变换IFFT。

        \frac{1}{N}\begin{bmatrix}f(0)\\f(1)\\ f(2)\\ \vdots\\ f(N-1) \end{bmatrix} =\begin{bmatrix} 1&1&1&\cdots &1\\ 1&W_{N}^1&W_{N}^2&\cdots &W_{N}^{(N-1)}\\ 1&W_{N}^2&W_{N}^4&\cdots &W_{N}^{2(N-1)}\\ \vdots&\vdots&\vdots&\vdots &\vdots\\ 1&W_{N}^{(N-1)}&W_{N}^{2(N-1)}&\cdots &W_{N}^{(N-1)(N-1)}\\ \end{bmatrix} + \begin{bmatrix}F(0)\\F(1)\\ F(2)\\ \vdots\\ F(N-1) \end{bmatrix}

比较DFT和IDFT公式,会发现逆变换和正变换的区别在于:

        1、e^{\theta i}中的\theta正负的区别。

        2、逆变换在结束之后,每个值都要除以原数组的大小。

所以我们只要套用FFT的算法就可以了,将F(k)作为输入数组,输出f(x)作为输出数组,将因子W_{N}^k改为W_{N}^{-k},最后将f(x)全部除以N就可以了。

\tiny f(k)=\sum_{x=0}^{\frac{N}{2}-1}F(2x)W_{N}^{(2x)k}+\sum_{x=0}^{\frac{N}{2}-1}F(2x+1)W_{N}^{(2x+1)k}=\sum_{x=0}^{\frac{N}{2}-1}F(2x)W_{N}^{(2x)k}+W_{N}^{k}\sum_{x=0}^{\frac{N}{2}-1}F(2x+1)W_{N}^{(2x)k}

f(k+\frac{N}{2})=G(k+\frac{N}{2})+W_{N}^{k+\frac{N}{2}}P(k+\frac{N}{2})=f(x)=G(x)-W_{N}^kP(x)

二、主要代码

1、复数类。

package com.zxj.reptile.utils.number;

public class Complex {
    private double real;//实数
    private double image;//虚数

    public Complex() {
        real = 0;
        image = 0;
    }

    public Complex(double real, double image) {
        this.real = real;
        this.image = image;
    }

    //加:(a+bi)+(c+di)=(a+c)+(b+d)i
    public Complex add(Complex complex) {
        double real = complex.getReal();
        double image = complex.getImage();
        double newReal = this.real + real;
        double newImage = this.image + image;
        return new Complex(newReal, newImage);
    }

    //减:(a+bi)-(c+di)=(a-c)+(b-d)i
    public Complex sub(Complex complex) {
        double real = complex.getReal();
        double image = complex.getImage();
        double newReal = this.real - real;
        double newImage = this.image - image;
        return new Complex(newReal, newImage);
    }

    //乘:(a+bi)(c+di)=(ac-bd)+(bc+ad)i
    public Complex mul(Complex complex) {
        double real = complex.getReal();
        double image = complex.getImage();
        double newReal = this.real * real - this.image * image;
        double newImage = this.image * real + this.real * image;
        return new Complex(newReal, newImage);
    }

    //乘:a(c+di)=ac+adi
    public Complex mul(double multiplier) {
        return mul(new Complex(multiplier, 0));
    }

    //除:(a+bi)/(c+di)=(ac+bd)/(c^2+d^2) +((bc-ad)/(c^2+d^2))i
    public Complex div(Complex complex) {
        double real = complex.getReal();
        double image = complex.getImage();
        double denominator = real * real + image * image;
        double newReal = (this.real * real + this.image * image) / denominator;
        double newImage = (this.image * real - this.real * image) / denominator;
        return new Complex(newReal, newImage);
    }

    public double getReal() {
        return real;
    }

    public void setReal(double real) {
        this.real = real;
    }

    public double getImage() {
        return image;
    }

    public void setImage(double image) {
        this.image = image;
    }

    @Override
    public String toString() {
        String str = "";
        if (real != 0) {
            str += real;
        } else {
            str += "0";
        }
        if (image < 0) {
            str += image + "i";
        } else if (image > 0) {
            str += "+" + image + "i";
        }
        return str;
    }

    //欧拉公式 e^(ix)=cosx+isinx
    public static Complex euler(double x) {
        double newReal = Math.cos(x);
        double newImage = Math.sin(x);
        return new Complex(newReal, newImage);
    }

    /**
     * 比较两个复数是否相等,并可以调节小数精度误差
     *
     * @param a 复数a
     * @param b 复数b
     */
    public static boolean equals(Complex a, Complex b) {
        return NumberUtils.getRound(a.getImage(), 4) == NumberUtils.getRound(b.getImage(), 4) &&
                NumberUtils.getRound(a.getReal(), 4) == NumberUtils.getRound(b.getReal(), 4);
    }

    /**
     * 比较两个复数数组是否相等,小数精度误差在6位数
     *
     * @param a 复数数组a
     * @param b 复数数组b
     */
    public static boolean equals(Complex[] a, Complex[] b) {
        if (a.length != b.length) {
            return false;
        }
        for (int i = 0; i < a.length; i++) {
            if (!equals(a[i], b[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * 将复数数组转换为double数组
     *
     * @param complexArray 复数数组
     */
    public static double[] getDoubleArray(Complex[] complexArray) {
        int length = complexArray.length;
        double[] doubleArray = new double[length];
        for (int i = 0; i < length; i++) {
            doubleArray[i] = NumberUtils.getRound(complexArray[i].getReal(), 2);
        }
        return doubleArray;
    }

    /**
     * 将double数组转换为复数数组
     *
     * @param doubleArray double数组
     */
    public static Complex[] getComplexArray(double[] doubleArray) {
        int length = doubleArray.length;
        Complex[] complexArray = new Complex[length];
        for (int i = 0; i < length; i++) {
            complexArray[i] = new Complex(doubleArray[i], 0);
        }
        return complexArray;
    }
}

2、工具方法。


    /**
     * value指定小数精度的四舍五入
     * 
     * @param value 值
     * @param decimalLength 精度值
     */
    public static double getRound(double value, int decimalLength) {
        double flag = Math.pow(10, decimalLength);
        int a = (int) Math.round(value * flag);
        return a / flag;
    }

    /**
     * 随机生成一维数组
     *
     * @param length   数值长度
     * @param minValue 最小值(包括)
     * @param maxValue 最大值(包括)
     */
    public static double[] getRandom(int length, int minValue, int maxValue) {
        int range = maxValue - minValue + 1;
        double[] array = new double[length];
        for (int i = 0; i < length; i++) {
            array[i] = (int) (Math.random() * range) + minValue;
        }
        return array;
    }

3、一维离散傅里叶变换DFT和一维逆离散傅里叶变换IDFT。

/**
 * 一维离散傅里叶变换DFT
 *
 * @param array 一维数组
 */
public static Complex[] getDft(double[] array) {
    int N = array.length;
    Complex[] complexArray = new Complex[N];
    double flag = -2 * Math.PI / N;
    for (int u = 0; u < N; u++) {
        //x-N的累加
        Complex sum = new Complex();
        for (int x = 0; x < N; x++) {
            //f(x) * e^(-(2 * PI * u * x / N)i)
            Complex complex = Complex.euler(flag * u * x).mul(array[x]);
            sum = complex.add(sum);
        }
        complexArray[u] = sum;
    }
    return complexArray;
}

/**
 * 一维逆离散傅里叶变换IDFT
 *
 * @param complexArray 一维复数数组
 */
public static double[] getInverseDft(Complex[] complexArray) {
    int N = complexArray.length;
    double[] array = new double[N];
    double flag = 2 * Math.PI / N;
    for (int x = 0; x < N; x++) {
        //u-N的累加
        Complex sum = new Complex();
        for (int u = 0; u < N; u++) {
            //F(u) * e^((2 * PI * u * x / N)i)
            Complex complex = Complex.euler(flag * u * x).mul(complexArray[u]);
            sum = complex.add(sum);
        }
        //F(u) * e^((2 * PI * u * x / N)i) / N
        sum = sum.mul(1.0 / N);
        double realSum = sum.getReal();
        array[x] = NumberUtils.getRound(realSum, 2);
    }
    return array;
}

 

4、一维快速傅里叶变换FFT和一维逆快速傅里叶变换IFFT,采用递归分治的方式。

    /**
     * 一维快速傅里叶变换FFT
     *
     * @param array 一维数组
     */
    public static Complex[] getFft(double[] array) {
        //实际的长度
        int length = array.length;
        //调节过的长度
        int variableLength = (int) NumberUtils.getVariablePow(length, 2);
        Complex[] variableArray = new Complex[variableLength];
        for (int i = 0; i < variableLength; i++) {
            if (i < length) {
                variableArray[i] = new Complex(array[i]);
            } else {
                variableArray[i] = new Complex();
            }
        }
        return fftProgress(variableArray, -1);
    }

    /**
     * 一维逆快速傅里叶变换IFFT  当N是2次幂时
     *
     * @param complexArray 一维复数数组
     */
    public static double[] getInverseFft(Complex[] complexArray) {
        return getInverseFft(complexArray, complexArray.length);
    }

    /**
     * 一维逆快速傅里叶变换IFFT  当N不是2次幂时
     *
     * @param complexArray 一维复数数组
     * @param realLength   返回的数组长度
     */
    public static double[] getInverseFft(Complex[] complexArray, int realLength) {
        int length = complexArray.length;
        Complex[] resultArrays = fftProgress(complexArray, 1);//f(k)
        double[] array = new double[realLength];
        //每个数都要除以N
        for (int i = 0; i < realLength; i++) {
            array[i] = NumberUtils.getRound(resultArrays[i].getReal() / length, 2);
        }
        return array;
    }

    /**
     * 一维快速傅里叶变换FFT和一维逆快速傅里叶变换IFFT递归过程
     *
     * @param complexArray 一维复数数组
     * @param minus        FFT为1,IFFT为-1
     */
    private static Complex[] fftProgress(Complex[] complexArray, int minus) {
        int length = complexArray.length;
        if (length == 2) {
            //F(0)=f(0)+f(1),F(1)=f(0)-f(1)
            return new Complex[]{
                    complexArray[0].add(complexArray[1]),
                    complexArray[0].sub(complexArray[1]),};
        } else if (length == 1) {
            return complexArray;
        }
        int middle = length / 2;
        //
        Complex[] a = new Complex[middle];//a(x)=f(2x)
        Complex[] b = new Complex[middle];//b(x)=f(2x+1)
        for (int i = 0; i < middle; i++) {
            a[i] = complexArray[2 * i];
            b[i] = complexArray[2 * i + 1];
        }
        //
        Complex[] complexesA = fftProgress(a, minus);//计算G(k)
        Complex[] complexesB = fftProgress(b, minus);//计算P(k)
        Complex[] resultArray = new Complex[length];//F(k)
        double flag = minus * 2 * Math.PI / length;//2Pi*k/N
        for (int i = 0; i < middle; i++) {
            //e^(2Pi*k/N)
            Complex complex = Complex.euler(flag * i).mul(complexesB[i]);
            //F(k)=G(k)+(e^(2Pi*k/N))*P(k)
            resultArray[i] = complexesA[i].add(complex);
            //F(k+(N/2))=G(k)+(e^(2Pi*(k+(N/2))/N))*P(k+(N/2))
            resultArray[i + middle] = complexesA[i].sub(complex);
        }
        return resultArray;
    }

三、FFT和DFT结果比较

1、主流程代码。

package com.zxj.reptile.test.image;

import com.zxj.reptile.utils.image.FourierUtils;
import com.zxj.reptile.utils.number.ArrayUtils;
import com.zxj.reptile.utils.number.Complex;

import java.util.Arrays;

public class FourierTest {
    public static void main(String[] args) {
        System.out.println("------开始------");
        testDft(8);
        System.out.println("------结束------");
    }

    private static void testDft(int length) {
        System.out.println("原数据: ");
        System.out.println(String.format("大小为%d", length));
        double[] array = ArrayUtils.getRandom(length, 0, 255);
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
        System.out.println();
        //
        System.out.println("一维离散傅里叶变换DFT: ");
        long time = System.currentTimeMillis();
        Complex[] dftArray = FourierUtils.getDft(array);
        System.out.println("花费时间 :" + (System.currentTimeMillis() - time));
        for (int i = 0; i < dftArray.length; i++) {
            System.out.println(dftArray[i]);
        }
        System.out.println();
        //
        System.out.println("一维逆离散傅里叶变换IDFT: ");
        time = System.currentTimeMillis();
        double[] inverseDftArray = FourierUtils.getInverseDft(dftArray);
        System.out.println("花费时间 :" + (System.currentTimeMillis() - time));
        for (int i = 0; i < inverseDftArray.length; i++) {
            System.out.println(inverseDftArray[i]);
        }
        if (Arrays.equals(array, inverseDftArray)) {
            System.out.println("IDFT成功");
        } else {
            System.out.println("IDFT失败");
        }
        System.out.println();
        //
        System.out.println("一维快速傅里叶变换FFT: ");
        time = System.currentTimeMillis();
        Complex[] fftArray = FourierUtils.getFft(array);
        System.out.println("花费时间 :" + (System.currentTimeMillis() - time));
        for (int i = 0; i < fftArray.length; i++) {
            System.out.println(fftArray[i]);
        }
        System.out.println();
        //
        System.out.println("一维逆快速傅里叶变换IFFT: ");
        time = System.currentTimeMillis();
        double[] inverseFFTArray = FourierUtils.getInverseFft(fftArray, array.length);
        System.out.println("花费时间 :" + (System.currentTimeMillis() - time));
        for (int i = 0; i < inverseFFTArray.length; i++) {
            System.out.println(inverseFFTArray[i]);
        }
        if (Arrays.equals(array, inverseFFTArray)) {
            System.out.println("IFFT成功");
        } else {
            System.out.println("IFFT失败");
        }
        System.out.println();
    }
}

2、IFFT和IFFT是否正确,当大小为8的时候,打印的日志如下。将FFT和DFT生成的数据进行对比,发现FFT变换成功。将IFFT生成的数据和原始的数据进行对比,发现IFFT变换成功。

        ------开始------
        原数据: 
        大小为8
        161.0
        76.0
        88.0
        83.0
        5.0
        54.0
        149.0
        172.0
        
        一维离散傅里叶变换DFT: 
        花费时间 :16
        788.0
        234.48885271170673+108.37615433949871i
        -71.00000000000007+124.99999999999993i
        77.51114728829329-13.623845660501232i
        18.0-8.682745805954733E-14i
        77.51114728829299+13.623845660501132i
        -71.00000000000018-125.0000000000002i
        234.48885271170724-108.37615433949823i
        
        一维逆离散傅里叶变换IDFT: 
        花费时间 :0
        161.0
        76.0
        88.0
        83.0
        5.0
        54.0
        149.0
        172.0
        IDFT成功
        
        一维快速傅里叶变换FFT: 
        花费时间 :0
        788.0
        234.48885271170678+108.3761543394987i
        -71.00000000000001+125.0i
        77.51114728829322-13.623845660501324i
        18.0
        77.51114728829323+13.623845660501303i
        -70.99999999999999-125.0i
        234.48885271170678-108.37615433949867i
        
        一维逆快速傅里叶变换IFFT: 
        花费时间 :0
        161.0
        76.0
        88.0
        83.0
        5.0
        54.0
        149.0
        172.0
        IFFT成功
        
        ------结束------

2、当大小不为2次幂的时候,如6,FFT会在后面自动补0,知道2次幂为止,即8。但是这样子转换成频谱图会跟DFT的不一样,但是IFFT转换之后,再讲多余的给移除掉,最后的结果还是会跟原数据一样的。打印日志如下。

        ------开始------
        原数据: 
        大小为6
        82.0
        41.0
        232.0
        90.0
        183.0
        11.0
        
        一维离散傅里叶变换DFT: 
        花费时间 :0
        639.0
        -189.50000000000003-68.41600689897068i
        -61.49999999999996+16.45448267190423i
        355.0+1.0164568432923031E-13i
        -61.500000000000085-16.4544826719046i
        -189.4999999999998+68.41600689896998i
        
        一维逆离散傅里叶变换IDFT: 
        花费时间 :0
        82.0
        41.0
        232.0
        90.0
        183.0
        11.0
        IDFT成功
        
        一维快速傅里叶变换FFT: 
        花费时间 :0
        639.0
        -143.42640687119282-316.8528137423857i
        33.0+38.0i
        -58.573593128807154+147.1471862576143i
        355.0
        -58.57359312880715-147.1471862576143i
        33.0-38.0i
        -143.42640687119288+316.8528137423857i
        
        一维逆快速傅里叶变换IFFT: 
        花费时间 :0
        82.0
        41.0
        232.0
        90.0
        183.0
        11.0
        IFFT成功
        
        ------结束------

3、测试FFT、IFFT、DFT和IDF的性能比较。将大小设为2048,并将数组的打印注释掉。发现完全没法比。

傅里叶变换 一维快速傅里叶变换(快速的一维离散傅里叶变换、分治法)_第1张图片

4、将大小设为1百万,并将DFT和IDFT的代码注释掉。只要6s多。

傅里叶变换 一维快速傅里叶变换(快速的一维离散傅里叶变换、分治法)_第2张图片

 

 

你可能感兴趣的:(高数)