C++实现二维快速傅里叶变换(FFT)

本文介绍了如何使用C++通过快速傅里叶变换(FFT)技术大幅加速二维离散傅里叶变换,从512x512图片处理的0.1秒缩短到传统方法的几分钟。讲解了FFT原理、递归和常规算法的实现,以及二维FFT的具体步骤,适用于图像处理和信号分析应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       上一篇文章里,我根据DFT公式用C++实现了二维离散傅里叶变换。但跑一张300*300的图片都要好几分钟,速度实在太慢了。我研究了下快速傅里叶变换,在网上找了一些资料,然后用C++实现了二维快速傅里叶变换,速度超级快,512*512的图片在0.1秒内就能处理完。

FFT原理

我们知道一维离散傅里叶变换公式如下

 其中

用矩阵表示即为:

这样就有N*N次乘法计算,很耗时。可以通过一些方式减小计算量。

由于是个周期函数,有如下两个性质

这样

 

从上面两个式子可以看出,

我们只要分别计算奇数项的DFT值

 

和偶数项的DFT值,

 

然后再通过一些加减,得到整体DFT的值X(k),乘法计算量大致就是(N/2)*(N/2)+N/2)*(N/2)=N*N/2。是之前的一半。

依次类推,奇数项和偶数项计算DFT值,也可以通过上面的方法,这样乘法计算量就每次除以2的降低。只要N满足是2的n次方,减到最后,计算量就减少2的n次方倍。

例如以下8个点计算FFT

 FFT方法一:

分析可知,可以通过递归实现这种算法。

C++代码如下

void fft(vector<Cpx>& a, int lim, int opt)
{
	if (lim == 1) return;
	vector<Cpx> 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;
	//}

}

FFT方法二:

当然,再次观察这个蝶形图,也可以有另外一种常规算法

 

 从蝶形图可以看出,从x到X,可以写成3次循环实现。当然关于x的排序,可以推理出为二进制逆序。

//二进制逆序排列
int ReverseBin(int a, int n)
{
	int ret = 0;
	for (int i = 0; i < n; i++)
	{
		if (a&(1 << i)) ret |= (1 << (n - 1 - i));
	}
	return ret;
}

void fft2(vector<Cpx>& a, int lim, int opt)
{
	int index;
	vector<Cpx> tempA(lim);
	for (int i = 0; i < lim; i++)
	{
		index = ReverseBin(i, log2(lim));
		tempA[i] = a[index];
	}

	vector<Cpx> WN(lim / 2);
	//生成WN表,避免重复计算
	for (int i = 0; i < lim / 2; i++)
	{
		WN[i] = Cpx(cos(2 * PI *i / lim), opt * -sin(2 * PI*i / lim));
	}

	//蝶形运算
	int Index0, Index1;
	Cpx temp;
	for (int steplenght = 2; steplenght <= lim; steplenght *= 2)
	{
		for (int step = 0; step < lim / steplenght; step++)
		{
			for (int i = 0; i < steplenght / 2; i++)
			{
				Index0 = steplenght * step + i;
				Index1 = steplenght * step + i + steplenght / 2;

				temp = tempA[Index1] * WN[lim / steplenght * i];
				tempA[Index1] = tempA[Index0] - temp;
				tempA[Index0] = tempA[Index0] + temp;
			}
		}
	}
	for (int i = 0; i < lim; i++)
	{
		if (opt == -1)
		{
			a[i] = tempA[i] / lim;
		}
		else
		{
			a[i] = tempA[i];
		}
	}
}

二维FFT原理

  二维FFT是在一维FFT基础上实现,实现过程为:

1.对二维输入数据的每一行进行FFT,变换结果仍然按行存入二维数组中。

2.在1的结果基础上,对每一列进行FFT,再存入原来数组,及得到二维FFT结果。

例子

      我这边用一张图做了变换处理,

原图

二维FFT

逆变换

 C++实现二维FFT代码

#include <iostream>
#include<opencv2/opencv.hpp>

using namespace cv;
using namespace std;
const int height = 512, width = 512;

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); }
Cpx operator / (Cpx a, int b) { return Cpx(a.r*1.0 / b, a.i*1.0 / b); }

