一、前言

最近几天接触了图像的傅里叶变换,数学原理依旧不是很懂,因此不敢在这里妄言。下午用Opencv代码实现了这一变换,有一些经验心得,愿与大家分享。

二、关键函数解析

2.1copyMakeBorder() 扩展图片尺寸

傅里叶变换的计算对图像的尺寸有一定要求,尺寸不满足要求的,可用copyMakeBorder() 函数进行扩展。函数定义如下:

  1. void copyMakeBorder(InputArray src, //输入图像
  2. OutputArray dst, //输出图像
  3. int top, //上边界添加的像素行数
  4. int bottom, //下边界添加的像素行数
  5. int left, //左边界添加的像素列数
  6. int right, //右边界添加的像素列数
  7. int borderType, //表示边界的类型
  8. const Scalar& value=Scalar()//表示如果边界的类型是BORDER_CONSTANT时边界的颜色值 )

borderType边界的类型有以下几种:

1)BORDER_REPLICATE:重复:                 aaaaaa|abcdefgh|hhhhhhh

2)BORDER_REFLECT:反射:                       fedcba|abcdefgh|hgfedcb

3)BORDER_REFLECT_101:反射101:         gfedcb|abcdefgh|gfedcba

4)BORDER_WRAP:外包装:                      cdefgh|abcdefgh|abcdefg

5)BORDER_CONSTANT:常量复制:          iiiiii|abcdefgh|iiiiiii(i的值由最后一个参数 const Scalar& value=Scalar()确定,如Scalar::all(0) )

2.2getOptimalDFTSize() 获取最佳计算尺寸

离散傅里叶变换的计算速度与图片的尺寸有关,当图片的尺寸是2,3,5的倍数时,计算速度最快。常与函数copyMakeBorder() 配合使用,保证较快的计算速度。

  1. int getOptimalDFTSize(int vecsize)

使用示例:

  1. int m = getOptimalDFTSize(mImage.rows);//返回行的最佳尺寸
  2. int n = getOptimalDFTSize(mImage.cols);//返回列的最佳尺寸
  3. copyMakeBorder(mImage, mImage, 0, m - mImage.rows, 0, n - mImage.cols, BORDER_CONSTANT, Scalar(0));

2.3dft()傅里叶变换计算

傅里叶变换计算函数。

  1. void dft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0);

参数解释: 
InputArray src: 输入图像,可以是实数或虚数 
OutputArray dst: 输出图像,其大小和类型取决于第三个参数flags 
int flags = 0: 转换的标识符,有默认值0。其可取的值如下所示: 

1)DFT_INVERSE: 用一维或二维逆变换取代默认的正向变换 ;

2)DFT_SCALE: 缩放比例标识符,根据数据元素个数平均求出其缩放结果,如有N个元素,则输出结果以1/N缩放输出,常与DFT_INVERSE搭配使用; 

3)DFT_ROWS: 对输入矩阵的每行进行正向或反向的傅里叶变换;此标识符可在处理多种适量的的时候用于减小资源的开销,这些处理常常是三维或高维变换等复杂操作; 

4)DFT_COMPLEX_OUTPUT: 对一维或二维的实数数组进行正向变换,这样的结果虽然是复数阵列,但拥有复数的共轭对称性(CCS),可以以一个和原数组尺寸大小相同的实数数组进行填充,这是最快的选择也是函数默认的方法。你可能想要得到一个全尺寸的复数数组(像简单光谱分析等等),通过设置标志位可以使函数生成一个全尺寸的复数输出数组; 

5)DFT_REAL_OUTPUT: 对一维二维复数数组进行逆向变换,这样的结果通常是一个尺寸相同的复数矩阵,但是如果输入矩阵有复数的共轭对称性(比如是一个带有DFT_COMPLEX_OUTPUT标识符的正变换结果),便会输出实数矩阵。 
int nonzeroRows = 0: 当这个参数不为0,函数会假设只有输入数组(没有设置DFT_INVERSE)的第一行或第一个输出数组(设置了DFT_INVERSE)包含非零值。这样的话函数就可以对其他的行进行更高效的处理节省一些时间,这项技术尤其是在采用DFT计算矩阵卷积时非常有效。

2.4magnitude()计算二维矢量幅值

  1. void magnitude(InputArray x, InputArray y, OutputArray magnitude);

参数解释: 
InputArray x: 浮点型数组的x坐标矢量,也就是实部 
InputArray y: 浮点型数组的y坐标矢量,必须和x尺寸相同 
OutputArray magnitude: 与x类型和尺寸相同的输出数组 

