1、OpenCV傅里叶变换相关函数

首先我要说明的是,在使用OpenCV写代码做图像傅里叶变换的时候,并仅仅是调用dft函数做一个傅里叶变换这么简单的,而是先要对图像进行一些变换之后,才能得到正确的傅里叶变换结果。因此,第一部分我想先列出几个OpenCV提供的与傅里叶变换相关的函数,在了解这些函数功能的基础上,我们再进行具体的图像傅里叶变换的过程。

1.1 dft()

首先,OpenCV提供的傅里叶变换函数dft。其定义如下:

void dft(InputArray src, OutputArray, dst, int flags, int nonzeroRows);
/**
  * 参数解释:
  * src : 输入图像
  * dst : 输出图像,傅里叶变换结果。默认情况下返回值有两个通道,第一个通达是实部,第二个通道是虚部。
  * flags : 转换的标识符,有默认值0,暂时不用理会这个参数
  */

1.2 magnitude()

计算二维矢量的幅值。其定义如下:

void magnitude(InputArray x, InputArray y, OutputArray magnitude);
/**
  * 参数解释:
  * x : 实部
  * y : 虚部
  * magnitude : 幅值结果
  */

其计算公式如下:

dst(I) = \sqrt{x(I)^2 + y(I)^2}

1.3 phase()

void phase(InputArray x, InputArray y, OutputArray dst, bool angleIndegrees=false);
/**
  * 参数解释:
  * x : 实部
  * y : 虚部
  * dst : x和y的反正切值结果
  */

其计算公式如下:

dst = \arctan \frac{y}{x}

1.4 getOptimalDFTSize()

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

int getOptimalDFTSize(int vecsize);
/**
  * 参数解释:
  * 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.
  */

1.5 copyMakeBorder()

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

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

1.6 normalize()

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

void normalize(InputArray src, OutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray() );
/**
  * 参数解释:
  * src: 输入图像
  * dst: 输出图像,尺寸大小和src相同
  * alpha = 1: range normalization模式的最小值
  * beta = 0: range normalization模式的最大值,不用于norm normalization(范数归一化)模式
  * norm_type = NORM_L2: 归一化的类型,主要有 
  *             NORM_INF: 归一化数组的C-范数(绝对值的最大值)
  *             NORM_L1: 归一化数组的L1-范数(绝对值的和) 
  *             NORM_L2: 归一化数组的L2-范数(欧几里得)
  *             NORM_MINMAX: 数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。
  * int dtype = -1: 当该参数为负数时,输出数组的类型与输入数组的类型相同,否则输出数组与输入数组只是通道数相同,而depth =                 * CV_MAT_DEPTH(dtype)
  * mask = noArray(): 操作掩膜版,用于指示函数是否仅仅对指定的元素进行操作。
  */

1.2 OpenCV傅里叶变换步骤

傅里叶变换

  • getOptimalDFTSize()函数得到DFT变换后结果的最优尺寸大小
  • 根据得到的尺寸大小,使用copyMakeBorder()函数填充图像,得到填充后的Mat
  • 根据新生成的Mat,使用merge()函数得到一个双通道的Mat,命名为planes
  • 使用dft()函数进行傅里叶变换,得到通道1为实部,通道2为虚部

显示傅里叶变换结果

  • 使用magnitude()函数得到傅里叶变换结果幅度谱
  • 将幅度值转换到对数尺度,取对数的目的是使那些振幅较低的成分相对高振幅成分得以拉高,以便观察掩盖在低幅噪声中的周期信号
  • 使用normalize()函数进行归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
  • 显示结果

1.3 具体代码

在这里我只展示了关键的代码片段,而不是具体的可直接运行的代码。这部分完整的代码可以访问我的gitee(码云)项目,该项目记录了我自学数字图像处理所写的全部代码,当然也包括OpenCV傅里叶变换的部分。项目链接:https://gitee.com/lucasnan/Digital-Image-Process

傅里叶变换

cv::Mat frequencyfilter::frequencyFiltering::fourierTransform(const cv::Mat& originImage){
    cv::Mat paddingImage;

    //得到适合傅里叶变换的最优尺寸
    int m = cv::getOptimalDFTSize(originImage.rows);
    int n = cv::getOptimalDFTSize(originImage.cols);

    //填充,上侧和左侧不填充
    cv::copyMakeBorder(originImage, paddingImage, 0, m-originImage.rows, 
                       0, n-originImage.cols, cv::BORDER_CONSTANT, cv::Scalar(0));

    //双通道Mat
    cv::Mat planes[] = {cv::Mat_<float>(paddingImage), cv::Mat::zeros(paddingImage.size(), CV_32FC1)};
    cv::Mat mergeImage;
    cv::merge(planes, 2, mergeImage);

    //进行傅里叶变换
    cv::dft(mergeImage, mergeImage, cv::DFT_COMPLEX_OUTPUT);

    return mergeImage;
}

