FFT与二维FFT的C#实现

  傅里叶变换在信息处理应用中具有很实用的价值,而快速傅里叶变换,即FFT,是实用的计算算法。

  本文介绍FFT和2维FFT的C#实现方法。

1. FFT编程依据

  FFT是按照如图结构(也称蝶形结构)进行运算(图片来源于网络)。

FFT算法

  图中,箭头代表数据流向,箭头与箭头的合并点代表相加,箭头下面的常数代表相乘,WN(注:N为下标) = exp(-j * 2 * PI / N),j为虚数单位,DIT的输入(或DIF的输出)序列重排点y[m]与初始输入点x[n]的关系为:m与n互为倒位序(例:001 -- 100,011 -- 110)。

  该图给出的是8点FFT例程,其余2^N点FFT可按照这个图进行类推,类推后可以发现,2^N点FFT只需用三个循环套嵌即可实现。

  对于idft的算法,根据公式X[k] = 对n求和{x[n] * (WN) ^ kn}以及x[n] = 对k求和{X[k] * (WN) ^ (-kn)} /N可知,只需将FFT算法中的WN换为(1 / WN)并将最后结果除以N即可得到idft。

2. FFT算法实现

  由于作者并不擅长面向对象编程,仅仅是在功能上实现了本文需求,代码在阅读、效率上有很多缺陷。

  下面是FFT算法的实现,完整的实现在文章后面。

        // 快速傅里叶变换
        // 作者:死猫
        // ComplexList src:数据输入
        // ref int lenght:欲变换数据的长度,函数调用后此值更改为实际变换长度
        // int flag:区分fft或dtft,为1为fft,为-1为idft
        private static ComplexList fft_core(ComplexList src, ref int lenght, int flag)
        {
            //按时间抽取FFT方法(DIT)

            //补零后长度
            int relog2N = ReLog2N(lenght);

            int bitlenghth = relog2N;
            int N = 0x01 << bitlenghth;

            //重新复制数据,同时进行
            //    逆序排放,并补零
            int index;
            ComplexList Register = new ComplexList(N);
            for (int i = 0; i < N; i++)
            {
                index = ReArrange(i, bitlenghth);
                Register[i] = (index < src.Lenght) ? src[index] : new Complex(0);
            }

            //生成WN表,以免运行时进行重复计算
            ComplexList WN = new ComplexList(N / 2);
            for (int i = 0; i < N / 2; i++)
            {
                WN[i] = new Complex(Math.Cos(2 * Math.PI / N * i), -flag * Math.Sin(2 * Math.PI / N * i));
            }

            //蝶形运算
            int Index0, Index1;
            Complex temp;
            for (int steplenght = 2; steplenght <= N; steplenght *= 2)
            {
                for (int step = 0; step < N / steplenght; step++)
                {
                    for (int i = 0; i < steplenght / 2; i++)
                    {
                        Index0 = steplenght * step + i;
                        Index1 = steplenght * step + i + steplenght / 2;

                        temp = Register[Index1] * WN[N / steplenght * i];
                        Register[Index1] = Register[Index0] - temp;
                        Register[Index0] = Register[Index0] + temp;
                    }
                }
            }

            //若为idft
            if (-1 == flag)
            {
                for (int i = 0; i < Register.Lenght; i++)
                {
                    Register[i] /= N;
                }
            }

            //赋值
            lenght = N;

            /*
            //清理内存
            WN = null;
            temp = null;
            GC.Collect();
            */

            //返回
            return Register;
        }