其计算公式如下: 

2.5log()自然对数计算

log()函数的功能是计算每个数组元素绝对值的自然对数 。

  1. void log(InputArray src,OutputArray dst)

参数解释:

InputArray src:为输入图像 
OutputArray dst:为得到的对数值

其原理如下: 

2.6normalize()矩阵归一化

归一化就是把要处理的数据经过某种算法的处理限制在所需要的范围内。首先归一化是为了后面数据处理的方便,其次归一化能够保证程序运行时收敛加快。

  1. void normalize(InputArray src, OutputArray dst,
  2. double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray() )

参数解释: 
InputArray src: 输入图像 ;
OutputArray dst: 输出图像,尺寸大小和src相同 ;
double alpha = 1: range normalization模式的最小值 ;
double beta = 0: range normalization模式的最大值,不用于norm normalization(范数归一化)模式 ;
int norm_type = NORM_L2: 归一化的类型,主要有 :

1)NORM_INF: 归一化数组的C-范数(绝对值的最大值) 

2)NORM_L1: 归一化数组的L1-范数(绝对值的和) 

3)NORM_L2: 归一化数组的L2-范数(欧几里得) 

4)NORM_MINMAX: 数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。 
int dtype = -1: 当该参数为负数时,输出数组的类型与输入数组的类型相同,否则输出数组与输入数组只是通道数相同,而depth = CV_MAT_DEPTH(dtype) ;
InputArray mask = noArray(): 操作掩膜版,用于指示函数是否仅仅对指定的元素进行操作。

归一化公式:

1)norm_type!=NORM_MINMAX(线性函数转换):

if mask(i,j)!=0

dst(i,j)=(src(i,j)-min(src))*(b'-a')/(max(src)-min(src))+ a'

else

dst(i,j)=src(i,j)

其中b'=MAX(a,b), a'=MIN(a,b);

2)norm_type!=NORM_MINMAX:

if mask(i,j)!=0

dst(i,j)=src(i,j)*a/norm (src,norm_type,mask)

else

dst(i,j)=src(i,j)

其中,函数norm的功能是计算norm(范数)的绝对值

三、代码及结果分享

  1. #include <opencv2/opencv.hpp>
  2. #include <iostream>
  3. using namespace std;
  4. using namespace cv;
  5. int main()
  6. {
  7. Mat mImage = imread("Lenna.jpg", 0);
  8. if (mImage.data == 0)
  9. {
  10. cerr << "Image reading error" << endl;
  11. system("pause");
  12. return -1;
  13. }
  14. namedWindow("The original image", WINDOW_NORMAL);
  15. imshow("The original image", mImage);
  16. //Extending image
  17. int m = getOptimalDFTSize(mImage.rows);
  18. int n = getOptimalDFTSize(mImage.cols);
  19. copyMakeBorder(mImage, mImage, 0, m - mImage.rows, 0, n - mImage.cols, BORDER_CONSTANT, Scalar(0));
  20. //Fourier transform
  21. Mat mFourier(mImage.rows + m, mImage.cols + n, CV_32FC2, Scalar(0, 0));
  22. Mat mForFourier[] = { Mat_<float>(mImage), Mat::zeros(mImage.size(), CV_32F) };
  23. Mat mSrc;
  24. merge(mForFourier, 2, mSrc);
  25. dft(mSrc, mFourier);
  26. //channels[0] is the real part of Fourier transform,channels[1] is the imaginary part of Fourier transform
  27. vector<Mat> channels;
  28. split(mFourier, channels);
  29. Mat mRe = channels[0];
  30. Mat mIm = channels[1];
  31. //Calculate the amplitude
  32. Mat mAmplitude;
  33. magnitude(mRe, mIm, mAmplitude);
  34. //Logarithmic scale
  35. mAmplitude += Scalar(1);
  36. log(mAmplitude, mAmplitude);
  37. //The normalized
  38. normalize(mAmplitude, mAmplitude, 0, 255, NORM_MINMAX);
  39. Mat mResult(mImage.rows, mImage.cols, CV_8UC1, Scalar(0));
  40. for (int i = 0; i < mImage.rows; i++)
  41. {
  42. uchar* pResult = mResult.ptr<uchar>(i);
  43. float* pAmplitude = mAmplitude.ptr<float>(i);
  44. for (int j = 0; j < mImage.cols; j++)
  45. {
  46. pResult[j] = (uchar)pAmplitude[j];
  47. }
  48. }
  49. Mat mQuadrant1 = mResult(Rect(mResult.cols / 2, 0, mResult.cols / 2, mResult.rows / 2));
  50. Mat mQuadrant2 = mResult(Rect(0, 0, mResult.cols / 2, mResult.rows / 2));
  51. Mat mQuadrant3 = mResult(Rect(0, mResult.rows / 2, mResult.cols / 2, mResult.rows / 2));
  52. Mat mQuadrant4 = mResult(Rect(mResult.cols / 2, mResult.rows / 2, mResult.cols / 2, mResult.rows / 2));
  53. Mat mChange1 = mQuadrant1.clone();
  54. //mQuadrant1 = mQuadrant3.clone();
  55. //mQuadrant3 = mChange1.clone();
  56. mQuadrant3.copyTo(mQuadrant1);
  57. mChange1.copyTo(mQuadrant3);
  58. Mat mChange2 = mQuadrant2.clone();
  59. //mQuadrant2 = mQuadrant4.clone();
  60. //mQuadrant4 = mChange2.clone();
  61. mQuadrant4.copyTo(mQuadrant2);
  62. mChange2.copyTo(mQuadrant4);
  63. namedWindow("The Fourier transform", WINDOW_NORMAL);
  64. imshow("The Fourier transform", mResult);
  65. waitKey();
  66. destroyAllWindows();
  67. return 0;
  68. }

