FFTHelper.cs 12.8 KB
using System;

#if !NETSTANDARD2_0
using System.Drawing.Drawing2D;
#endif

namespace HslCommunication.Algorithms.Fourier
{
    /// <summary>
    /// 离散傅氏变换的快速算法,处理的信号,适合单周期信号数为2的N次方个,支持变换及逆变换
    /// </summary>
    public class FFTHelper
    {


        /// <summary>
        /// 
        /// </summary>
        /// <param name="xreal"></param>
        /// <param name="ximag"></param>
        /// <param name="n"></param>
        private static void bitrp(double[] xreal, double[] ximag, int n)
        {
            // 位反转置换 Bit-reversal Permutation
            int i, j, a, b, p;

            for (i = 1, p = 0; i < n; i *= 2)
            {
                p++;
            }
            for (i = 0; i < n; i++)
            {
                a = i;
                b = 0;
                for (j = 0; j < p; j++)
                {
                    b = b * 2 + a % 2;
                    a = a / 2;
                }
                if (b > i)
                {
                    double t = xreal[i];
                    xreal[i] = xreal[b];
                    xreal[b] = t;

                    t = ximag[i];
                    ximag[i] = ximag[b];
                    ximag[b] = t;
                }
            }
        }


        /// <summary>
        /// 快速傅立叶变换
        /// </summary>
        /// <param name="xreal">实数部分</param>
        /// <returns>变换后的数组值</returns>
        public static double[] FFT(double[] xreal)
        {
            return FFT(xreal, new double[xreal.Length]);
        }


#if !NETSTANDARD2_0