Mat BGR2GRAY(Mat img)
{
	int w = img.cols;
	int h = img.rows;
	Mat grayImg(h, w, CV_8UC1);

	uchar *p = grayImg.ptr<uchar>(0);
	Vec3b *pImg = img.ptr<Vec3b>(0);

	for (int i = 0; i < w*h; ++i)
	{
		p[i] = 0.2126*pImg[i][2] + 0.7152*pImg[i][1] + 0.0722*pImg[i][0];
	}
	return grayImg;
}

Mat Resize(Mat img)
{
	int w = img.cols;
	int h = img.rows;
	Mat out(height, width, CV_8UC1);
	uchar *p = out.ptr<uchar>(0);
	uchar *p2 = img.ptr<uchar>(0);
	int x_before, y_before;
	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++)
		{
			x_before = (int)x*w*1.0 / width;
			y_before = (int)y*h*1.0 / height;
			p[y * width + x] = p2[y_before * w + x_before];
		}
	}
	return out;

}

//自动缩放到0-255范围内,并变换象限,将低频移至中间
int AutoScale(Mat src, Mat out)
{
	int w = src.cols;
	int h = src.rows;
	double *p = src.ptr<double>(0);
	uchar *pOut = out.ptr<uchar>(0);
	double max = p[0];
	double min = p[0];
	for (int i = 0; i < w*h; i++)
	{
		if (p[i] > max) max = p[i];
		if (p[i] < min) min = p[i];
	}

	double scale = 255.0 / (max - min);

	for (int i = 0; i < w*h; i++)
	{
		int j = i + w * h / 2 + w / 2;
		if (j > w*h) j = j - w * h;   //低频移至中间
		pOut[i] = (uchar)((p[j] - min)*scale);
	}
	return 0;
}

void fft(vector<Cpx>& a, int lim, int opt)
{
	if (lim == 1) return;
	vector<Cpx> 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 ReverseBin(int a, int n)
{
	int ret = 0;
	for (int i = 0; i < n; i++)
	{
		if (a&(1 << i)) ret |= (1 << (n - 1 - i));
	}
	return ret;
}

void fft2(vector<Cpx>& a, int lim, int opt)
{
	int index;
	vector<Cpx> tempA(lim);
	for (int i = 0; i < lim; i++)
	{
		index = ReverseBin(i, log2(lim));
		tempA[i] = a[index];
	}

	vector<Cpx> WN(lim / 2);
	//生成WN表,避免重复计算
	for (int i = 0; i < lim / 2; i++)
	{
		WN[i] = Cpx(cos(2 * PI *i / lim), opt * -sin(2 * PI*i / lim));
	}

	//蝶形运算
	int Index0, Index1;
	Cpx temp;
	for (int steplenght = 2; steplenght <= lim; steplenght *= 2)
	{
		for (int step = 0; step < lim / steplenght; step++)
		{
			for (int i = 0; i < steplenght / 2; i++)
			{
				Index0 = steplenght * step + i;
				Index1 = steplenght * step + i + steplenght / 2;

				temp = tempA[Index1] * WN[lim / steplenght * i];
				tempA[Index1] = tempA[Index0] - temp;
				tempA[Index0] = tempA[Index0] + temp;
			}
		}
	}
	for (int i = 0; i < lim; i++)
	{
		if (opt == -1)
		{
			a[i] = tempA[i] / lim;
		}
		else
		{
			a[i] = tempA[i];
		}
	}
}

void FFT2D(Cpx(*src)[width], Cpx(*dst)[width], int opt)
{
	//第一遍fft
	for (int i = 0; i < height; i++)
	{
		vector<Cpx> tempData(width);
		//获取每行数据
		for (int j = 0; j < width; j++)
		{
			tempData[j] = src[i][j];
		}
		//一维FFT
		fft2(tempData, width, opt);
		//写入每行数据
		for (int j = 0; j < width; j++)
		{
			dst[i][j] = tempData[j];
		}
	}

	//第二遍fft
	for (int i = 0; i < width; i++)
	{
		vector<Cpx> tempData(height);
		//获取每列数据
		for (int j = 0; j < height; j++)
		{
			tempData[j] = dst[j][i];
		}
		//一维FFT
		fft2(tempData, height, opt);
		//写入每列数据
		for (int j = 0; j < height; j++)
		{
			dst[j][i] = tempData[j];
		}
	}
}

void Mat2Cpx(Mat src, Cpx(*dst)[width])
{
	//这里Mat里的数据得是unchar类型
	uchar *p = src.ptr<uchar>(0);
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			dst[i][j] = Cpx(p[i*width + j],0);
		}
	}
}