四、注意事项

4.1Mat对象类型问题

为方便运算,在计算过程中,Mat对象中数据都是float型,将其归一化至0-255范围后仍保留有小数部分,造成无法用imshow()正常显示。最后结果必须用uchar强制转换。源代码中以下片段实现了这一过程:

  1. normalize(mAmplitude, mAmplitude, 0, 255, NORM_MINMAX);
  2. Mat mResult(mImage.rows, mImage.cols, CV_8UC1, Scalar(0));
  3. for (int i = 0; i < mImage.rows; i++)
  4. {
  5. uchar* pResult = mResult.ptr<uchar>(i);
  6. float* pAmplitude = mAmplitude.ptr<float>(i);
  7. for (int j = 0; j < mImage.cols; j++)
  8. {
  9. pResult[j] = (uchar)pAmplitude[j];
  10. }
  11. }

4.2clone()与copyTo()的差异问题

clone 是完全的深拷贝,在内存中申请新的空间。copyTo 也是深拷贝,但是否申请新的内存空间,取决于dst矩阵头中的大小信息是否与src一至,若一致则只深拷贝并不申请新的空间,否则先申请空间后再进行拷贝。

  1. Mat= Mat::ones(4,5,CV_32F);
  2. Mat B = A.clone()    //clone 是完全的深拷贝,在内存中申请新的空间,与A独立
  3. Mat C;
  4. A.copyTo(C) //此处的C矩阵大小与A大小不一致,则申请新的内存空间,并完成拷贝,等同于clone()
  5. Mat D = A.col(1);
  6. A.col(0).copyTo(D) //此处D矩阵大小与A.col(0)大小一致,因此不会申请空间,而是直接进行拷贝,相当于把A的第1列赋值给第二列

因此在进行不同象限数据对换时,用copyTo()能成功对换数据,而用clone()则不能起到作用,原因就在于mQuadrant.clone()时重新开辟了内存空间,数据对换时对换的并非是原矩阵mResult中的数据。所以源代码中,以下片段能够正常对换数据,而注释掉的部分不能正常工作。

  1. Mat mChange1 = mQuadrant1.clone();
  2. //mQuadrant1 = mQuadrant3.clone();
  3. //mQuadrant3 = mChange1.clone();
  4. mQuadrant3.copyTo(mQuadrant1);
  5. mChange1.copyTo(mQuadrant3);
  6. Mat mChange2 = mQuadrant2.clone();
  7. //mQuadrant2 = mQuadrant4.clone();
  8. //mQuadrant4 = mChange2.clone();
  9. mQuadrant4.copyTo(mQuadrant2);
  10. mChange2.copyTo(mQuadrant4);

注:错误之处,敬请雅正!

