当前位置:   article > 正文

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

c++ fft

       上一篇文章里,我根据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++代码如下

  1. void fft(vector<Cpx>& a, int lim, int opt)
  2. {
  3. if (lim == 1) return;
  4. vector<Cpx> a0(lim >> 1), a1(lim >> 1); // 初始化一半大小,存放偶数和奇数部分
  5. for (int i = 0; i < lim; i += 2)
  6. a0[i >> 1] = a[i], a1[i >> 1] = a[i + 1]; // 分成偶数部分和奇数部分
  7. fft(a0, lim >> 1, opt); // 递归计算偶数部分
  8. fft(a1, lim >> 1, opt); // 递归计算偶数部分
  9. Cpx wn(cos(2 * PI / lim), opt * -sin(2 * PI / lim)); //等于WN
  10. Cpx w(1, 0);
  11. for (int k = 0; k < (lim >> 1); k++) // 见蝶形图1运算过程
  12. {
  13. a[k] = a0[k] + w * a1[k];
  14. a[k + (lim >> 1)] = a0[k] - w * a1[k];
  15. w = w * wn;
  16. }
  17. //for (int k = 0; k < (lim >> 1); k++) // 见蝶形图2,小优化一下,少一次乘法
  18. //{
  19. // Cpx t = w * a1[k];
  20. // a[k] = a0[k] + t;
  21. // a[k + (lim >> 1)] = a0[k] - t;
  22. // w = w * wn;
  23. //}
  24. }

FFT方法二:

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

 

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

  1. //二进制逆序排列
  2. int ReverseBin(int a, int n)
  3. {
  4. int ret = 0;
  5. for (int i = 0; i < n; i++)
  6. {
  7. if (a&(1 << i)) ret |= (1 << (n - 1 - i));
  8. }
  9. return ret;
  10. }
  11. void fft2(vector<Cpx>& a, int lim, int opt)
  12. {
  13. int index;
  14. vector<Cpx> tempA(lim);
  15. for (int i = 0; i < lim; i++)
  16. {
  17. index = ReverseBin(i, log2(lim));
  18. tempA[i] = a[index];
  19. }
  20. vector<Cpx> WN(lim / 2);
  21. //生成WN表,避免重复计算
  22. for (int i = 0; i < lim / 2; i++)
  23. {
  24. WN[i] = Cpx(cos(2 * PI *i / lim), opt * -sin(2 * PI*i / lim));
  25. }
  26. //蝶形运算
  27. int Index0, Index1;
  28. Cpx temp;
  29. for (int steplenght = 2; steplenght <= lim; steplenght *= 2)
  30. {
  31. for (int step = 0; step < lim / steplenght; step++)
  32. {
  33. for (int i = 0; i < steplenght / 2; i++)
  34. {
  35. Index0 = steplenght * step + i;
  36. Index1 = steplenght * step + i + steplenght / 2;
  37. temp = tempA[Index1] * WN[lim / steplenght * i];
  38. tempA[Index1] = tempA[Index0] - temp;
  39. tempA[Index0] = tempA[Index0] + temp;
  40. }
  41. }
  42. }
  43. for (int i = 0; i < lim; i++)
  44. {
  45. if (opt == -1)
  46. {
  47. a[i] = tempA[i] / lim;
  48. }
  49. else
  50. {
  51. a[i] = tempA[i];
  52. }
  53. }
  54. }

二维FFT原理

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

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

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

例子

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

原图

二维FFT

