C++实现FFT频谱分析

2023-11-19

Update:9/10/2022 鸽了太久…增补了一些新的表述和简单推导,以及FFT在算法竞赛中的应用部分。帖子里的代码已经分别在2021全国大学生电子设计竞赛、洛谷OJ和课程设计中实战过,可靠性有保障。
Origin:10/23/2021 原始文章,刚学完FFT时做的一些笔记…漏洞挺多


原理

找一本数字信号处理的书,把DFT的原理耐心看一遍就能明白所有前置知识的概念,比如什么是W(N,nk),为什么要把实数序列拓展到复数域上,不要看xxx博文的介绍。FFT就是DFT的一种快速实现算法,DFT复杂度O( n 2 n^2 n2),FFT可以把复杂度降到O( n l o g n nlogn nlogn)。FFT分为基2 时间抽取法与基2 频率抽取法,本文介绍的是时间抽取法。
FFT的实现步骤主要分为三步:

  • 将原序列扩展到复数域上,然后进行序数重排(元素的交换)
  • 归一化蝶形系数
  • 按照M级分解的顺序从左到右逐级进行蝶形运算

序数重排

就是把序列下标的二进制编码倒置,新序列x‘[i]=x[rev(i)],软件法或者硬件寻址法(比如DSP)都可以。

蝶形系数

相对于DFT优势在于一个蝴蝶结的计算只要一次乘法,两次加减法
W就是 W N 2 M − i j {W_\frac{N}{2^{M-i}}}^{j} W2MiNj,

  • W A B = e − j 2 π B A {W_A}^B=e^{-j\frac{2\pi B}{A}} WAB=ejA2πB
  • i是第i级蝶形计算 ( 1 < = i < = M ) (1<=i<=M) (1<=i<=M)
  • j是第i级蝶形运算的第j个W ( 0 < = j < = 2 i ) (0<=j<=2^i) (0<=j<=2i)
  • N = 2 M N=2^M N=2M (采样点数)

蝶形系数有三个重要性质:

  • 周期性: W N n {W_N}^n WNn = W N n + i N ={W_N}^{n+iN} =WNn+iN,i为整数。
  • 可约性: W m N k n = W N k n m {W_{mN}}^{kn}={W_\frac{N}{k}}^{\frac{n}{m}} WmNkn=WkNmn m,k为任意整数。
  • 共轭对称性: W N n = ( W N − n ) ∗ {W_N}^{n} = {({W_N}^{-n})}^* WNn=(WNn)
  • 正交性(这个主要是IFFT用,这里不作说明)。

这里主要用到可约性。我们可以把所有 W N 2 M − i j {W_\frac{N}{2^{M-i}}}^{j} W2MiNj化为 W N j ⋅ 2 M − i {W_N}^{j\cdot 2^{M-i}} WNj2Mi,这样,所有的 W N i ( 0 < = i < = N ) {W_N}^{i} (0<=i<=N) WNi(0<=i<=N)就可以直接预处理完成,蝶形计算中要用到的时候直接调用即可。

蝶形运算

8点FFT计算过程模式图
虽然FFT的原理是把每一级的序列细分为奇数下标组和偶数下标组,但由于FFT蝶形计算具有原位同址的特点,第 i i i 级蝶形输出仅与第 i − 1 i-1 i1 级的输入有关,所以我们不用关心每一级的第 i i i 个位置上具体是 x x x 序列中原本第几个元素,直接按照蝶形方向计算前进即可。我们可以观察到随着每一级的提升,蝶形会"膨胀",第 i i i 级一个蝴蝶结两端的间隔是 2 i − 1 2^{i-1} 2i1。根据以上特征,将每一级的序列分治为两个处理区间 [ l , l + n − 1 ] [l,l+n-1] [l,l+n1] [ l + n , r ] [l+n,r] [l+n,r],使用递归法实现FFT核心程序,递归法的模型主要就是遍历区间的完全二叉树,以根节点为入口,遍历到每个叶子结点后进行最小蝶形子运算,然后再回溯更新其父节点区间,回溯到根节点即可得到输出序列,具体的实现过程如下所示:
首先,蝶形运算处理过程可以抽象为以下完全二叉树:
在这里插入图片描述
然后,整个蝶形运算的过程可以表示为:
在这里插入图片描述

典型应用

IFFT

有FFT变换自然就会有其逆变换IFFT,欣喜的是,IFFT也可以靠FFT算法实现!操作步骤如下:

  1. 将频域序列X(k)取共轭
  2. 直接对新序列进行FFT运算
  3. 对输出序列取共轭再除以N,得到时域序列x(n)