void Cpx2Mat(Cpx(*src)[width], Mat dst)
{
	//这里Mat里的数据得是unchar类型
	uchar *p = dst.ptr<uchar>(0);
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			double g = sqrt(src[i][j].r*src[i][j].r);
			p[j + i * width] = (uchar)g;
		}
	}
}

void Cpx2MatDouble(Cpx(*src)[width], Mat dst)
{
	//这里Mat里的数据得是double类型
	double *p = dst.ptr<double>(0);
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			double g = sqrt(src[i][j].r*src[i][j].r+ src[i][j].i*src[i][j].i);
			g = log(g + 1);  //转换为对数尺度
			p[j + i * width] = (double)g;
		}
	}
}


int main()
{
	Mat img = imread("d:\\1.jpg");
	imshow("img", img);

	Mat gray = BGR2GRAY(img);
	imshow("gray", gray);
	imwrite("gray.jpg", gray);

	Mat imgRez = Resize(gray);
	imshow("imgRez", imgRez);
	imwrite("imgRez.jpg", imgRez);


	Cpx(*src)[width] = new Cpx[height][width];

	Mat2Cpx(imgRez, src);

	Cpx(*dst)[width] = new Cpx[height][width];



	double t1 = getTickCount();
	FFT2D(src, dst, 1);
	double t2 = getTickCount();

	double t1t2 = (t2 - t1) / getTickFrequency();
	std::cout << "DFT耗时: " << t1t2 << "秒" << std::endl;


	Cpx(*dst2)[width] = new Cpx[height][width];
	double t3 = getTickCount();
	FFT2D(dst, dst2, -1);
	double t4 = getTickCount();

	double t3t4 = (t4 - t3) / getTickFrequency();
	std::cout << "DFT耗时: " << t3t4 << "秒" << std::endl;

	Mat out2 = Mat::zeros(height, width, CV_8UC1);
	Cpx2Mat(dst2, out2);
	imshow("out2", out2);
	imwrite("out2.jpg", out2);

	Mat out = Mat::zeros(height, width, CV_64F);
	Cpx2MatDouble(dst, out);
	Mat out3 = Mat::zeros(height, width, CV_8UC1);
	AutoScale(out, out3);
	imshow("out3", out3);
	imwrite("out3.jpg", out3);


	waitKey(0);
}

OK!

 参考资料:FFT原理及C++与MATLAB混合编程详细介绍 - 羽扇纶巾o0 - 博客园

二维离散傅里叶(DFT)与快速傅里叶(FFT)的实现_asmartplum的博客-CSDN博客_二维fft

FFT与二维FFT的C#实现 - 死猫 - 博客园