3. 维FFT变换

  2维傅里叶变换是在1维傅里叶变换基础上实现的,实现方法为:

  1. 对2维输入数据的每一行进行FFT变换,变换结果仍然按行存入一个二维数组中;

  2. 对按行FFT变换后的结果,对每一列进行FFT变换,变换结果仍然按列存入一个二维数组中,该数组即为2维FFT变换结果。

  代码实现如下:

        // 2维快速傅里叶变换
        // 作者:死猫
        // int width, int height:欲变换数据的长度和宽度,函数调用后此值更改为实际变换长度
        // int flag:区分fft或dtft,为1为fft,为-1为idft
        private static Complex2D fft_2D_core(Complex2D src, ref int width, ref int height, int flag)
        {
            //补零后长度
            int width_Log2N = ReLog2N(width);
            int height_Log2N = ReLog2N(height);
            int Relog2N = Math.Max(width_Log2N, height_Log2N);
            int ReWidth = 0x01 << Relog2N;
            int ReHeight = 0x01 << Relog2N;

            //重新复制数据,清零
            Complex2D ReList2D = new Complex2D(ReWidth, ReHeight);
            ReList2D.Clear();
            int width_temp = Math.Min(src.Width, width);
            int height_temp = Math.Min(src.Height, height);
            for (int i = 0; i < height_temp; i++)
            {
                for (int j = 0; j < width_temp; j++)
                {
                    ReList2D[i, j] = new Complex(src[i, j].Re, src[i, j].Im);
                }
            }

            ComplexList Xn;
            ComplexList Xk;
            int Lenght_temp;

            //第1遍fft
            for (int i = 0; i < ReHeight; i++)
            {
                Xn = ReList2D.GetRow(i);
                Lenght_temp = Xn.Lenght;
                Xk = fft_core(Xn, ref Lenght_temp, flag);
                ReList2D.SetRow(i, Xk);
            }

            //第2遍fft
            for (int i = 0; i < ReWidth; i++)
            {
                Xn = ReList2D.GetColumn(i);
                Lenght_temp = Xn.Lenght;
                Xk = fft_core(Xn, ref Lenght_temp, flag);
                ReList2D.SetColumn(i, Xk);
            }

            //赋值
            width = ReWidth;
            height = ReHeight;

            //清理内存
            Xn = null;
            Xk = null;
            GC.Collect();

            //返回
            return ReList2D;
        }

3. 二维FFT测试结果

  我对一张图片进行低通、高通滤波,其传输函数分别为:

  低通:Hlp(z) = (1 - a) / 2 * (1 + 1 / z) / (1 - a * 1 / z),a = 0.8;

  高通:Hhp(z) = (1 + a) / 2 * (1 - 1 / z) / (1 - a * 1 / z),a = 0.7。

  低通、高通滤波测试结果分别如下:

低通图片滤波

高通图片滤波