数字信号处理中的频谱分析

举个例子,有了FFT,我们可以得到任意时刻音乐信号的实时频谱(音乐软件的特效也大多是FFT频谱可视化),将信号的频谱分布得到后,我们就可以进行进一步的处理,比如加一个FIR数字滤波器,实现一个均衡器的功能。

算法竞赛

ACM和OI感觉还是用的挺多的…主要是用在快速求解多项式系数上(卷积)。作为EE人,个人不是很认同算法竞赛圈里普遍的复变函数推导FFT,感觉多少有点魔怔了,把简单问题复杂化。可以给一个信号处理视角的FFT卷积运算策略。
首先,两个序列 x ( n ) x(n) x(n) h ( n ) h(n) h(n) 卷积 x ( n ) ∗ h ( n ) x(n) * h(n) x(n)h(n)这个操作看作是实现了一个FIR滤波器。
然后,我们把FFT的变换从数学中的实数域到复数域变换看成是时间域到频域的时频变换。
为什么信号处理中经常要考虑时频变换呢?因为一个时域信号的波形往往是很奇形怪状的,看不出什么花头,反而是频域信号,我们可以通过信号的频谱了解构成这个信号的各个频率分量。
下面给出一个信号与系统中的一个时频变换经典结论
x ( n ) ∗ h ( n ) < 时频变换 > X ( k ) H ( k ) x(n) * h(n)<\frac{时频变换}{}> X(k)H(k) x(n)h(n)<时频变换>X(k)H(k)
也就是说,时域里做卷积运算相当于频域里做乘积
而FFT就是实现时频变换的好工具。
因此,快速求解 x ( n ) ∗ h ( n ) x(n)*h(n) x(n)h(n) 的方法就变成了
在这里插入图片描述
洛谷模板Code:洛谷P3803 【模板】多项式乘法(FFT)
算法竞赛中FFT我自己接触的不是太多(太弱了摸不到做FFT的题捏),但了解了一下似乎是能实现大整数乘法、还有动态规划决策求解(货币系统类问题)。
不过听打ACM的朋友说现在FFT已经成每个队都会的签到题了????

代码实现

递归法实现(不是最好的方法,但是最直观的)
ST官方库有一个用纯汇编实现的FFT,72Mhz的单片机系统下只要4ms就能跑完1024点FFT,以后有机会去逆向一下嘻嘻。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define pi 3.1415926
using namespace std;

/***
Codes by LZH on 10/23/2021
***/

typedef struct Complex{
	double a,b; //a:real b:Imagine
}; 
const int N=1024; //Num of Sample Nodes
const double fs=2048; //Sample frequency
Complex x[N],u[N],W[N]; //x:the fft sequence 
const int resolution=fs/N; //Calculation frequency resolution

Complex comp_plus(Complex u,Complex v){
	Complex e;
	e.a=u.a+v.a; e.b=u.b+v.b;
	return e;
}

Complex comp_times(Complex u,Complex v){
	Complex e;
	e.a=u.a*v.a-u.b*v.b;
	e.b=u.a*v.b+u.b*v.a;
	return e;
}

Complex comp_minus(Complex u,Complex v){
	Complex e;
	e.a=u.a-v.a; e.b=u.b-v.b;
	return e;
}

void rev(){ //reverse the sequence in bit order.  
	int len=log2(N);
	int idd,ret,bit;
	for (int i=0; i<N; i++){
		idd=i; ret=0;
		for (int j=0; j<len; j++){
			ret = ret << 1;                 
         	bit = idd & 1;             
         	idd = idd >> 1;   
         	ret = bit | ret;          
		}
		u[i]=x[ret];
	}
	for (int i=0; i<N; i++)
		x[i]=u[i];
}

Complex Wn(double A,double B){  
	Complex u;
	u.a=cos((2*pi/A)*B); u.b=-sin((2*pi/A)*B);
	return u;
}

void fft(int l,int r,int len){
	Complex tmp;
	int n=len/2;
	if (len<2) return; //Level 1 

	fft(l,l+n-1,n); //even seg process 
	fft(l+n,r,n);	//odd seg process
	
	for (int i=l; i<=r; i++){
		if (i<l+n){
			u[i]=comp_plus(x[i],comp_times(W[(i-l)*(N/len)],x[i+n])); 
		}
		else{
			x[i]=comp_minus(x[i-n],comp_times(x[i],W[(i-n-l)*(N/len)]));
			x[i-n]=u[i-n];
		}
	}
	return;
}