        /// <summary>
        /// 获取FFT变换后的显示图形,需要指定图形的相关参数
        /// </summary>
        /// <param name="xreal">实数部分的值</param>
        /// <param name="width">图形的宽度</param>
        /// <param name="heigh">图形的高度</param>
        /// <param name="lineColor">线条颜色</param>
        /// <returns>等待呈现的图形</returns>
        /// <remarks>
        /// <note type="warning">.net standrard2.0 下不支持。</note>
        /// </remarks>
        public static Bitmap GetFFTImage( double[] xreal,int width,int heigh ,Color lineColor)
        {
            double[] ximag = new double[xreal.Length];                // 构造虚对象
            double[] array = FFT( xreal, ximag );                     // 傅立叶变换

            Bitmap bitmap = new Bitmap( width, heigh );               // 构造图形
            Graphics g = Graphics.FromImage( bitmap );
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
            g.Clear( Color.White );

            Pen pen_Line = new Pen( Color.DimGray, 1 );               // 定义画笔资源
            Pen pen_Dash = new Pen( Color.LightGray, 1 );
            Pen pen_Fourier = new Pen( lineColor, 1 );
            pen_Dash.DashPattern = new float[2] { 5, 5 };
            pen_Dash.DashStyle = DashStyle.Custom;

            Font Font_Normal = SystemFonts.DefaultFont;               // 定义字体资源

            StringFormat sf_right = new StringFormat( );
            sf_right.Alignment = StringAlignment.Far;
            sf_right.LineAlignment = StringAlignment.Center;

            StringFormat sf_center = new StringFormat( );
            sf_center.LineAlignment = StringAlignment.Center;
            sf_center.Alignment = StringAlignment.Center;

            int padding_top = 20;
            int padding_left = 49;
            int padding_right = 49;
            int padding_down = 30;
            int sections = 9;

            // g.DrawLine( pen_Line, new Point( padding_left, padding_top ), new Point( padding_left, heigh - padding_down ) );
            
            float paint_height = heigh - padding_top - padding_down;
            float paint_width = width - padding_left - padding_right;

            if (array.Length > 1)
            {
                double Max = array.Max( );
                double Min = array.Min( );

                Max = Max - Min > 1 ? Max : Min + 1;
                double Length = Max - Min;
                
                //提取峰值
                List<float> Peaks = new List<float>( );
                if (array.Length >= 2)
                {
                    if (array[0] > array[1])
                    {
                        Peaks.Add( 0 );
                    }

                    for (int i = 1; i < array.Length - 2; i++)
                    {
                        if (array[i - 1] < array[i] && array[i] > array[i + 1])
                        {
                            Peaks.Add( i );
                        }
                    }
                    
                    if (array[array.Length - 1] > array[array.Length - 2])
                    {
                        Peaks.Add( array.Length - 1 );
                    }
                }
                

                //高400
                for (int i = 0; i < sections; i++)
                {
                    RectangleF rec = new RectangleF( -10f, (float)i / (sections - 1) * paint_height, padding_left + 8f, 20f );
                    double n = (sections - 1 - i) * Length / (sections - 1) + Min;
                    g.DrawString( n.ToString( "F1" ), Font_Normal, Brushes.Black, rec, sf_right );
                    g.DrawLine( 
                        pen_Dash, padding_left - 3, paint_height * i / (sections - 1) + padding_top,
                        width - padding_right, paint_height * i / (sections - 1) + padding_top );
                }

                float intervalX = paint_width / array.Length;                        // 横向间隔

                for (int i = 0; i < Peaks.Count; i++)
                {
                    if (array[(int)Peaks[i]] * 200 / Max > 1)
                    {
                        g.DrawLine( pen_Dash, Peaks[i] * intervalX + padding_left + 1, padding_top, Peaks[i] * intervalX + padding_left + 1, heigh - padding_down );
                        RectangleF rec = new RectangleF( Peaks[i] * intervalX + padding_left + 1 - 40, heigh - padding_down + 1, 80f, 20f );

                        g.DrawString( Peaks[i].ToString( ), Font_Normal, Brushes.DeepPink, rec, sf_center );
                    }
                }
                
                for (int i = 0; i < array.Length; i++)
                {
                    PointF point = new PointF( );
                    point.X = i * intervalX + padding_left + 1;
                    point.Y = (float)(paint_height - (array[i] - Min) * paint_height / Length + padding_top);

                    PointF point2 = new PointF( );
                    point2.X = i * intervalX + padding_left + 1;
                    point2.Y = (float)(paint_height - (Min - Min) * paint_height / Length + padding_top);
                    

                    g.DrawLine( Pens.Tomato, point, point2 );
                }
            }
            else
            {
                double Max = 100;
                double Min = 0;
                double Length = Max - Min;
                //高400
                for (int i = 0; i < sections; i++)
                {
                    RectangleF rec = new RectangleF( -10f, (float)i / (sections - 1) * paint_height, padding_left + 8f, 20f );
                    double n = (sections - 1 - i) * Length / (sections - 1) + Min;
                    g.DrawString( n.ToString( "F1" ), Font_Normal, Brushes.Black, rec, sf_right );
                    g.DrawLine(
                        pen_Dash, padding_left - 3, paint_height * i / (sections - 1) + padding_top,
                        width - padding_right, paint_height * i / (sections - 1) + padding_top );
                }
            }


            pen_Dash.Dispose( );
            pen_Line.Dispose( );
            pen_Fourier.Dispose( );
            Font_Normal.Dispose( );
            sf_right.Dispose( );
            sf_center.Dispose( );
            g.Dispose( );
            return bitmap;
        }

#endif