4. 其余相关代码实现

  由于作者并不擅长面向对象编程,仅仅是在功能上实现了本文需求,代码在阅读、效率上有很多缺陷。

  代码1:

        // 快速傅里叶变换
        // ComplexList src:数据输入,若长度非2的n次幂,
        //     则函数对末尾补零后的数据进行运算
        public static ComplexList fft(ComplexList src)
        {
            int lenght = src.Lenght;
            return fft_core(src, ref lenght, 1);
        }
        // 快速傅里叶变换
        // ComplexList src:数据输入
        // ref int lenght:欲变换的长度,函数调用后此值更改为实际变换长度
        public static ComplexList fft(ComplexList src, ref int lenght)
        {
            return fft_core(src, ref lenght, 1);
        }

        // 快速傅里叶变换的逆变换
        // ComplexList src:数据输入
        public static ComplexList idft(ComplexList src)
        {
            int lenght = src.Lenght;
            return idft(src, ref lenght);
        }

        // 快速傅里叶变换的逆变换
        // ComplexList src:数据输入
        // ref int lenght:欲变换数据的长度,函数调用后此值更改为实际变换长度
        public static ComplexList idft(ComplexList src, ref int lenght)
        {
            return fft_core(src, ref lenght, -1);
        }

        public static Complex2D fft_2D(Complex2D src)
        {
            int width = src.Width;
            int height = src.Height;
            return fft_2D_core(src, ref width, ref height, 1);
        }

        public static Complex2D idft_2D(Complex2D src)
        {
            int width = src.Width;
            int height = src.Height;
            return fft_2D_core(src, ref width, ref height, -1);
        }

  代码2:

        // 获取按位逆序,bitlenght为数据长度
        // fft函数内使用
        private static int ReArrange(int dat, int bitlenght)
        {
            int ret = 0;
            for (int i = 0; i < bitlenght; i++)
            {
                if (0 != (dat & (0x01 << i))) ret |= ((0x01 << (bitlenght - 1)) >> i);
            }
            return ret;
        }

        // 获取扩展长度后的幂次
        // 由于fft要求长度为2^n,所以用此函数来获取所需长度
        public static int ReLog2N(int count)
        {
            int log2N = 0;
            uint mask = 0x80000000;
            for (int i = 0; i < 32; i++)
            {
                if (0 != ((mask >> i) & count))
                {
                    if ((mask >> i) == count) log2N = 31 - i;
                    else log2N = 31 - i + 1;
                    break;
                }
            }
            return log2N;
        }

  代码3:

    public class Complex2D
    {
        double[] _Complex2D_Re;
        double[] _Complex2D_Im;
        public int Width { get; private set; }
        public int Height { get; private set; }
        public Complex this[int Row, int Column]
        {
            get 
            {
                return new Complex(_Complex2D_Re[Row * Width + Column], _Complex2D_Im[Row * Width + Column]); 
            }
            set 
            {
                _Complex2D_Re[Row * Width + Column] = ((Complex)value).Re;
                _Complex2D_Im[Row * Width + Column] = ((Complex)value).Im; 
            }
        }

        public Complex2D(int width, int height)
        {
            Width = width;
            Height = height;
            int lenght = Width * Height;
            _Complex2D_Re = new double[lenght];
            _Complex2D_Im = new double[lenght];
        }

        public void Clear()
        {
            Array.Clear(_Complex2D_Re, 0, _Complex2D_Re.Length);
            Array.Clear(_Complex2D_Im, 0, _Complex2D_Im.Length);
        }

        public ComplexList GetColumn(int index)
        {
            ComplexList ret = new ComplexList(Height);
            for(int i = 0; i < Height; i++)
            {
                ret[i] = this[i, index];
            }
            return ret;
        }
        public int SetColumn(int index, ComplexList src)
        {
            for (int i = 0; i < Height; i++)
            {
                this[i, index] = (i < src.Lenght) ? src[i] : new Complex(0);
            }
            return 0;
        }
        public ComplexList GetRow(int index)
        {
            ComplexList ret = new ComplexList(Width);
            for(int i = 0; i < Width; i++)
            {
                ret[i] = this[index, i];
            }
            return ret;
        }
        public int SetRow(int index, ComplexList src)
        {
            for (int i = 0; i < Width; i++)
            {
                this[index, i] = (i < src.Lenght) ? src[i] : new Complex(0);
            }
            return 0;
        }
        public Complex2D GetAmplitude()
        {
            Complex2D ret = new Complex2D(Width, Height);
            for (int i = 0; i < Height; i++)
            {
                for (int j = 0; j < Width; j++)
                {
                    ret[i, j] = new Complex(this[i, j].Modulus());
                }
            }
            return ret;
        }
    }
    public class ComplexList
    {
        double[] _ComplexList_Re;
        double[] _ComplexList_Im;
        public int Lenght { get; private set; }
        public Complex this[int Index]
        {
            get
            {
                return new Complex(_ComplexList_Re[Index], _ComplexList_Im[Index]);
            }
            set
            {
                _ComplexList_Re[Index] = ((Complex)value).Re;
                _ComplexList_Im[Index] = ((Complex)value).Im;
            }
        }

        public ComplexList(int lenght)
        {
            Lenght = lenght;
            _ComplexList_Re = new double[Lenght];
            _ComplexList_Im = new double[Lenght];
        }
        public ComplexList(double[] re)
        {
            Lenght = re.Length;
            _ComplexList_Re = re;
            _ComplexList_Im = new double[Lenght];
        }
        public ComplexList(double[] re, double[] im)
        {
            Lenght = Math.Max(re.Length, im.Length);
            if (re.Length == im.Length)
            {
                _ComplexList_Re = re;
                _ComplexList_Im = im;
            }
            else
            {
                _ComplexList_Re = new double[Lenght];
                _ComplexList_Im = new double[Lenght];
                for (int i = 0; i < re.Length; i++) _ComplexList_Re[i] = re[i];
                for (int i = 0; i < im.Length; i++) _ComplexList_Im[i] = im[i];
            }
        }

        public void Clear()
        {
            Array.Clear(_ComplexList_Re, 0, _ComplexList_Re.Length);
            Array.Clear(_ComplexList_Im, 0, _ComplexList_Im.Length);
        }

        public double[] GetRePtr()
        {
            return _ComplexList_Re;
        }
        public double[] GetImPtr()
        {
            return _ComplexList_Im;
        }
        public ComplexList Clone()
        {
            return new ComplexList((double[])(_ComplexList_Re.Clone()), (double[])(_ComplexList_Im.Clone()));
        }
        public ComplexList GetAmplitude()
        {
            double[] amp = new double[Lenght];
            for (int i = 0; i < Lenght; i++)
            {
                amp[i] = this[i].Modulus();
            }
            return new ComplexList(amp);
        }
    }
    public class Complex
    {
        public double Re;
        public double Im;
        public Complex()
        {
            Re = 0;
            Im = 0;
        }
        public Complex(double re)
        {
            Re = re;
            Im = 0;
        }
        public Complex(double re, double im)
        {
            Re = re;
            Im = im;
        }

        public double Modulus()
        {
            return Math.Sqrt(Re * Re + Im * Im);
        }

        public override string ToString()
        {
            string retStr;
            if (Math.Abs(Im) < 0.0001) retStr = Re.ToString("f4");
            else if (Math.Abs(Re) < 0.0001)
            {
                if (Im > 0) retStr = "j" + Im.ToString("f4");
                else retStr = "- j" + (0 - Im).ToString("f4");
            }
            else
            {
                if (Im > 0) retStr = Re.ToString("f4") + "+ j" + Im.ToString("f4");
                else retStr = Re.ToString("f4") + "- j" + (0 - Im).ToString("f4");
            }
            retStr += " ";
            return retStr;
        }

        //操作符重载
        public static Complex operator +(Complex c1, Complex c2)
        {
            return new Complex(c1.Re + c2.Re, c1.Im + c2.Im);
        }
        public static Complex operator +(double d, Complex c)
        {
            return new Complex(d + c.Re, c.Im);
        }
        public static Complex operator -(Complex c1, Complex c2)
        {
            return new Complex(c1.Re - c2.Re, c1.Im - c2.Im);
        }
        public static Complex operator -(double d, Complex c)
        {
            return new Complex(d - c.Re, -c.Im);
        }
        public static Complex operator *(Complex c1, Complex c2)
        {
            return new Complex(c1.Re * c2.Re - c1.Im * c2.Im, c1.Re * c2.Im + c2.Re * c1.Im);
        }
        public static Complex operator *(Complex c, double d)
        {
            return new Complex(c.Re * d, c.Im * d);
        }
        public static Complex operator *(double d, Complex c)
        {
            return new Complex(c.Re * d, c.Im * d);
        }
        public static Complex operator /(Complex c, double d)
        {
            return new Complex(c.Re / d, c.Im / d);
        }
        public static Complex operator /(double d, Complex c)
        {
            double temp = d / (c.Re * c.Re + c.Im * c.Im);
            return new Complex(c.Re * temp, -c.Im * temp);
        }
        public static Complex operator /(Complex c1, Complex c2)
        {
            double temp = 1 / (c2.Re * c2.Re + c2.Im * c2.Im);
            return new Complex((c1.Re * c2.Re + c1.Im * c2.Im) * temp, (-c1.Re * c2.Im + c2.Re * c1.Im) * temp);
        }
    }

 ==================

20200208补充源工程,下载地址:

https://files.cnblogs.com/files/Si-Mao/FFT.rar

原文地址:https://www.cnblogs.com/Si-Mao/p/3950023.html