C++ Opencv 傅里叶变换的代码实现及关键函数详解的更多相关文章

  1. opencv中 int main(int argc,char* argv[])详解

    opencv中  int main(int argc,char* argv[])详解 argc是命令行总的参数个数     argv[]是argc个参数,其中第0个参数是程序的全名,以后的参数     ...

  2. 【opencv基础】detectmultiscale函数详解

    前言 简单的人脸检测程序可以直接基于opencv的函数库进行实现,本文介绍一下detectMultiScale函数. 函数简介 opencv2人脸检测使用的是detectMultiScale函数,可以 ...

  3. php调用C代码的方法详解和zend_parse_parameters函数详解

    php调用C代码的方法详解 在php程序中需要用到C代码,应该是下面两种情况: 1 已有C代码,在php程序中想直接用 2 由于php的性能问题,需要用C来实现部分功能   针对第一种情况,最合适的方 ...

  4. java代码(13) ---Predicate详解

    Predicate详解 遇到Predicate是自己在自定义Mybatis拦截器的时候,在拦截器中我们是通过反射机制获取对象的所有属性,在查看这些属性上是否有我们自定义的UUID注解 如果有该注解,那 ...

  5. OpenCV: Canny边缘检测算法原理及其VC实现详解(转载)

    原文地址:http://blog.csdn.net/likezhaobin/article/details/6892176 原文地址:http://blog.csdn.net/likezhaobin/ ...

  6. opencv学习笔记——cv::CommandLineParser函数详解

    命令行解析类CommandLineParser 该类的作用主要用于命令行的解析,也就是分解命令行的作用.以前版本没这个类时,如果要运行带参数的.exe,必须在命令行中输入文件路径以及各种参数,并且输入 ...

  7. opencv学习笔记——cv::mean()函数详解

    opencv中封装了一个专门用于求解cv::Mat均值的函数,即cv::mean(&cv::Mat),该函数会得到Mat中各个通道的均值,若要获取指定通道的均值,做进一步解析即可. 具体使用方 ...

  8. JS代码的window.location属性详解

    转载:http://www.5icool.org/a/201105/a564.html 如果你稍微懂一些JS代码,一般都会知道 window.location.href 这个属性.并且用该属性获取页面 ...

  9. php_D3_“简易聊天室 ”实现的关键技术 详解

                      PHP+MySQL实现Internet上一个简易聊天室的关键技术  系统目标: 聊天室使用数据库汇集每个人的发言,并可将数据库内的发言信息显示在页面,让每个用户都可 ...

随机推荐

  1. skynet记录7:第一个服务logger和第二个服务bootstrap

    (1)logger是skynet_context_new创建:skynet_context及mq,模块create和init (2)bootstrap启动过程:snlua时一个lua的so,对应的sn ...

  2. Subplot 多合一显示

    1.均匀图中图 matplotlib 是可以组合许多的小图, 放在一张大图里面显示的. 使用到的方法叫作 subplot. 使用import导入matplotlib.pyplot模块, 并简写成plt ...

  3. 服务管理之samba

    目录 samba 1.samba的简介 2. samba访问 1.搭建用户认证共享服务器 2.搭建匿名用户共享服务器 samba 1.samba的简介 Samba是在Linux和UNIX系统上实现SM ...

  4. vue版 文件下载

    标签的download: 是HTML5标准新增的属性,作用是指示浏览器下载URL而不是导航到URL,因此将提示用户将其保存为本地文件. 这种是定义的接口不是下载文件的路径,而是通过API可以获得文件的 ...

  5. java添加菜单项目

  6. AX_CreateAndPostSales

    static void CreateAndPostSales(Args _args) { List il = new List(Types::Record); SalesTable localSale ...

  7. vs2015 行数统计

    ctrol+shift+f  正則查找 b*[^:b#/]+.$

  8. 制作DNS字典

    1.收集字典 一般kali自带的DNS爆破工具都会有自己的字典,使用  dpkg -L dns爆破软件名 查询字典的路径.txt文件一般是字典. 合并到一个txt文件中. 2.删除字典中重复的字符串 ...

  9. Ramnit 蠕虫分析

    0x00前言 Ramnit 蠕虫是一种通过可移动驱动器传播的蠕虫.该蠕虫还可以作为后门,允许远程攻击者访问受感染的计算机,通常会寄生在用户的浏览器中,难以察觉,因此每天都有数以万计的用户受其困扰. 分 ...

  10. 组件内守卫beforeRouteEnter和beforeRouteLeave

    beforeRouteEnter用法和其他守卫差不多. 有个注意的地方就是beforeRouteEnter不能用this获取组件内收据. 在next()方法内存入vm这个参数,获取组件内数据. bef ...