        /// <summary>
        /// 快速傅立叶变换
        /// </summary>
        /// <param name="xreal">实数部分,数组长度最好为2的n次方</param>
        /// <param name="ximag">虚数部分,数组长度最好为2的n次方</param>
        /// <returns>变换后的数组值</returns>
        public static double[] FFT(double[] xreal, double[] ximag)
        {
            //n值为2的N次方
            int n = 2;
            while (n <= xreal.Length)
            {
                n *= 2;
            }
            n /= 2;

            // 快速傅立叶变换,将复数 x 变换后仍保存在 x 中,xreal, ximag 分别是 x 的实部和虚部
            double[] wreal = new double[n / 2];
            double[] wimag = new double[n / 2];
            double treal, timag, ureal, uimag, arg;
            int m, k, j, t, index1, index2;

            bitrp(xreal, ximag, n);

            // 计算 1 的前 n / 2 个 n 次方根的共轭复数 W'j = wreal [j] + i * wimag [j] , j = 0, 1, ... , n / 2 - 1
            arg = (-2 * Math.PI / n);
            treal = Math.Cos(arg);
            timag = Math.Sin(arg);
            wreal[0] = 1.0f;
            wimag[0] = 0.0f;
            for (j = 1; j < n / 2; j++)
            {
                wreal[j] = wreal[j - 1] * treal - wimag[j - 1] * timag;
                wimag[j] = wreal[j - 1] * timag + wimag[j - 1] * treal;
            }

            for (m = 2; m <= n; m *= 2)
            {
                for (k = 0; k < n; k += m)
                {
                    for (j = 0; j < m / 2; j++)
                    {
                        index1 = k + j;
                        index2 = index1 + m / 2;
                        t = n * j / m;    // 旋转因子 w 的实部在 wreal [] 中的下标为 t
                        treal = wreal[t] * xreal[index2] - wimag[t] * ximag[index2];
                        timag = wreal[t] * ximag[index2] + wimag[t] * xreal[index2];
                        ureal = xreal[index1];
                        uimag = ximag[index1];
                        xreal[index1] = ureal + treal;
                        ximag[index1] = uimag + timag;
                        xreal[index2] = ureal - treal;
                        ximag[index2] = uimag - timag;
                    }
                }
            }

            double[] result = new double[n];
            for (int i = 0; i < result.Length; i++)
            {
                result[i] = Math.Sqrt(Math.Pow(xreal[i], 2) + Math.Pow(ximag[i], 2));
            }

            return result;
        }


        /// <summary>
        /// 快速傅立叶变换的逆变换
        /// </summary>
        /// <param name="xreal">实数部分,数组长度最好为2的n次方</param>
        /// <param name="ximag">虚数部分,数组长度最好为2的n次方</param>
        /// <returns>2的多少次方</returns>
        public static int IFFT(double[] xreal, double[] ximag)
        {
            //n值为2的N次方
            int n = 2;
            while (n <= xreal.Length)
            {
                n *= 2;
            }
            n /= 2;

            // 快速傅立叶逆变换
            double[] wreal = new double[n / 2];
            double[] wimag = new double[n / 2];
            double treal, timag, ureal, uimag, arg;
            int m, k, j, t, index1, index2;

            bitrp(xreal, ximag, n);

            // 计算 1 的前 n / 2 个 n 次方根 Wj = wreal [j] + i * wimag [j] , j = 0, 1, ... , n / 2 - 1
            arg = (2 * Math.PI / n);
            treal = (Math.Cos(arg));
            timag = (Math.Sin(arg));
            wreal[0] = 1.0f;
            wimag[0] = 0.0f;
            for (j = 1; j < n / 2; j++)
            {
                wreal[j] = wreal[j - 1] * treal - wimag[j - 1] * timag;
                wimag[j] = wreal[j - 1] * timag + wimag[j - 1] * treal;
            }

            for (m = 2; m <= n; m *= 2)
            {
                for (k = 0; k < n; k += m)
                {
                    for (j = 0; j < m / 2; j++)
                    {
                        index1 = k + j;
                        index2 = index1 + m / 2;
                        t = n * j / m;    // 旋转因子 w 的实部在 wreal [] 中的下标为 t
                        treal = wreal[t] * xreal[index2] - wimag[t] * ximag[index2];
                        timag = wreal[t] * ximag[index2] + wimag[t] * xreal[index2];
                        ureal = xreal[index1];
                        uimag = ximag[index1];
                        xreal[index1] = ureal + treal;
                        ximag[index1] = uimag + timag;
                        xreal[index2] = ureal - treal;
                        ximag[index2] = uimag - timag;
                    }
                }
            }

            for (j = 0; j < n; j++)
            {
                xreal[j] /= n;
                ximag[j] /= n;
            }

            return n;
        }
    }
}