int main(){
	for (int i=0; i<N; i++){
		double t=i/fs; //time resolution
		x[i].a=114*sin(2*pi*50*t)+514*sin(2*pi*152*t)+1919*sin(2*pi*536*t)+810*sin(2*pi*996*t); //Generate the original signal sequence and tranfer it to Complex number field
		x[i].b=0; //Im{x(n)}=0 
		W[i]=Wn(N,i); //e^-j((2*pi*i)/N)
	}
	
	rev(); //reverse the original order 
	fft(0,N-1,N);

	for (int i=0; i<N/2; i++){
		double u;
		u=sqrt(x[i].a*x[i].a+x[i].b*x[i].b);
		printf("%d %.3lf\n",i*resolution,u*2/N); //the real Ampltude of the signal is the Amp of fft sequence times 2 divide N.
	}
	
	return 0;
}

演示结果

上面的代码演示了采样点N=1024,采样频率=2048的FFT,原始信号是四个正弦信号的叠加,频率分别是:50,152,536,996;幅度是:114,514,1919,810。通过实验结果,我们可以发现FFT还是挺成功的,没有发生明显的频谱泄漏。这里要注意的是FFT中信号抽样依然要满足奈奎斯特采样定理 ( f s > = 2 f c f_s>=2f_c fs>=2fc)。
在这里插入图片描述


FAQ

1.Q:为什么我用这套程序跑1000点的FFT会跟Matlab出来不一样?
A:帖子里给出的是基2 FFT,也是最常用的一种FFT。基2的含义是FFT运算的点数必须是 2 n 2^n 2n ,如果要运算1000点,请先通过补零将序列长度补充到1024,数字信号处理的知识告诉我们,对任意序列补零后再进行FFT运算,并不影响其运算结果。Matlab中任意点数的FFT是另外一种FFT的实现方法,实现过程相当复杂,暂时网络上没看到什么详细的开源工程。
2.Q:频谱泄露那么严重该怎么解决?
A:可以通过加窗进行改善。常用的有汉宁窗、海明窗、布莱克曼窗(幅值特性保留最好)。但是加窗之后会造成频谱能量的衰减,这是因为由帕萨瓦尔定理可知同一信号在时域内和频域内的总能量必然是相等的,但加窗这个过程是一种数学上的近似衰减,这必然会损失部分信号的原始能量。
3.Q:嵌入式系统的内存资源太紧张,FFT跑个1024点都很勉强怎么办?
A:这是工程上一个很常见的问题,嗯…内存优化,目前我只知道工程上64点FFT就够用了,好像需要对FFT进行改进一下进行等效操作…这个本科数字信号处理教材上还真没讲过…内存优化的话还是看看startup.s里怎么进行优化吧,实际项目中似乎启动用的汇编文件内存开销是最大的。
4.Q:上面的例子进一步加速的方法?
A:1.用循环版的非递归实现法。2.序列重排复杂度可以再降到 O ( n ) O(n) O(n) , 具体可以参考OI Wiki上的方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++实现FFT频谱分析 的相关文章

  • 通过 CMIS (dotCMIS) 连接到 SP2010:异常未经授权

    我正在使用 dotCMIS 并且想要简单连接到我的 SP2010 服务器 我尝试用 C 来做到这一点 如下所示http chemistry apache org dotnet getting started with dotcmis htm
  • 为什么 C# Array.BinarySearch 这么快?

    我已经实施了一个很简单用于在整数数组中查找整数的 C 中的 binarySearch 实现 二分查找 static int binarySearch int arr int i int low 0 high arr Length 1 mid
  • ASP.NET MVC:这个业务逻辑应该放在哪里?

    我正在开发我的第一个真正的 MVC 应用程序 并尝试遵循一般的 OOP 最佳实践 我正在将控制器中的一些简单业务逻辑重构到我的域模型中 我最近一直在阅读一些内容 很明显我应该将逻辑放在域模型实体类中的某个位置 以避免出现 贫血域模型 反模式
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 嵌套接口:将 IDictionary> 转换为 IDictionary>?

    我认为投射一个相当简单IDictionary
  • 使用实体框架模型输入安全密钥

    这是我今天的完美想法 Entity Framework 中的强类型 ID 动机 比较 ModelTypeA ID 和 ModelTypeB ID 总是 至少几乎 错误 为什么编译时不处理它 如果您使用每个请求示例 DbContext 那么很
  • 从Web API同步调用外部api

    我需要从我的 Web API 2 控制器调用外部 api 类似于此处的要求 使用 HttpClient 从 Web API 操作调用外部 HTTP 服务 https stackoverflow com questions 13222998
  • OleDbDataAdapter 未填充所有行

    嘿 我正在使用 DataAdapter 读取 Excel 文件并用该数据填充数据表 这是我的查询和连接字符串 private string Query SELECT FROM Sheet1 private string ConnectStr
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • 带动态元素的 WPF 启动屏幕。如何?

    我是 WPF 新手 我需要一些帮助 我有一个加载缓慢的 WPF 应用程序 因此我显示启动屏幕作为权宜之计 但是 我希望能够在每次运行时更改屏幕 并在文本区域中显示不同的引言 这是一个生产力应用程序 所以我将使用非愚蠢但激励性的引言 当然 如
  • SolrNet连接说明

    为什么 SolrNet 连接的容器保持静态 这是一个非常大的错误 因为当我们在应用程序中向应用程序发送异步请求时 SolrNet 会表现异常 在 SolrNet 中如何避免这个问题 class P static void M string
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 如何在整个 ASP .NET MVC 应用程序中需要授权

    我创建的应用程序中 除了启用登录的操作之外的每个操作都应该超出未登录用户的限制 我应该添加 Authorize 每个班级标题前的注释 像这儿 namespace WebApplication2 Controllers Authorize p
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • Windows 窗体:如果文本太长,请添加新行到标签

    我正在使用 C 有时 从网络服务返回的文本 我在标签中显示 太长 并且会在表单边缘被截断 如果标签不适合表单 是否有一种简单的方法可以在标签中添加换行符 Thanks 如果您将标签设置为autosize 它会随着您输入的任何文本自动增长 为
  • 如何使用 C# / .Net 将文件列表从 AWS S3 下载到我的设备?

    我希望下载存储在 S3 中的多个图像 但目前如果我只能下载一个就足够了 我有对象路径的信息 当我运行以下代码时 出现此错误 遇到错误 消息 读取对象时 访问被拒绝 我首先做一个亚马逊S3客户端基于我的密钥和访问配置的对象连接到服务器 然后创
  • 如何从两个不同的项目中获取文件夹的相对路径

    我有两个项目和一个共享库 用于从此文件夹加载图像 C MainProject Project1 Images 项目1的文件夹 C MainProject Project1 Files Bin x86 Debug 其中有project1 ex
  • cmake 将标头包含到每个源文件中

    其实我有一个简单的问题 但找不到答案 也许你可以给我指一个副本 所以 问题是 是否可以告诉 cmake 指示编译器在每个源文件的开头自动包含一些头文件 这样就不需要放置 include foo h 了 谢谢 CMake 没有针对此特定用例的
  • IEnumreable 动态和 lambda

    我想在 a 上使用 lambda 表达式IEnumerable
  • C# - OutOfMemoryException 在 JSON 文件上保存列表

    我正在尝试保存压力图的流数据 基本上我有一个压力矩阵定义为 double pressureMatrix new double e Data GetLength 0 e Data GetLength 1 基本上 我得到了其中之一pressur

