2021-01-06

FFT原理及C++与MATLAB混合编程详细介绍
羽扇纶巾o0·2021-01-06 00:43·43 次阅读

返回主页羽扇纶巾o0
博客园
首页
新随笔
联系
管理
一:FFT原理#
1.1 DFT计算#
在一个周期内的离散傅里叶级数(DFS)变换定义为离散傅里叶变换(DFT)。

{X(k)=∑N−1n=0x(n)WknN,x(n)=1N∑N−1k=0X(k)W−knN,0≤k≤N−10≤n≤N−1
其中,WN=e−j2πN。X(k)是x(n)的离散傅里叶变换。

用矩阵方程可以更加清楚的看出DFT的变换过程:

X=W⋅x(1)
X=⎛⎝⎜⎜⎜⎜⎜⎜⎜X(0)X(1)x(2)⋮X(N−1)⎞⎠⎟⎟⎟⎟⎟⎟⎟;W=⎛⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜111⋮11W1NW2N⋮WN−1N1W2NW4N⋮W2(N−1)N⋯⋯⋯⋱⋯1WN−1NW2(N−1)N⋮W(N−1)(N−1)N⎞⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟;x=⎛⎝⎜⎜⎜⎜⎜⎜⎜x(0)x(1)x(2)⋮x(N−1)⎞⎠⎟⎟⎟⎟⎟⎟⎟

可以看出,长度为N的有限长序列x(n),其离散傅里叶变换X(k)仍是一个长度为N的有限长序列。由(1)可看出时间复杂度为O(N2),如果N=1024点的话,需要1048576(一百多万)次复数乘法。DFT的计算量实在是太大了,于是有了后面的优化版本:快速傅里叶变换(FFT)。

1.2 FFT计算#
1.2.1 性质铺垫#
由于系数WnkN=e−j2πNnk是一个周期函数,可以用它的性质来改进算法,提高计算效率。

性质一:Wk+N2N=−WkN (对称性)

性质二:WnkN=WnkN1 (同除一个常数)

这里主要利用以上两个性质,把长度为N点的大点数的DFT运算依次分解为若干个小点数的DFT。因为DFT的计算量正比于N2,N小计算量也小。

1.2.2 按时间抽取的基2FFT(N点)#
假设进行FFT的点数N是2的整次方(基2),首先将序列分为两组,一组为偶数项,一组为奇数项,然后进行如下的变换,推导如下:

X(k)=∑n=0N−1x(n)WnkN=∑n=0为偶数N−2x(n)WnkN+∑n=1为奇数N−2x(n)WnkN=∑r=0N2−1x(2r)W2rkN+∑r=0N2−1x(2r+1)W(2r+1)kN=∑r=0N2−1x(2r)W2rkN+WkN∑r=0N2−1x(2r+1)W2rkN(由性质二↓)=∑r=0N2−1x(2r)WrkN2+WkN∑r=0N2−1x(2r+1)WrkN2,0≤k≤N2−1(2)
可以看出求x(n)的DFT变成了求其偶数项的DFT和奇数项的DFT的组合,但注意这只计算出了前一半的DFT值,后一半由如下性质得到:

由性质一↓

X(k+N2)=∑r=0N2−1x(2r)WrkN2−WkN∑r=0N2−1x(2r+1)WrkN2,0≤k≤N2−1(3)
这样一来,我们就计算出了完整的x(n)的DFT值,其实这就是FFT的核心思想了,接下来我们用蝶形图让上面的计算步骤更直观形象一些。

1.3 蝶形信号流图#

用G(k)代替偶数项DFT,用H(k)代替奇数项DFT,则整理公式(2)、(3)为:

{X(k)=G(k)+WkNH(k),X(k+N2)=G(k)−WkNH(k),0≤k≤N2−10≤k≤N2−1(4)
其中

⎧⎩⎨⎪⎪⎪⎪G(k)=∑N2−1r=0x(2r)WrkN2H(k)=∑N2−1r=0x(2r+1)WrkN2(5)
从(4)和(5)可以看出,我们可以把一串时域数据分成偶数部分和奇数部分来计算G(K)和H(k),同样也可以再把偶数部分再分成偶数部分和奇数部分计算,直到分到最后只剩下两个数据,再递归计算出FFT结果,具体直观点的流程见下面经典的N点蝶形图:

二:FFT的C++实现#
Copy
#include // fft算法实现,基2时间抽取
#include
#include
using namespace std;

const double PI = acos(-1); // pi值

struct Cpx // 定义一个复数结构体和复数运算法则
{
double r, i;
Cpx() : r(0), i(0) {}
Cpx(double _r, double _i) : r(_r), i(_i) {}
};
Cpx operator + (Cpx a, Cpx b) { return Cpx(a.r + b.r, a.i + b.i); }
Cpx operator - (Cpx a, Cpx b) { return Cpx(a.r - b.r, a.i - b.i); }
Cpx operator * (Cpx a, Cpx b) { return Cpx(a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r); }

void fft(vector& a, int lim, int opt)
{
if (lim == 1) return;
vector a0(lim >> 1), a1(lim >> 1); // 初始化一半大小,存放偶数和奇数部分
for (int i = 0; i < lim; i += 2)
a0[i >> 1] = a[i], a1[i >> 1] = a[i + 1]; // 分成偶数部分和奇数部分

fft(a0, lim >> 1, opt); // 递归计算偶数部分
fft(a1, lim >> 1, opt); // 递归计算偶数部分

Cpx wn(cos(2 * PI / lim), opt * -sin(2 * PI / lim)); //等于WN
Cpx w(1, 0);
for (int k = 0; k < (lim >> 1); k++) // 见蝶形图1运算过程
{
	a[k] = a0[k] + w * a1[k];
	a[k + (lim >> 1)] = a0[k] - w * a1[k];
	w = w * wn;
}

//for (int k = 0; k < (lim >> 1); k++) // 见蝶形图2,小优化一下,少一次乘法
//{
//	Cpx t = w * a1[k];
//	a[k] = a0[k] + t;
//	a[k + (lim >> 1)] = a0[k] - t;
//	w = w * wn;
//}

}

int main()
{
int opt = 1; // 1为FFT,-1为IFFT
vector a(16); // 这里固定为16点,可以改变
for (int i = 0; i < 16; i++) // 随机生成16个数作为待处理的数据
{
Cpx c = Cpx(cos(0.2 * PI * i), 0);
a[i] = c;
}

if (1 == opt)
	fft(a, 16, opt); // a数组成为FFT过后的值
else if (-1 == opt)
{
	fft(a, 16, opt); // a数组成为IFFT过后的值
	for (int i = 0; i < 512; i++) a[i].r /= 512, a[i].i /= -512;// IFFT要除以长度
}
else;

return 0;

}
三:MATLAB与C++混合编程#
在工程上有的时候为了使数据处理更快或者支持某些定点运算,而选择将某些处理步骤用C/C++来处理,其实一般工程用MATLAB处理速度已经足够了,混合编程也全当是复习一下C++吧。

MATLAB与C++混合编程分为MATLAB中调用C++和C++中调用MATLAB,这里我们讨论的是前者。MATLAB与C++混合编程不是简单的把两种语言写在一起就行,而是需要遵循一种接口规范,具体在3.2中讨论。

3.1 混合编程步骤#
从MATLAB的编译器配置到最后程序跳转到VS中打断点调试,在整个混合编程的过程中遇到了不少的困难,网上能找的资料多但是也杂乱,这里总结一下我从开始到最后所做的步骤。

① 我是用的是MATLAB2019b和VS2019,之前用的MATLAB2016,然后下载什么2019支持文件,修改注册表等等搞了很久也没弄好,索性直接换MATLAB2019b。

② MATLAB中运行mex -setup C++与mbuild -setup C++,如果不成功那就是当前版本的MATLAB不支持当前版本的Visual Studio,建议把MATLAB版本升高。不建议把VS的版本降低,会有兼容问题。

③ 不需要创建工程,直接创建一个xx.cpp文件按照mex接口定义写一个C++程序(具体程序之后讨论)。之前创建工程捣鼓了很久VS里面的配置问题,比如链接extern库等等,但感觉最后也并不需要创建工程,所以并不需要配置这些外部链接库?直接写xx.cpp文件就好了?(我也不太确定,也可能有用)

④ 程序写好之后在MATLAB中运行mex -g xx.cpp,如果xx.cpp程序写的符合规范的话,就会mex成功,生成xx.mexw64和xx.pdb文件;如果mex失败的话根据MATLAB返回的警告去修改代码。注意为了之后能进入到VS2019里断点调试,要加-g。

⑤ 在MATLAB脚本中写相应的测试程序,设置断点运行停在xx()函数处。

⑥ 用VS2019打开xx.cpp文件,在‘调试’一栏找到‘添加到进程’,进去 选择‘本机’,然后把MATLAB添加到进程。在你想停的地方设置断点。

⑦ MATLAB继续运行,则进入到VS2019中的相应断点处。(最后两步有可能进不去,其实我也是有时候能进去有时候不能,暂时也没有什么好的解决办法)

3.2 接口使用#
mex文件是MATLAB中.m文件与VS中.cpp文件的桥梁,mex接口好坏关系到我们的MATLAB数据能不能正确地在C++程序中运行。

其中最重要的头文件和接口主函数如下,写法是固定的。

Copy
#include “mex.h”

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
int nrhs:输入参数的个数

mxArray *prhs[]:输入参数的指针数组

int nlhs:输出参数的个数

const mxArray *plhs[]:输出参数的指针数组

注意输入和输出都是以指针的形式传输的,可以理解成MATLAB把它的参数放到了某个地址处,然后C++中根据这个参数的长度去相应地址处读取相应长度的数据,就完成了参数的传递过程。相反最后再传递回去。
下面总结几个常用的mex函数:

读取参数时会用到的函数:

Copy
// 复数单值读取
double Nr1 = mxGetPr(prhs[0]); // 读取第一个参数的实部
double Ni2 = mxGetPr(prhs[1]); // 读取第二个参数的虚部
Copy
// 地址读取
double
Pr1 = mxGetPr(prhs[0]); // 读取第一个参数的实部地址
double
Pi2 = mxGetPi(prhs[0]); // 读取第一个参数的虚部地址
Copy
// 矩阵维度读取
int M = mxGetM(prhs[2]); // 读取第三个参数的行数
int N = mxGetN(prhs[2]); // 读取第三个参数的列数
待补充

输出参数时会用到的函数:

Copy
// 输出复矩阵
plhs[0] = mxCreateDoubleMatrix(M, N, mxCOMPLEX); // 创建MN的复矩阵
double
outPr = mxGetPr(plhs[0]);
double* outPi = mxGetPi(plhs[0]);
待补充

3.3 FFT的MATLAB/C++混合实现#
先将第二章中FFT的代码用mex接口改写成如下形式:

Copy

include “mex.h”

include

include

const double PI = acos(-1); // pi

struct Cpx // 定义一个复数结构体和复数运算法则
{
double r, i;
Cpx() : r(0), i(0) {}
Cpx(double _r, double _i) : r(_r), i(_i) {}
};
Cpx operator + (Cpx a, Cpx b) { return Cpx(a.r + b.r, a.i + b.i); }
Cpx operator - (Cpx a, Cpx b) { return Cpx(a.r - b.r, a.i - b.i); }
Cpx operator * (Cpx a, Cpx b) { return Cpx(a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r); }

void fft(std::vector& a, int lim, int opt)
{
if (lim == 1) return;
std::vector a0(lim >> 1), a1(lim >> 1);
for (int i = 0; i < lim; i += 2)
a0[i >> 1] = a[i], a1[i >> 1] = a[i + 1]; // 分成偶数部分和奇数部分

fft(a0, lim >> 1, opt);
fft(a1, lim >> 1, opt);

Cpx wn(cos(2 * PI / lim), opt * -sin(2 * PI / lim));
Cpx w(1, 0);
for (int k = 0; k < (lim >> 1); k++) // 蝶形运算过程
{
	a[k] = a0[k] + w * a1[k];
	a[k + (lim >> 1)] = a0[k] - w * a1[k];
	w = w * wn;
}

}

void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) // mex主函数
{
int M = mxGetM(prhs[0]); // 输入矩阵行数
int N = mxGetN(prhs[0]); // 输入矩阵列数
double* xpr = mxGetPr(prhs[0]); // 输入矩阵实部指针
double* xpi = mxGetPi(prhs[0]); // 输入矩阵虚部指针
int lim = *mxGetPr(prhs[1]); // 输入参数,长度,这里输入的为行向量,所以lim = N,M = 1
int opt = *mxGetPr(prhs[2]); // 输入参数,选择, 1为FFT,-1为IFFT

plhs[0] = mxCreateDoubleMatrix(M, N, mxCOMPLEX); // 输出矩阵创建(重要)
double* ypr = mxGetPr(plhs[0]); // 输出矩阵实部指针
double* ypi = mxGetPi(plhs[0]); // 输出矩阵虚部指针


std::vector<Cpx> a(lim); // 用vector存储数据
for (int i = 0; i < lim; i++) // 输入向量传入
{
	a[i].r = xpr[i];
	a[i].i = xpi[i];
}

if (1 == opt)
	fft(a, lim, opt); // a数组变为FFT过后的值
else if (-1 == opt)
{
	fft(a, lim, opt); // a数组变为IFFT过后的值
	for (int i = 0; i < lim; i++) a[i].r /= lim, a[i].i /= lim;// IFFT要除以长度
}
else;
 
for (int i = 0; i < lim; i++) // 输出向量传出
{
	ypr[i] = a[i].r;
	ypi[i] = a[i].i;
}

return;

}
再在MATLAB脚本中写如下程序:

Copy
clear all
mex fftxx.cpp -g
a = randn(1, 16) + 1i * randn(1, 16); % 随机生成16个复数数据
fftsize = 16;
b = fftxx(a, fftsize, 1) % 传入C++中进行FFT处理
b1 = fft(a,fftsize) % MATLAB系统函数进行FFT处理

c = fftxx(b, fftsize, -1) % 传入C++中进行IFFT处理
c1 = ifft(b, fftsize) % MATLAB系统函数进行IFFT处理

最后运行该.m程序,在MATLAB命令行窗口中可以看到b和b1,c和c1输出结果完全一致。

作者: 羽扇纶巾o0

出处:https://www.cnblogs.com/gjblog/p/14227295.html

本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。

分类: 通信算法
标签: 通信算法, 傅里叶变换, C++, MATLAB
« 上一篇: DPSK通信系统的FPGA实现
posted @ 2021-01-06 00:43 羽扇纶巾o0 阅读(43) 评论(0) 编辑 收藏
登录后才能发表评论,立即 登录 或 注册, 访问 网站首页
Copyright © 2021 羽扇纶巾o0
Powered by .NET 5.0 on Kubernetes
Powered By Cnblogs | Theme Toretto v1.0.0
目录

一:FFT原理
1.1 DFT计算
1.2 FFT计算
1.2.1 性质铺垫
1.2.2 按时间抽取的基2FFT(N点)
1.3 蝶形信号流图
二:FFT的C++实现
三:MATLAB与C++混合编程
3.1 混合编程步骤
3.2 接口使用
3.3 FFT的MATLAB/C++混合实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值