C#实现FFT(递归法)

1. C#实现复数类

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

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

  1. 复数实部与虚部的属性
  2. 复数与复数的加减乘除运算
  3. 复数与实数的加减乘除运算
  4. 复数取模
  5. 复数取相位角
  6. 欧拉公式(即\(e^{ix+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

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

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

\[\begin{split}
X(k)=DFT[x(n)]&=\sum_{n=0}^{N-1}x(n)W_N^{nk}\\
&=\sum_{r=0}^{N/2-1}x(2r)W_N^{2rk}+\sum_{r=0}^{N/2-1}x(2r+1)W_N^{(2r+1)k},将x(n)按奇偶分解\\
&=\sum_{r=0}^{N/2-1}x(2r)(W_N^{2})^{rk}+W_N^k\sum_{r=0}^{N/2-1}x(2r+1)(W_N^2)^{rk}
\end{split}
\]

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

\[x_1(n)=x(2r)\\
x_2(n)=x(2r+1)
\]

则原来的DFT就变成了:

\[\begin{split}
X(k)&=\sum_{n=0}^{N/2-1}x_1(n)(W_N^{2})^{nk}+W_N^k\sum_{n=0}^{N/2-1}x_2(n)(W_N^2)^{nk}\\
&=F(x_1(n))+W_N^kF(x_2(n))\\
&=X_1(k)+W_N^kX_2(k)
\end{split}
\]

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

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

因为\(X_1(k)\)和\(X_2(k)\)周期都是\(N/2\),所以有

\[X_1(k+N/2)=X_1(k),X_2(k+N/2)=X_2(k)\\
W_N^{k+N/2}=-W_N^k\\
\]

从而得到:

\[\begin{split}
X(k+N/2)&=X_1(k+N/2)+W_N^{k+N/2}X_2(k+N/2)\\
&=X_1(k)-W_n^kX_2(k)
\end{split}
\]

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

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

  2. 两组数据分别进行傅里叶变换,得到\(X_1(k)\)和\(X_2(k)\)

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

    \[X(k)==X_1(k)+W_N^kX_2(k)\\
    X(k+N/2)=X_1(k)-W_n^kX_2(k)\\
    0\le k \le N/2 -1
    \]
  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;
}
}

C#实现FFT(递归法)的更多相关文章

  1. 算法笔记_013:汉诺塔问题(Java递归法和非递归法)

    目录 1 问题描述 2 解决方案  2.1 递归法 2.2 非递归法 1 问题描述 Simulate the movement of the Towers of Hanoi Puzzle; Bonus ...

  2. 递归法绑定文件夹到导航树&在指定文件夹下新建文件夹

    protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { if (Request.QueryString[&q ...

  3. 归并排序,递归法,C语言实现。

    利用归并排序法对序列排序的示意图(递归法): 一.算法分析:利用递归的分治方法:1.将原序列细分,直到成为单个元素:2.在将分割后的序列一层一层地按顺序合并,完成排序.细分通过不断深入递归完成,合并通 ...

  4. PHP利用递归法获取多级类别的树状数组

    数据结构:category(id, pid, name),对应:信息ID,父项ID,类别名 测试数据: $aryCate = array( array('id' => 1, 'pid' => ...

  5. 八皇后问题详细分析与解答(递归法解答,c#语言描述)

    八皇后问题,是一个古老而著名的问题,是回溯算法的典型例题.该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或 ...

  6. 回溯法 leetcode题解 Combination Sum 递归法

    题目大意:给出一个数组,用这些数组里的元素去凑一个target.元素可以重复取用. 感觉对这种题目还是生疏的.脑子里有想法,但是不知道怎么表达出来. 先记录下自己的递归法.应该还可以用循环实现. 回溯 ...

  7. 用递归法计算从n个人中选选k个人组成一个委员会的不同组合数

    用递归法计算从n个人中选选k个人组成一个委员会的不同组合数. 分析 由n个人里选k个人的组合数= 由n-1个人里选k个人的组合数+由n-1个人里选k-1个人的组合数: 当n = k或k = 0时,组合 ...

  8. JavaScript 递归法排列组合二维数组2

    <html> <head> <title>二维数组排列组合</title> </head> <body> <div id= ...

  9. JavaScript 递归法排列组合二维数组

    <html> <head> <title>二维数组排列组合</title> </head> <body> <div id= ...

随机推荐

  1. python学习-Day38-HTTP

    目录 HTTP简介 可以充当客户端的有哪些 HTTP 工作原理 HTTP协议 HTTP协议四大特性 数据格式 请求格式: 响应格式: HTTP 请求方法 HTTP 状态码分类 响应分为五类: HTTP ...

  2. Infrastructure 知识: dnf对module的处理

    引言 从RHEL8/CentOS8开始,dnf取代yum作为rpm 包管理工具.与之而来的还有模块(module)这个东西. 有了它们的加持,让在同一个OS上安装不同版本的软件或者开发语言的工作比之前 ...

  3. ONNXRuntime学习笔记(二)

    继上一篇计划的实践项目,这篇记录我训练模型相关的工作. 首先要确定总体目标:训练一个pytorch模型,CIFAR-100数据集测试集acc达到90%:部署后推理效率达到50ms/张, 部署平台为wi ...

  4. Python图像处理:如何获取图像属性、兴趣ROI区域及通道处理

    摘要:本篇文章主要讲解Python调用OpenCV获取图像属性,截取感兴趣ROI区域,处理图像通道. 本文分享自华为云社区<[Python图像处理] 三.获取图像属性.兴趣ROI区域及通道处理 ...

  5. Glide源码解析二---into方法

    转载请标明出处,维权必究: https://www.cnblogs.com/tangZH/p/12543154.html Glide作为一个强大的图片加载框架,已经被android官方使用,所以,明白 ...

  6. JDK自带线程池学习

    JDK自带线程池 线程池的状态 线程有如下状态 RUNNING状态:Accept new tasks and process queued tasks SHUTDOWN状态:Don't accept ...

  7. 513. Find Bottom Left Tree Value - LeetCode

    Question 513. Find Bottom Left Tree Value Solution 题目大意: 给一个二叉树,求最底层,最左侧节点的值 思路: 按层遍历二叉树,每一层第一个被访问的节 ...

  8. cefsharp + winform 内嵌网页的触屏输入焦点问题

    原文 现象 我正在使用 cefsharp + winform 建立一个桌面程序用于显示网页.但程序启动后触屏点击网页中的输入框,使用键盘输入,文字输入不进去.win + D 最小化程序后,再恢复窗口才 ...

  9. Proxmox 7.2 部署 DoraCloud桌面云,支持vGPU

    介绍 本文介绍了使用Proxmox + DoraCloud,将一台图形工作站(配置有Tesla P4显卡)改造成一台桌面云主机.可以满足多个桌面用户同时使用3D应用的需求. 该方案适合于小型工作室.电 ...

  10. 【Java面试】Spring中 BeanFactory和FactoryBean的区别

    一个工作了六年多的粉丝,胸有成竹的去京东面试. 然后被Spring里面的一个问题卡住,唉,我和他说,6年啦,Spring都没搞明白? 那怎么去让面试官给你通过呢? 这个问题是: Spring中Bean ...