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频谱分析 的相关文章

  • 检查两个数是否是彼此的排列?

    给定两个数字 a b 使得 1 例如 123 是 312 的有效排列 我也不想对数字中的数字进行排序 如果您指的是数字的字符 例如 1927 和 9721 则 至少 有几种方法 如果允许排序 一种方法是简单地sprintf将它们放入两个缓冲
  • 访问私人成员[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过将类的私有成员转换为 void 指针 然后转换为结构来访问类的私有成员是否合适 我认为我无权修改包含我需要访问的数据成员的类 如果不道德 我
  • pthread_cond_timedwait() 和 pthread_cond_broadcast() 解释

    因此 我在堆栈溢出和其他资源上进行了大量搜索 但我无法理解有关上述函数的一些内容 具体来说 1 当pthread cond timedwait 因为定时器值用完而返回时 它如何自动重新获取互斥锁 互斥锁可能被锁定在其他地方 例如 在生产者
  • 如何避免情绪低落?

    我有一个实现状态模式每个状态处理从事件队列获取的事件 根据State因此类有一个纯虚方法void handleEvent const Event 事件继承基础Event类 但每个事件都包含其可以是不同类型的数据 例如 int string
  • 将布尔参数传递给 SQL Server 存储过程

    我早些时候问过这个问题 我以为我找到了问题所在 但我没有 我在将布尔参数传递给存储过程时遇到问题 这是我的 C 代码 public bool upload false protected void showDate object sende
  • C - 找到极限之间的所有友好数字

    首先是定义 一对友好的数字由两个不同的整数组成 其中 第一个整数的除数之和等于第二个整数 并且 第二个整数的除数之和等于第一个整数 完美数是等于其自身约数之和的数 我想做的是制作一个程序 询问用户一个下限和一个上限 然后向他 她提供这两个限
  • 如何将图像和 POST 数据上传到 Azure 移动服务 ApiController 终结点?

    我正在尝试上传图片and POST表单数据 尽管理想情况下我希望它是json 到我的端点Azure 移动服务应用 我有ApiController method HttpPost Route api upload databaseId sea
  • Web API - 访问 DbContext 类中的 HttpContext

    在我的 C Web API 应用程序中 我添加了CreatedDate and CreatedBy所有表中的列 现在 每当在任何表中添加新记录时 我想填充这些列 为此目的我已经覆盖SaveChanges and SaveChangesAsy
  • 指针减法混乱

    当我们从另一个指针中减去一个指针时 差值不等于它们相距多少字节 而是等于它们相距多少个整数 如果指向整数 为什么这样 这个想法是你指向内存块 06 07 08 09 10 11 mem 18 24 17 53 7 14 data 如果你有i
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • 在数据库中搜索时忽略空文本框

    此代码能够搜索数据并将其加载到DataGridView基于搜索表单文本框中提供的值 如果我将任何文本框留空 则不会有搜索结果 因为 SQL 查询是用 AND 组合的 如何在搜索 从 SQL 查询或 C 代码 时忽略空文本框 private
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 如何在 VBA 中声明接受 XlfOper (LPXLOPER) 类型参数的函数?

    我在之前的回答里发现了问题 https stackoverflow com q 19325258 159684一种无需注册即可调用 C xll 中定义的函数的方法 我之前使用 XLW 提供的注册基础结构 并且使用 XlfOper 类型在 V
  • 插入记录后如何从SQL Server获取Identity值

    我在数据库中添加一条记录identity价值 我想在插入后获取身份值 我不想通过存储过程来做到这一点 这是我的代码 SQLString INSERT INTO myTable SQLString Cal1 Cal2 Cal3 Cal4 SQ
  • C - 直接从键盘缓冲区读取

    这是C语言中的一个问题 如何直接读取键盘缓冲区中的数据 我想直接访问数据并将其存储在变量中 变量应该是什么数据类型 我需要它用于我们研究所目前正在开发的操作系统 它被称为 ICS OS 我不太清楚具体细节 它在 x86 32 位机器上运行
  • const、span 和迭代器的问题

    我尝试编写一个按索引迭代容器的迭代器 AIt and a const It两者都允许更改容器的内容 AConst it and a const Const it两者都禁止更改容器的内容 之后 我尝试写一个span
  • x86 上未对齐的指针

    有人可以提供一个示例 将指针从一种类型转换为另一种类型由于未对齐而失败吗 在评论中这个答案 https stackoverflow com questions 544928 reading integer size bytes from a
  • 如何使用 std::string 将所有出现的一个字符替换为两个字符?

    有没有一种简单的方法来替换所有出现的 in a std string with 转义 a 中的所有斜杠std string 完成此操作的最简单方法可能是boost字符串算法库 http www boost org doc libs 1 46
  • 如何在 C++ BOOST 中像图形一样加载 TIFF 图像

    我想要加载一个 tiff 图像 带有带有浮点值的像素的 GEOTIFF 例如 boost C 中的图形 我是 C 的新手 我的目标是使用从源 A 到目标 B 的双向 Dijkstra 来获得更高的性能 Boost GIL load tiif
  • 恢复上传文件控制

    我确实阅读了以下帖子 C 暂停 恢复上传 https stackoverflow com questions 1048330 pause resume upload in c 使用 HTTP 恢复上传 https stackoverflow

随机推荐

  • 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