OpenCV之傅里叶变换(C++实现)

目录

 

一、傅里叶变换

二、dft()函数详解

三、getOptimalDFTSize()函数

四、magnitude()函数

五、copyMakeBorder()函数

六、normalize()


一、傅里叶变换

离散傅里叶变换(Discrete Fourier Transform, DFT),是指傅里叶变换在时域和频域上都呈现离散的形式,将时域信号的采样变换为在离散时间傅立叶变换频域的采样。

简单来说,对一张图像进行离散傅里叶变换就是将它分解成正弦和余弦两部分,也就是将图像从空间域转换到频域。这一转换的理论基础是:任一函数都可以表示成无数个正弦和余弦函数的和的形式。

二维图像的傅里叶变化用数学公式可以表示为:

OpenCV之傅里叶变换(C++实现)_第1张图片

式中的f是空间域值,F是频域值。变换之后的频域值是复数,因此,显示傅里叶变换之后的结果需要使用实数图像(real image)加虚数图像(complex image),或者幅度图像(magitude image)加相位图像(phase image)形式。

在实际的图像处理过程中,仅仅使用了幅度图像,因为幅度图像包含了原图像的几乎所有我们需要的几何信息。但是如果想通过修改幅度图像或相位图像的方法来简洁修改原空间图像,需要使用逆傅里叶变换得到修改之后的图像,这样就必须同时保留幅度图像和相位图像了。

在频域中,对一幅图像,高频部分代表了图像的细节、纹理信息;低频部分代表了图像的轮廓信息。如果对一幅精细的图像使用低通滤波器,那么滤波之后就只剩下轮廓了。如果图像的噪声恰好位于某个特定的频率范围之内,则可以通过滤波器来恢复原来的图像。

傅里叶变换在图像处理中可以做到图像增强与图像去噪、图像分割值边缘检测、图像特征提取、图像压缩等。

二、dft()函数详解

dft函数的作用是对一维或二维浮点数数组进行正向或反向离散傅里叶变换

C++: void dft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0);
  • 第一个参数,InputArray类型的src。输入矩阵,可以是实数或虚数。
  • 第二个参数,OutputArray类型的dst,函数调用后的运算结果在这里,其尺寸和类型取决于标识符,也就是第三个参数flags
  • 第三个参数,int类型的flags,转换的标识符,有默认值0,取值可以为下表。
  • 第四个参数,int类型的nonzeroRows,默认值是0。

OpenCV之傅里叶变换(C++实现)_第2张图片

三、getOptimalDFTSize()函数

返回给定向量尺寸经过DFT变换后结果的最优尺寸大小。其函数定义如下:

C++: int getOptimalDFTSize(int vecsize);
  • int vecsize: 输入向量尺寸大小(vector size)

DFT变换在一个向量尺寸上不是一个单调函数,当计算两个数组卷积或对一个数组进行光学分析,它常常会用0扩充一些数组来得到稍微大点的数组以达到比原来数组计算更快的目的。一个尺寸是2阶指数(2,4,8,16,32…)的数组计算速度最快,一个数组尺寸是2、3、5的倍数(例如:300 = 5*5*3*2*2)同样有很高的处理效率。

getOptimalDFTSize()函数返回大于或等于vecsize的最小数值N,这样尺寸为N的向量进行DFT变换能得到更高的处理效率。在当前N通过p,q,r等一些整数得出N = 2^p*3^q*5^r.

这个函数不能直接用于DCT(离散余弦变换)最优尺寸的估计,可以通过getOptimalDFTSize((vecsize+1)/2)*2得到。

四、magnitude()函数

C++: void magnitude(InputArray x, InputArray y, OutputArray magnitude);

参数解释:

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

其计算公式如下:

五、copyMakeBorder()函数

扩充图像边界,其函数定义如下:

C++: void copyMakeBorder(InputArray src, OutputArray dst, int top, int bottom, int left, int right, int borderType, const Scalar& value=Scalar() );

参数解释:

  • . InputArray src: 输入图像
  • . OutputArray dst: 输出图像,与src图像有相同的类型,其尺寸应为Size(src.cols+left+right, src.rows+top+bottom)
  • . int类型的top、bottom、left、right: 在图像的四个方向上扩充像素的值
  • . int borderType: 边界类型,由borderInterpolate()来定义,常见的取值为BORDER_CONSTANT
  • . const Scalar& value = Scalar(): 如果边界类型为BORDER_CONSTANT则表示为边界值

六、normalize()