逆变换

 C++实现二维FFT代码

  1. #include <iostream>
  2. #include<opencv2/opencv.hpp>
  3. using namespace cv;
  4. using namespace std;
  5. const int height = 512, width = 512;
  6. const double PI = acos(-1); // pi值
  7. struct Cpx // 定义一个复数结构体和复数运算法则
  8. {
  9. double r, i;
  10. Cpx() : r(0), i(0) {}
  11. Cpx(double _r, double _i) : r(_r), i(_i) {}
  12. };
  13. Cpx operator + (Cpx a, Cpx b) { return Cpx(a.r + b.r, a.i + b.i); }
  14. Cpx operator - (Cpx a, Cpx b) { return Cpx(a.r - b.r, a.i - b.i); }
  15. Cpx operator * (Cpx a, Cpx b) { return Cpx(a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r); }
  16. Cpx operator / (Cpx a, int b) { return Cpx(a.r*1.0 / b, a.i*1.0 / b); }
  17. Mat BGR2GRAY(Mat img)
  18. {
  19. int w = img.cols;
  20. int h = img.rows;
  21. Mat grayImg(h, w, CV_8UC1);
  22. uchar *p = grayImg.ptr<uchar>(0);
  23. Vec3b *pImg = img.ptr<Vec3b>(0);
  24. for (int i = 0; i < w*h; ++i)
  25. {
  26. p[i] = 0.2126*pImg[i][2] + 0.7152*pImg[i][1] + 0.0722*pImg[i][0];
  27. }
  28. return grayImg;
  29. }
  30. Mat Resize(Mat img)
  31. {
  32. int w = img.cols;
  33. int h = img.rows;
  34. Mat out(height, width, CV_8UC1);
  35. uchar *p = out.ptr<uchar>(0);
  36. uchar *p2 = img.ptr<uchar>(0);
  37. int x_before, y_before;
  38. for (int y = 0; y < height; y++) {
  39. for (int x = 0; x < width; x++)
  40. {
  41. x_before = (int)x*w*1.0 / width;
  42. y_before = (int)y*h*1.0 / height;
  43. p[y * width + x] = p2[y_before * w + x_before];
  44. }
  45. }
  46. return out;
  47. }
  48. //自动缩放到0-255范围内,并变换象限,将低频移至中间
  49. int AutoScale(Mat src, Mat out)
  50. {
  51. int w = src.cols;
  52. int h = src.rows;
  53. double *p = src.ptr<double>(0);
  54. uchar *pOut = out.ptr<uchar>(0);
  55. double max = p[0];
  56. double min = p[0];
  57. for (int i = 0; i < w*h; i++)
  58. {
  59. if (p[i] > max) max = p[i];
  60. if (p[i] < min) min = p[i];
  61. }
  62. double scale = 255.0 / (max - min);
  63. for (int i = 0; i < w*h; i++)
  64. {
  65. int j = i + w * h / 2 + w / 2;
  66. if (j > w*h) j = j - w * h; //低频移至中间
  67. pOut[i] = (uchar)((p[j] - min)*scale);
  68. }
  69. return 0;
  70. }
  71. void fft(vector<Cpx>& a, int lim, int opt)
  72. {
  73. if (lim == 1) return;
  74. vector<Cpx> a0(lim >> 1), a1(lim >> 1); // 初始化一半大小,存放偶数和奇数部分
  75. for (int i = 0; i < lim; i += 2)
  76. a0[i >> 1] = a[i], a1[i >> 1] = a[i + 1]; // 分成偶数部分和奇数部分
  77. fft(a0, lim >> 1, opt); // 递归计算偶数部分
  78. fft(a1, lim >> 1, opt); // 递归计算偶数部分
  79. Cpx wn(cos(2 * PI / lim), opt * -sin(2 * PI / lim)); //等于WN
  80. Cpx w(1, 0);
  81. for (int k = 0; k < (lim >> 1); k++) // 见蝶形图1运算过程
  82. {
  83. a[k] = a0[k] + w * a1[k];
  84. a[k + (lim >> 1)] = a0[k] - w * a1[k];
  85. w = w * wn;
  86. }
  87. //for (int k = 0; k < (lim >> 1); k++) // 见蝶形图2,小优化一下,少一次乘法
  88. //{
  89. // Cpx t = w * a1[k];
  90. // a[k] = a0[k] + t;
  91. // a[k + (lim >> 1)] = a0[k] - t;
  92. // w = w * wn;
  93. //}
  94. }
  95. //二进制逆序排列
  96. int ReverseBin(int a, int n)
  97. {
  98. int ret = 0;
  99. for (int i = 0; i < n; i++)
  100. {
  101. if (a&(1 << i)) ret |= (1 << (n - 1 - i));
  102. }
  103. return ret;
  104. }
  105. void fft2(vector<Cpx>& a, int lim, int opt)
  106. {
  107. int index;
  108. vector<Cpx> tempA(lim);
  109. for (int i = 0; i < lim; i++)
  110. {
  111. index = ReverseBin(i, log2(lim));
  112. tempA[i] = a[index];
  113. }
  114. vector<Cpx> WN(lim / 2);
  115. //生成WN表,避免重复计算
  116. for (int i = 0; i < lim / 2; i++)
  117. {
  118. WN[i] = Cpx(cos(2 * PI *i / lim), opt * -sin(2 * PI*i / lim));
  119. }
  120. //蝶形运算
  121. int Index0, Index1;
  122. Cpx temp;
  123. for (int steplenght = 2; steplenght <= lim; steplenght *= 2)
  124. {
  125. for (int step = 0; step < lim / steplenght; step++)
  126. {
  127. for (int i = 0; i < steplenght / 2; i++)
  128. {
  129. Index0 = steplenght * step + i;
  130. Index1 = steplenght * step + i + steplenght / 2;
  131. temp = tempA[Index1] * WN[lim / steplenght * i];
  132. tempA[Index1] = tempA[Index0] - temp;
  133. tempA[Index0] = tempA[Index0] + temp;
  134. }
  135. }
  136. }
  137. for (int i = 0; i < lim; i++)
  138. {
  139. if (opt == -1)
  140. {
  141. a[i] = tempA[i] / lim;
  142. }
  143. else
  144. {
  145. a[i] = tempA[i];
  146. }
  147. }
  148. }
  149. void FFT2D(Cpx(*src)[width], Cpx(*dst)[width], int opt)
  150. {
  151. //第一遍fft
  152. for (int i = 0; i < height; i++)
  153. {
  154. vector<Cpx> tempData(width);
  155. //获取每行数据
  156. for (int j = 0; j < width; j++)
  157. {
  158. tempData[j] = src[i][j];
  159. }
  160. //一维FFT
  161. fft2(tempData, width, opt);
  162. //写入每行数据
  163. for (int j = 0; j < width; j++)
  164. {
  165. dst[i][j] = tempData[j];
  166. }
  167. }
  168. //第二遍fft
  169. for (int i = 0; i < width; i++)
  170. {
  171. vector<Cpx> tempData(height);
  172. //获取每列数据
  173. for (int j = 0; j < height; j++)
  174. {
  175. tempData[j] = dst[j][i];
  176. }
  177. //一维FFT
  178. fft2(tempData, height, opt);
  179. //写入每列数据
  180. for (int j = 0; j < height; j++)
  181. {
  182. dst[j][i] = tempData[j];
  183. }
  184. }
  185. }
  186. void Mat2Cpx(Mat src, Cpx(*dst)[width])
  187. {
  188. //这里Mat里的数据得是unchar类型
  189. uchar *p = src.ptr<uchar>(0);
  190. for (int i = 0; i < height; i++)
  191. {
  192. for (int j = 0; j < width; j++)
  193. {
  194. dst[i][j] = Cpx(p[i*width + j],0);
  195. }
  196. }
  197. }
  198. void Cpx2Mat(Cpx(*src)[width], Mat dst)
  199. {
  200. //这里Mat里的数据得是unchar类型
  201. uchar *p = dst.ptr<uchar>(0);
  202. for (int i = 0; i < height; i++)
  203. {
  204. for (int j = 0; j < width; j++)
  205. {
  206. double g = sqrt(src[i][j].r*src[i][j].r);
  207. p[j + i * width] = (uchar)g;
  208. }
  209. }
  210. }
  211. void Cpx2MatDouble(Cpx(*src)[width], Mat dst)
  212. {
  213. //这里Mat里的数据得是double类型
  214. double *p = dst.ptr<double>(0);
  215. for (int i = 0; i < height; i++)
  216. {
  217. for (int j = 0; j < width; j++)
  218. {
  219. double g = sqrt(src[i][j].r*src[i][j].r+ src[i][j].i*src[i][j].i);
  220. g = log(g + 1); //转换为对数尺度
  221. p[j + i * width] = (double)g;
  222. }
  223. }
  224. }
  225. int main()
  226. {
  227. Mat img = imread("d:\\1.jpg");
  228. imshow("img", img);
  229. Mat gray = BGR2GRAY(img);
  230. imshow("gray", gray);
  231. imwrite("gray.jpg", gray);
  232. Mat imgRez = Resize(gray);
  233. imshow("imgRez", imgRez);
  234. imwrite("imgRez.jpg", imgRez);
  235. Cpx(*src)[width] = new Cpx[height][width];
  236. Mat2Cpx(imgRez, src);
  237. Cpx(*dst)[width] = new Cpx[height][width];
  238. double t1 = getTickCount();
  239. FFT2D(src, dst, 1);
  240. double t2 = getTickCount();
  241. double t1t2 = (t2 - t1) / getTickFrequency();
  242. std::cout << "DFT耗时: " << t1t2 << "秒" << std::endl;
  243. Cpx(*dst2)[width] = new Cpx[height][width];
  244. double t3 = getTickCount();
  245. FFT2D(dst, dst2, -1);
  246. double t4 = getTickCount();
  247. double t3t4 = (t4 - t3) / getTickFrequency();
  248. std::cout << "DFT耗时: " << t3t4 << "秒" << std::endl;
  249. Mat out2 = Mat::zeros(height, width, CV_8UC1);
  250. Cpx2Mat(dst2, out2);
  251. imshow("out2", out2);
  252. imwrite("out2.jpg", out2);
  253. Mat out = Mat::zeros(height, width, CV_64F);
  254. Cpx2MatDouble(dst, out);
  255. Mat out3 = Mat::zeros(height, width, CV_8UC1);
  256. AutoScale(out, out3);
  257. imshow("out3", out3);
  258. imwrite("out3.jpg", out3);
  259. waitKey(0);
  260. }

OK!

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/104973
推荐阅读
相关标签
  

闽ICP备14008679号