获得傅里叶变换结果幅度值

cv::Mat frequencyfilter::frequencyFiltering::getMagnitudeImage(const cv::Mat& fourierImage){
    cv::Mat planes[] = {cv::Mat::zeros(fourierImage.size(), CV_32FC1), 
                        cv::Mat::zeros(fourierImage.size(), CV_32FC1)};

    cv::Mat magImage = planes[0].clone();
    cv::split(fourierImage, planes);
    cv::magnitude(planes[0], planes[1], magImage);

    //cv::log(magImage+1, magImage);

    //如果有奇数行或列,则转换为偶数
    magImage = magImage(cv::Rect(0, 0, magImage.cols-(magImage.cols%2), magImage.rows-(magImage.rows%2)));

    return magImage;
}

傅里叶变换结果中心化

cv::Mat frequencyfilter::frequencyFiltering::changeCenter(const cv::Mat& magImage){
    //重新排布傅里叶变换后的图像,使得原点位于图像中心
    int centerX = magImage.cols / 2;
    int centerY = magImage.rows / 2;

    cv::Mat magImageCopy = magImage.clone();
    cv::Mat planes[] = {cv::Mat::zeros(magImageCopy.size(), CV_32FC1), 
                        cv::Mat::zeros(magImageCopy.size(), CV_32FC1)};

    cv::Mat mat1(magImageCopy, cv::Rect(0, 0, centerX, centerY));               //左上
    cv::Mat mat2(magImageCopy, cv::Rect(0, centerY, centerX, centerY));         //右上
    cv::Mat mat3(magImageCopy, cv::Rect(centerX, 0, centerX, centerY));             //左下
    cv::Mat mat4(magImageCopy, cv::Rect(centerX, centerY, centerX, centerY));   //右下

    //互换左上和右下
    cv::Mat tempImage;
    mat1.copyTo(tempImage);
    mat4.copyTo(mat1);
    tempImage.copyTo(mat4);

    //互换左下和右上
    mat2.copyTo(tempImage);
    mat3.copyTo(mat2);
    tempImage.copyTo(mat3);

    return magImageCopy;
}

获得傅里叶变化结果相位谱

cv::Mat frequencyfilter::frequencyFiltering::getPhaseImage(const cv::Mat& fourierImage){
    cv::Mat planes[] = {cv::Mat::zeros(fourierImage.size(), CV_32FC1), 
                        cv::Mat::zeros(fourierImage.size(), CV_32FC1)};

    cv::Mat phaseImage = planes[0].clone();
    cv::split(fourierImage, planes);
    cv::phase(planes[0], planes[1], phaseImage);
    //imshow("phase", phaseImage);
    return phaseImage;
}

显示傅里叶变换结果

frequencyfilter::frequencyFiltering::frequencyFiltering(cv::Mat image){
    this->inputImage = image.clone();
    fourierTransformImage = fourierTransform(inputImage);

    cv::Mat magImage = getMagnitudeImage(fourierTransformImage);

    magImage = changeCenter(magImage);

    //计算幅值,并转换到对数尺度
    //取对数的目的是使那些振幅较低的成分相对高振幅成分得以拉高,以便观察掩盖在低幅噪声中的周期信号。
    cv::log(magImage+1, magImage);

    //归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
    cv::normalize(magImage, magImage, 0, 1, CV_MINMAX);

    cv::imshow("test", inputImage);
    cv::imshow("test2", magImage);

    inverseFourierTransform(fourierTransformImage);
}

傅里叶逆变换

cv::Mat frequencyfilter::frequencyFiltering::inverseFourierTransform(const cv::Mat& fourierImage){
    cv::Mat dealtImage = fourierImage.clone();
    cv::Mat iDFT[] = {cv::Mat::zeros(dealtImage.size(), CV_32FC1), 
                      cv::Mat::zeros(dealtImage.size(), CV_32FC1)};

    cv::idft(dealtImage, dealtImage);
    cv::split(dealtImage, iDFT);
    normalize(iDFT[0], iDFT[0], 0, 1, CV_MINMAX);
    return iDFT[0];
}

结果如下:

最后,再说明一下,以上代码片段都是从我的数字图像处理项目代码中截取的,如果你对OpenCV傅里叶变换有一定的了解,那么结和以上代码可以应该可以解决你的问题,知道怎么用OpenCV来做傅里叶变换,但是如果你不能理解以上代码,我推荐你去看我在码云上传的项目,里面有完整的项目代码和结构,应该可以帮助你更好地理解。地址:https://gitee.com/lucasnan/Digital-Image-Process/tree/master/Frequency_Filter

Last modification:August 15th, 2020 at 07:38 pm
如果觉得我的文章对你有用,请随意赞赏