归一化就是把要处理的数据经过某种算法的处理限制在所需要的范围内。首先归一化是为了后面数据处理的方便,其次归一化能够保证程序运行时收敛加快。归一化的具体作用是归纳同意样本的统计分布性,归一化在0-1之间是统计的概率分布,归一化在某个区间上是统计的坐标分布,在机器学习算法的数据预处理阶段,归一化也是非常重要的步骤。其定义如下:

C++: void normalize(InputArray src, OutputArray dst, 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-范数(欧几里得)
  • NORM_MINMAX: 数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。
  • int dtype = -1: 当该参数为负数时,输出数组的类型与输入数组的类型相同,否则输出数组与输入数组只是通道数相同,而depth = CV_MAT_DEPTH(dtype)
  • InputArray mask = noArray(): 操作掩膜版,用于指示函数是否仅仅对指定的元素进行操作。

代码实现傅里叶变换:

#include 
#include 
#include 
#include 
#include 

using namespace cv;
using namespace std;

int main() {
//    读入灰度图
    Mat srcImage = imread("//Users//dwz//Desktop//cpp//1.jpg", 0);
    cout << srcImage.size() << endl;


//    将输入图像扩展到最佳尺寸,边界用0填充
//    离散傅里叶变换的运行速度与图像的大小有很大的关系,当图像的尺寸使2,3,5的整数倍时,计算速度最快
//    为了达到快速计算的目的,经常通过添加新的边缘像素的方法获取最佳图像尺寸
//    函数getOptimalDFTSize()用于返回最佳尺寸,copyMakeBorder()用于填充边缘像素
    int m = getOptimalDFTSize(srcImage.rows);
    int n = getOptimalDFTSize(srcImage.cols);

    Mat padded;
    copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));
    cout << padded.size() << padded.channels() << endl;

//    为傅立叶变换的结果分配存储空间
//    将plannes数组组合成一个多通道的数组,两个同搭配,分别保存实部和虚部
//    傅里叶变换的结果使复数,这就是说对于每个图像原像素值,会有两个图像值
//    此外,频域值范围远远超过图象值范围,因此至少将频域储存在float中
//    所以我们将输入图像转换成浮点型,并且多加一个额外通道来存储复数部分
    Mat planes[] = {Mat_(padded), Mat::zeros(padded.size(), CV_32F)};

    Mat complexI;
    merge(planes, 2, complexI);
    cout << complexI.size() << endl;
    cout << planes->size() << endl;

//    进行离散傅立叶变换
    dft(complexI, complexI);

//    将复数转化为幅值,保存在planes[0]
    split(complexI, planes);   // 将多通道分为几个单通道
    magnitude(planes[0], planes[1], planes[0]);
    Mat magnitudeImage = planes[0];


//    傅里叶变换的幅值达到不适合在屏幕上显示,因此我们用对数尺度来替换线性尺度
//    进行对数尺度logarithmic scale缩放
    magnitudeImage += Scalar::all(1);     // 所有的像素都加1
    log(magnitudeImage, magnitudeImage);      // 求自然对数

//    剪切和重分布幅度图像限
//    如果有奇数行或奇数列,进行频谱裁剪
    magnitudeImage = magnitudeImage(Rect(0, 0, magnitudeImage.cols & -2, magnitudeImage.rows & -2));


//   ---- -------- 下面的是为了显示结果 ---------------
//    一分为四,左上与右下交换,右上与左下交换
//    重新排列傅里叶图像中的象限,使原点位于图像中心
    int cx = magnitudeImage.cols / 2;
    int cy = magnitudeImage.rows / 2;
    Mat q0(magnitudeImage, Rect(0, 0, cx, cy));   // ROI区域的左上
    Mat q1(magnitudeImage, Rect(cx, 0, cx, cy));  // ROI区域的右上
    Mat q2(magnitudeImage, Rect(0, cy, cx, cy));  // ROI区域的左下
    Mat q3(magnitudeImage, Rect(cx, cy, cx, cy)); // ROI区域的右下

    //交换象限(左上与右下进行交换)
    Mat tmp;
    q0.copyTo(tmp);
    q3.copyTo(q0);
    tmp.copyTo(q3);
    //交换象限(右上与左下进行交换)
    q1.copyTo(tmp);
    q2.copyTo(q1);
    tmp.copyTo(q2);

//    归一化
    normalize(magnitudeImage, magnitudeImage, 0, 1, NORM_MINMAX);

    //【9】显示效果图
    imshow("频谱幅值", magnitudeImage);
    waitKey();

    return 0;
}

结果:

 

 

你可能感兴趣的:(C++,opencv,图像处理,c++,opencv)