赞
踩
经过了很长一段时间的实验,总结下matlab转C给Qt使用的方案总结,这里的实验对象为matlab的fft函数。
方案 | 基本原理 | 优点 | 缺点 |
---|---|---|---|
matlab生成dll给qt调用 | matlab将你写好的.m文件封装成.h.lib.dll,通过固定的调用步骤即api去调用它,这里生成的.h.lib相当于函数声明即入口,编译时会将其和我们的代码一起编译成exe,而.dll是函数具体实现,在运行时才会加载,而问题在于matlab生成的dll是很难避开matlab的库环境的,意思就是我们的dll文件是依赖于matlab的库 | 利于函数更新迭代,使用步骤简单,利于开发 | 当我们使用了matlab生成的dll,一般情况下客户机需要安装相应版本的matlab运行时库即删减版的matlab,但这样从用户的角度来看显然是不行的,且经过实验发现,qt环境调用matlab的dll存在较大问题,同样的环境配置的平台控制台程序能够调用,换成qt项目则出现未知错误 |
matlabCoder生成C源码 | 采用matlab自带的生成工具生成函数源码,源码包括使用样例,数据类型定义,数据检查,函数实现等 | 不依赖matlab的环境,可完全脱离matlab,速度和matlab相当甚至更快 | 源码可读性较差,奇怪变量较多,但代码严谨且巧妙值得学习,代码存在较大的冗余,我们正常情况下需要对其进行精简清晰(要先读很长一段时间) |
根据公式自行翻译 | 根据公式采用实验对比进行转C | 可读性好,与项目契合度高,利于后期迭代 | 速度绝大多数情况下是不如matlab的,即耗时长 |
方案 | 基本原理 | 优点 | 缺点 |
---|---|---|---|
根据公式老实翻译 | 复杂度为O(n2)数据量大时,尤其是频繁使用它时会慢的你怀疑人生 | 实现简单,根据公式来就行了 | 没经过算法优化的fft太慢 |
蝶形运算 | 具体原理百度,可以把复杂度降到O(nlogn) | 速度显然更快,网上也很多代码实现 | 致命性缺点:限制输入长度必须是2的次幂大小的矩阵数组。且经过我在matlab的实验,扩充长度之后(补0),最后的数据是错误的 |
dobluestein算法 | 原理百度资料较少。大概是算出需要扩展的长度 并补0,算出sintab表和costab表,能力有限,希望有大侠教教我 ,这也是matlabCoder生成的源码的实际采用的算法 | 速度极快,无敌快,虽然可读性很差,但是matlab生成的C源码确实优化的很好,也有数据类型的功劳 | 自己实现难度较大,生成的源码可读性一言难尽 |
//快速傅里叶变换fft
QVector<Complex> fft(const QVector<double> &real, const QVector<double> &imag) {
/*
real:向量实部
imag:向量虚部
这里仅实现对向量的快速傅里叶变换:Y = fft(X)
计算公式可见matlab关于fft的介绍
Y(k) = n∑(j=1) X(j)*Wn^(j-1)(k-1)
X(j) = n∑(k=1)Y(k)*Wn^(j-1)(k-1)
其中 Wn = e^((-2πi)/n) = cos(-2π/n)+sin(-2π/n)i
*/
QVector<Complex> out(real.size(), {0.0, 0.0});
double fixed_factor = (-2 * PI) / real.size();
for (int u = 0; u < real.size(); u++) {
double uxf = u * fixed_factor;
for (int x = 0; x < real.size(); x++) {
//X(j)*Wn^(j-1)(k-1)
double power = uxf * x;
double temp = imag[x] * sin(power);
out[u].real += real[x] * cos(power) - temp;
out[u].imag += real[x] * sin(power) + temp;
}
}
return out;
}
#include <list>
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#define PI acos(-1)
using namespace std;
//定义复数结构
struct Complex
{
double imag;
double real;
};
//复数乘法函数
Complex ComplexMulti(Complex One, Complex Two)
{
//Temp用来存储复数相乘的结果
Complex Temp;
Temp.imag = One.imag * Two.real + One.real * Two.imag;
Temp.real = One.real * Two.real - One.imag * Two.imag;
return Temp;
}
//此处的length为原序列的长度 将输入的任意数组填充,以满足快速傅里叶变换
void Data_Expand(double *input, vector<double> &output, const int length)
{
int i = 1, flag = 1;
while (i < length)
{
i = i * 2;
}
//获取符合快速傅里叶变换的长度
int length_Best = i;
if (length_Best != length)
{
flag = 0;
}
if (flag)
{
//把原序列填充到Vector中
for (int j = 0; j < length; ++j)
{
output.push_back(input[j]);
}
}
else
{
//把原序列填充到Vector中
for (int j = 0; j < length; ++j)
{
output.push_back(input[j]);
}
//把需要拓展的部分进行填充
for (int j = 0; j < length_Best - length; j++)
{
output.push_back(0);
}
}
}
//此处的length为填充后序列的长度//将输入的实数数组转换为复数数组
void Real_Complex(vector<double> &input, Complex *output, const int length)
{
for (int i = 0; i < length; i++)
{
output[i].real = input[i];
output[i].imag = 0;
}
}
//FFT变换函数//快速傅里叶变换函数
//input: input array
//StoreResult: Complex array of output
//length: the length of input array
void FFT(Complex *input, Complex *StoreResult, const int length)
{
//获取序列长度在二进制下的位数
int BitNum = log2(length);
//存放每个索引的二进制数,重复使用,每次用完需清零
list<int> Bit;
//遍历序列的每个索引
for (int i = 0; i < length; ++i)
{
//flag用来将索引转化为二进制
//index用来存放倒叙索引
//flag2用来将二值化的索引倒序
int flag = 1, index = 0, flag2 = 0;
//遍历某个索引二进制下的每一位
for (int j = 0; j < BitNum; ++j)
{
//十进制转化为长度一致的二进制数,&位运算符作用于位,并逐位执行操作
//从最低位(右侧)开始比较
//example:
//a = 011, b1 = 001, b2 = 010 ,b3 = 100
//a & b1 = 001, a & b2 = 010, a & b3 = 000
int x = (i & flag) > 0 ? 1 : 0;
//把x从Bit的前端压进
Bit.push_front(x);
//等价于flag = flag << 1,把flag的值左移一位的值赋给flag
flag <<= 1;
}
//将原数组的索引倒序,通过it访问Bit的每一位
for (auto it : Bit)
{
//example:其相当于把二进制数从左到右设为2^0,2^1,2^2
//Bit = 011
//1: index = 0 + 0 * pow(2, 0) = 0
//2: index = 0 + 1 * pow(2, 1) = 2
//3: index = 2 + 1 * pow(2, 2) = 6 = 110
index += it * pow(2, flag2++);
}
//把Data[index]从DataTemp的后端压进,其实现了原序列的数据的位置调换
//变换前:f(0), f(1), f(2), f(3), f(4), f(5), f(6), f(7)
//变换后:f(0), f(4), f(2), f(6), f(1), f(5), f(3), f(7)
StoreResult[i].real = input[index].real;
StoreResult[i].imag = input[index].imag;
//清空Bit
Bit.clear();
}
//需要运算的级数
int Level = log2(length);
Complex Temp, up, down;
//进行蝶形运算
for (int i = 1; i <= Level; i++)
{
//定义旋转因子
Complex Factor;
//没有交叉的蝶形结的距离(不要去想索引)
//其距离表示的是两个彼此不交叉的蝶型结在数组内的距离
int BowknotDis = 2 << (i - 1);
//同一蝶形计算中两个数字的距离(旋转因子的个数)
//此处距离也表示的是两个数据在数组内的距离(不要去想索引)
int CalDis = BowknotDis / 2;
for (int j = 0; j < CalDis; j++)
{
//每一级蝶形运算中有CalDis个不同旋转因子
//计算每一级(i)所需要的旋转因子
Factor.real = cos(2 * PI / pow(2, i) * j);
Factor.imag = -sin(2 * PI / pow(2, i) * j);
//遍历每一级的每个结
for (int k = j; k < length - 1; k += BowknotDis)
{
//StoreResult[k]表示蝶形运算左上的元素
//StoreResult[k + CalDis]表示蝶形运算左下的元素
//Temp表示蝶形运算右侧输出结构的后半部分
Temp = ComplexMulti(Factor, StoreResult[k + CalDis]);
//up表示蝶形运算右上的元素
up.imag = StoreResult[k].imag + Temp.imag;
up.real = StoreResult[k].real + Temp.real;
//down表示蝶形运算右下的元素
down.imag = StoreResult[k].imag - Temp.imag;
down.real = StoreResult[k].real - Temp.real;
//将蝶形运算输出的结果装载入StoreResult
StoreResult[k] = up;
StoreResult[k + CalDis] = down;
}
}
}
}
int main()
{
//获取填充后的Data;
vector<double> Data;
//进行FFT的数据,此处默认长度为2的幂次方
//double Datapre[] = {1, 2, 6, 4, 48, 6, 7, 8, 58, 10, 11, 96, 13, 2, 75, 16};
double Datapre[] = { 100, 2, 56, 4, 48, 6, 45, 8, 58, 10 };
//数据扩充
Data_Expand(Datapre, Data, 10);
//将输入数组转化为复数数组
Complex array1D[16];
//存储傅里叶变换的结果
Complex Result[16];
//变为复数形式
Real_Complex(Data, array1D, 16);
//快速傅里叶变换
FFT(array1D, Result, 16);
//基于范围的循环,利用auto自动判断数组内元素的数据类型
for (auto data : Result)
{
//输出傅里叶变换后的幅值
cout << data.real << "\t" << data.imag << endl;
}
return 0;
}
该文章仅个人学习使用,欢迎大家一起交流学习
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。