• C#实现FFT(递归法)


    C#实现FFT(递归法)

    1. C#实现复数类

    我们在进行信号分析的时候,难免会使用到复数。但是遗憾的是,C#没有自带的复数类,以下提供了一种复数类的构建方法。

    复数相比于实数,可以理解为一个二维数,构建复数类,我们需要实现以下这些内容:

    1. 复数实部与虚部的属性
    2. 复数与复数的加减乘除运算
    3. 复数与实数的加减乘除运算
    4. 复数取模
    5. 复数取相位角
    6. 欧拉公式(即eix+y

    C#实现的代码如下:

     public class Complex
        {
            double real;
            double imag;
            public Complex(double x, double y)   //构造函数
            {
                this.real = x;
                this.imag = y;
            }
            //通过属性实现对复数实部与虚部的单独查看和设置
            public double Real
            {
                set { this.real = value; }
                get { return this.real; }
            }
            public double Imag
            {
                set { this.imag = value; }
                get { return this.imag; }
            }
            //重载加法
            public static Complex operator +(Complex c1, Complex c2)
            {
                return new Complex(c1.real + c2.real, c1.imag + c2.imag);
            }
            public static Complex operator +(double c1, Complex c2)
            {
                return new Complex(c1 + c2.real, c2.imag);
            }
            public static Complex operator +(Complex c1, double c2)
            {
                return new Complex(c1.Real + c2, c1.imag);
            }
            //重载减法
            public static Complex operator -(Complex c1, Complex c2)
            {
                return new Complex(c1.real - c2.real, c1.imag - c2.imag);
            }
            public static Complex operator -(double c1, Complex c2)
            {
                return new Complex(c1 - c2.real, -c2.imag);
            }
            public static Complex operator -(Complex c1, double c2)
            {
                return new Complex(c1.real - c2, c1.imag);
            }
            //重载乘法
            public static Complex operator *(Complex c1, Complex c2)
            {
                double cr = c1.real * c2.real - c1.imag * c2.imag;
                double ci = c1.imag * c2.real + c2.imag * c1.real;
                return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));
            }
            public static Complex operator *(double c1, Complex c2)
            {
                double cr = c1 * c2.real;
                double ci = c1 * c2.imag;
                return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));
            }
            public static Complex operator *(Complex c1, double c2)
            {
                double cr = c1.Real * c2;
                double ci = c1.Imag * c2;
                return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));
            }
    
            //重载除法
            public static Complex operator /(Complex c1, Complex c2)
            {
                if (c2.real == 0 && c2.imag == 0)
                {
                    return new Complex(double.NaN, double.NaN);
                }
                else
                {
                    double cr = (c1.imag * c2.imag + c2.real * c1.real) / (c2.imag * c2.imag + c2.real * c2.real);
                    double ci = (c1.imag * c2.real - c2.imag * c1.real) / (c2.imag * c2.imag + c2.real * c2.real);
                    return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));           //保留四位小数后输出
                }
            }
          
            public static Complex operator /(double c1, Complex c2)
            {
                if (c2.real == 0 && c2.imag == 0)
                {
                    return new Complex(double.NaN, double.NaN);
                }
                else
                {
                    double cr = c1 * c2.Real / (c2.imag * c2.imag + c2.real * c2.real);
                    double ci = -c1 * c2.imag / (c2.imag * c2.imag + c2.real * c2.real);
                    return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));           //保留四位小数后输出
                }
            }
          
            public static Complex operator /(Complex c1, double c2)
            {
                if (c2 == 0)
                {
                    return new Complex(double.NaN, double.NaN);
                }
                else
                {
                    double cr = c1.Real / c2;
                    double ci = c1.imag / c2;
                    return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));           //保留四位小数后输出
                }
            }
            //创建一个取模的方法
            public static double Abs(Complex c)
            {
                return Math.Sqrt(c.imag * c.imag + c.real * c.real);
            }
            //创建一个取相位角的方法
            public static double Angle(Complex c)
            {
                return Math.Round(Math.Atan2(c.real, c.imag), 6);//保留6位小数输出
            }
            //重载字符串转换方法,便于显示复数
            public override string ToString()
            {
                if (imag >= 0)
                    return string.Format("{0}+i{1}", real, imag);
                else
                    return string.Format("{0}-i{1}", real, -imag);
            }
            //欧拉公式
            public static Complex Exp(Complex c)
            {
                double amplitude = Math.Exp(c.real);
                double cr = amplitude * Math.Cos(c.imag);
                double ci = amplitude * Math.Sin(c.imag);
                return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));//保留四位小数输出
            }
        }
    
    折叠

    2. 递归法实现FFT

    以下的递归法是基于奇偶分解实现的。

    奇偶分解的原理推导如下:

    X(k)=DFT[x(n)]=N1n=0x(n)WnkN=N/21r=0x(2r)W2rkN+N/21r=0x(2r+1)W(2r+1)kNx(n)=N/21r=0x(2r)(W2N)rk+WkNN/21r=0x(2r+1)(W2N)rk

    x(2r)x(2r+1)都是长度为N/21的数据序列,不妨令

    x1(n)=x(2r)x2(n)=x(2r+1)

    则原来的DFT就变成了:

    X(k)=N/21n=0x1(n)(W2N)nk+WkNN/21n=0x2(n)(W2N)nk=F(x1(n))+WkNF(x2(n))=X1(k)+WkNX2(k)

    于是,将原来的N点傅里叶变换变成了两个N/2点傅里叶变换的线性组合。

    但是,N/2点傅里叶变换只能确定N/2个频域数据,另外N/2个数据怎么确定呢?

    因为X1(k)X2(k)周期都是N/2,所以有

    X1(k+N/2)=X1(k),X2(k+N/2)=X2(k)Wk+N/2N=WkN

    从而得到:

    X(k+N/2)=X1(k+N/2)+Wk+N/2NX2(k+N/2)=X1(k)WknX2(k)

    综上,我们就可以得到递归法实现FFT的流程:

    1. 对于每组数据,按奇偶分解成两组数据

    2. 两组数据分别进行傅里叶变换,得到X1(k)X2(k)

    3. 总体数据的X(k)由下式确定:

      X(k)==X1(k)+WkNX2(k)X(k+N/2)=X1(k)WknX2(k)0kN/21

    4. 对上述过程进行递归

    具体代码实现如下:

    public Complex[] FFTre(Complex[] c)
    {
        int n = c.Length;
        Complex[] cout = new Complex[n];
        if (n == 1)
        {
            cout[0] = c[0];
            return cout;
        }
        else
        {
            double n_2_f = n / 2;
            int n_2 = (int)Math.Floor(n_2_f);
            Complex[] c1 = new Complex[n / 2];
            Complex[] c2 = new Complex[n / 2];
            for (int i = 0; i < n_2; i++)
            {
                c1[i] = c[2 * i];
                c2[i] = c[2 * i + 1];
            }
            Complex[] c1out = FFTre(c1);
            Complex[] c2out = FFTre(c2);
            Complex[] c3 = new Complex[n / 2];
            for (int i = 0; i < n / 2; i++)
            {
                c3[i] = new Complex(0, -2 * Math.PI * i / n);
            }
            for (int i = 0; i < n / 2; i++)
            {
                c2out[i] = c2out[i] * Complex.Exp(c3[i]);
            }
    
            for (int i = 0; i < n / 2; i++)
            {
                cout[i] = c1out[i] + c2out[i];
                cout[i + n / 2] = c1out[i] - c2out[i];
            }
            return cout;
        }
    }
    
    折叠

    3. 补充:窗函数

    顺便提供几个常用的窗函数:

    • Rectangle
    • Bartlett
    • Hamming
    • Hanning
    • Blackman
        public class WDSLib
        {
            //以下窗函数均为periodic
            public double[] Rectangle(int len)
            {
                double[] win = new double[len];
                for (int i = 0; i < len; i++)
                {
                    win[i] = 1;
                }
                return win;
            }
    
            public double[] Bartlett(int len)
            {
                double length = (double)len - 1;
                double[] win = new double[len];
                for (int i = 0; i < len; i++)
                {
                    if (i < len / 2) { win[i] = 2 * i / length; }
                    else { win[i] = 2 - 2 * i / length; }
                }
                return win;
            }
    
            public double[] Hamming(int len)
            {
                double[] win = new double[len];
                for (int i = 0; i < len; i++)
                {
                    win[i] = 0.54 - 0.46 * Math.Cos(Math.PI * 2 * i / len);
                }
                return win;
            }
    
            public double[] Hanning(int len)
            {
                double[] win = new double[len];
                for (int i = 0; i < len; i++)
                {
                    win[i] = 0.5 * (1 - Math.Cos(2 * Math.PI * i / len));
                }
                return win;
            }
    
            public double[] Blackman(int len)
            {
                double[] win = new double[len];
                for (int i = 0; i < len; i++)
                {
                    win[i] = 0.42 - 0.5 * Math.Cos(Math.PI * 2 * (double)i / len) + 0.08 * Math.Cos(Math.PI * 4 * (double)i / len);
                }
                return win;
            }
        }
    
    折叠
  • 相关阅读:
    Linux编译器 -- gcc/g++ 调试器 -- gdb
    如何使ssh操作linux 更安全
    CentOS7.6安装docker
    【解密ChatGPT】:从过去到未来,揭示其发展与变革
    【Tricks】关于如何防止edge浏览器偷取chrome浏览器的账号
    【ROS进阶篇】第九讲 基于Rviz和Arbotix控制的机器人模型运动
    LogTAD:无监督跨系统日志异常域检测
    算法每日一题(合并两个有序的数组)
    Elasticsearch8.13.4版本的Docker启动关闭HTTPS
    软件测试行业5年经验,薪资不如刚入行的应届生,真是日了狗了,问题究竟出在哪里?
  • 原文地址:https://www.cnblogs.com/yang-ding/p/16466018.html