随机推荐

  • java: 程序包XXX不存在

    今天新导入的maven项目 一启动idea报各种包不存在 各种符号不存在 我是使用以下方法解决的 你可以尝试看看 在File Settings Build Execution Deployment Build Tools Maven Run
  • 寒假集训——八

    寒假集训 七十六 字符串 1 创建字符串 2 字符集 3 字符串常用方法 4 json格式字符串 七十七 数字常用方法 Math对象 七十八 Date 1 new Date 2 时间对象常用方法 七十九 定时器 倒计时定时器 八十 BOM
  • epoll小结

    1 select和poll模型为什么会慢 假如有100w用户和一个进程保持tcp连接 而每一个时刻只有几十个活跃的连接 也就是说 每一个时刻进程只需要处理这100w连接中的一小部分 那么如何高效的处理 进程是否在每次询问操作系统收集有事件发
  • docker部署实操二:tomcat部署

    首先我们要去下载Tomcat的镜像 因为镜像本身就是一个简化的操作系统 一般来说你下一个镜像不用去里面设置环境变量 所谓的开箱即用 搜索tomcat镜像 首先第一步搜索镜像 docker search tomcat 下载指定版本的tomca
  • 白盒测试(单元测试JUnit使用参数化测试@Parameters)

    目录 1 背景知识 2 例子 3 参数化流程 4 执行结果 5 练习题 1 背景知识 在测试过程中 我们可能会遇到这样的函数 它的参数有许多特殊值
  • Linux使用nvida-smi查看GPU类型

    nvida smi提供一个查看GPU信息的方法 然而这种方式不能查看GPU型号 型号被省略成了GeForce RTX 208 如果我们需要查看GPU的型号 只需要运行nvidia smi L即可 mrfive ubuntu nvidia s
  • STM32移植freemodbusRTU(hal库)从机

    MODBUS源码下载 freemodbus源码 github地址 一 移植准备 1 cubemx创建基础工程 配置串口和定时器以及时钟 2 拷贝freertos源码到工程目录 新建一个freemodbus文件夹 拷贝modbus文件夹 3
  • 编码 & 8421BCD 码的故事

    计算机编码中 我们都是先了解了二进制 其中分有符号数 无符号数 然后会接触到BCD码 那么BCD码是怎么产生的 为什么又要用四位二进制来表示呢 8421BCD 码的故事 一 BCD码 1 由来 2 8421BCD码 3 修正 二 底层验证修
  • 是面试官放水,还是公司实在是太缺人?这都没挂,华为原来这么容易进...

    华为是大企业 是不是很难进去啊 在华为做软件测试 能得到很好的发展吗 一进去就有9 5K 其实也没有想的那么难 直到现在 心情都还是无比激动 本人211非科班 之前在字节和腾讯实习过 这次其实没抱着什么特别大的希望投递 没想到华为可以再给我
  • 编译内核linux-2.6.38 出现error (2013-03-28 10:42)

    内核建议到官网下载 当然如果签名对的话也可以 解压后 保险起见 make mrproper 然后 make oldconfig 最后 make menuconfig 配置内核 然后再开始编译 在编译内核linux 2 6 38 出现以下问题
  • Android 透明状态栏

    转载 https blog csdn net fan7983377 article details 51604657 最近公司产品提出透明状态栏的要求 将一张背景填充满屏幕 自己记录一下 Android 透明状态栏 有两种 背景是图片还是纯
  • PHP 生成excel

    PHP 生成excel 好用强大的php excel类库 做Magento的订单导出Excel功能 找了这个php的excel类 PHPExcel PHPExcel是强大的 MS Office Excel 文档生成类库 基于Microsof
  • 课程笔记3

    一 以太坊 比特币被称为区块链1 0 以太坊被称为区块链2 0 以太坊的符号是ETH 以太币的符号是Ether 单位是Wei 比特币的符号是BTC 单位是Satoshi 以太坊做出的改进 在以太坊中出块时间减少到十几秒 比特币的mining
  • iOS实训笔记—调用系统相机与网络请求

    iOS开发实训第三周周报 总结 本周开始进行项目的开发 目前小组计划共同完成前端开发 我负责的部分为个人页面 其中涉及到加载个人信息时 需要从相册或相机获取图片 作为头像上传 并进行网络请求 获取资源 因此本周周报总结这部分的内容 学习知识
  • NeRF学习笔记(含公式、图解和过程)

    NeRF学习笔记 关注公众号 不定期分享NeRF相关文献 引言 NeRF Representing Scenes as Neural Radiance Fields for View Synthesis作为2020年ECCV的一篇论文 在用
  • 51单片机的智能饮水机控制系统【proteus仿真+程序+原理图】

    1 主要功能 该系统由AT89C51单片机 LCD1602模块 DS18B20温度传感器模块 DS1302时间模块 继电器驱动模块 电位器模块构成 适用于智能饮水机 智能水杯等相似项目 可实现功能 版本一 1 LCD1602实时显示时间 水
  • 在CentOS上安装桌面环境(例如GNOME)

    可以按照以下步骤在 CentOS 上安装桌面环境 例如 GNOME 确保您的 CentOS 系统已连接到互联网 并拥有 root 或具有 sudo 权限的用户 打开终端 并使用 yum 包管理器更新系统 sudo yum update 安装
  • MSP430嵌入式接口编程(惯性测量单元温湿度双音多频磁力计LCD显示等)

    Energia IDE编程MSP430 GPIO 串口通讯 定时中断 添加库 嵌入式器件接口编程 加速度计 include
  • 全 民 进 入 互 联 网

    2015年 3C行业的变化有目共睹 互联网 的概念全面深入人心 贯穿于企业经营和百姓的日常生活中 通讯行业提速降费 诸多国产精品手机现身 电商行业更加规范 移动端超越PC端成为主流渠道 家电行业诞生多个新技术 智能家电格局正在改写 让我们一
  • C++实现FFT频谱分析

    Update 9 10 2022 鸽了太久 增补了一些新的表述和简单推导 以及FFT在算法竞赛中的应用部分 帖子里的代码已经分别在2021全国大学生电子设计竞赛 洛谷OJ和课程设计中实战过 可靠性有保障 Origin 10 23 2021