<think>嗯,用户想找C++中用于快速傅里叶变换FFT)的库,需要实现和使用方法。我之前看到用户提到过TensorFlow Lite的依赖库,比如Eigen、FlatBuffers,可能这些库和FFT有关?不过Eigen主要是线性代数,虽然可能有FFT功能,但可能不够专业。 首先,我得回忆常见的C++ FFT库。FFTW是一个很著名的库,但它是GPL协议,商业使用可能需要购买许可证。那用户可能需要考虑开源或商业友好的选项。Kiss FFT是一个轻量级的库,适合嵌入式或资源有限的环境。还有Eigen的FFT模块,不过可能功能不如专门的库全面。另外,Intel的MKL和AMD的AMCL也提供FFT实现,但可能依赖硬件。 然后,用户可能需要跨平台支持,比如Android或iOS,这时候FFTW可能不太方便,因为需要编译不同架构的二进制文件。而Kiss FFT或者Project Nayuki的简单实现可能更容易集成。此外,C++标准库中是否有FFT呢?好像没有直接支持,但Boost有ublas中的FFT,不过可能已经被其他库取代了。 还要考虑用户的使用场景,比如是实时处理还是离线分析,数据规模大小,是否需要多线程或GPU加速。例如,使用CUDA的cuFFT适合大规模数据或需要GPU加速的情况,但需要NVIDIA显卡。 另外,引用中的TensorFlow Lite依赖库可能用户已经在项目中使用了,比如FlatBuffers和Abseil,但FFT库可能需要另外集成。比如用户提到的CMakeLists文件,可能需要添加FFTW或Kiss FFT的头文件和链接库。 需要检查用户提供的引用内容是否有相关库的信息。引用[3]提到了TensorFlow Lite的依赖库,如Eigen、FlatBuffers、Abseil,但可能不包括FFT库。引用[4]是关于OPUS编码的,可能无关。因此,用户可能需要独立于这些库之外的FFT实现。 接下来,应该列举几个主要的C++ FFT库,说明它们的特点、安装方法和使用示例。比如FFTW的安装可能涉及下载源码编译,而Kiss FFT可能通过包含头文件即可使用。需要给出代码示例,比如如何使用FFTW进行实数到复数的变换,或者Kiss FFT的基本用法。 同时,注意用户可能需要考虑许可证的问题,特别是如果项目是商业闭源的,FFTW的GPL许可证可能不合适,这时Kiss FFT的BSD许可证更友好。 另外,可能还需要提到如何将这些库集成到现有的CMake项目中,比如在CMakeLists中添加find_package或者包含目录,链接库文件等,类似引用[3]中的结构。 最后,生成相关问题,比如如何选择适合的FFT库,性能优化方法,或者如何处理许可证问题。确保回答结构清晰,分点说明每个库的特点和使用步骤,帮助用户逐步解决问题。</think>### C++快速傅里叶变换FFT)库的实现与使用 #### 1. **主流C++ FFT库推荐** - **FFTW** 最著名的高性能FFT库,支持多维变换和多种数据类型,但采用GPL许可证(商业用途需购买商业许可)[^1]。 **安装**: ```bash sudo apt-get install fftw3-dev # Debian/Ubuntu ``` **代码示例**: ```cpp #include <fftw3.h> int main() { int N = 1024; fftw_complex *in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N); fftw_complex *out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N); fftw_plan plan = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE); fftw_execute(plan); // 执行变换 fftw_destroy_plan(plan); fftw_free(in); fftw_free(out); } ``` - **Kiss FFT** 轻量级、开源(BSD协议),适合嵌入式系统或资源受限场景[^2]。 **集成方法**: 直接下载头文件`kiss_fft.h`和源码`kiss_fft.c`到项目中。 - **Eigen FFT** Eigen库的FFT模块(MPL2协议),适合与Eigen其他功能结合使用。 **示例**: ```cpp #include <Eigen/Dense> #include <unsupported/Eigen/FFT> Eigen::VectorXcd fft(const Eigen::VectorXd& signal) { Eigen::FFT<double> fft; Eigen::VectorXcd spectrum; fft.fwd(spectrum, signal); return spectrum; } ``` - **cuFFT** NVIDIA的GPU加速FFT库(需CUDA环境),适合大规模数据计算[^3]。 --- #### 2. **库选型对比** | 库名称 | 协议 | 性能 | 适用场景 | |----------|------------|--------|------------------------| | FFTW | GPL/商业 | 极高 | 高性能计算、科研 | | Kiss FFT | BSD | 中等 | 嵌入式、跨平台 | | Eigen | MPL2 | 较低 | 与Eigen生态集成 | | cuFFT | 专有 | GPU级 | 大规模并行计算 | --- #### 3. **集成到CMake项目(以FFTW为例)** 在`CMakeLists.txt`中添加: ```cmake find_package(FFTW REQUIRED) include_directories(${FFTW_INCLUDE_DIRS}) target_link_libraries(your_target ${FFTW_LIBRARIES}) ``` --- #### 4. **性能优化建议** - **FFTW的`FFTW_MEASURE`模式**:通过实测选择最优算法[^1]。 - **内存对齐**:使用`fftw_malloc`分配内存以提高SIMD效率。 - **多线程**:启用FFTW的多线程支持(需链接`fftw3_threads`)。 ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值