目录
一、傅里叶变换
二、dft()函数详解
三、getOptimalDFTSize()函数
四、magnitude()函数
五、copyMakeBorder()函数
六、normalize()
离散傅里叶变换(Discrete Fourier Transform, DFT),是指傅里叶变换在时域和频域上都呈现离散的形式,将时域信号的采样变换为在离散时间傅立叶变换频域的采样。
简单来说,对一张图像进行离散傅里叶变换就是将它分解成正弦和余弦两部分,也就是将图像从空间域转换到频域。这一转换的理论基础是:任一函数都可以表示成无数个正弦和余弦函数的和的形式。
二维图像的傅里叶变化用数学公式可以表示为:
式中的f是空间域值,F是频域值。变换之后的频域值是复数,因此,显示傅里叶变换之后的结果需要使用实数图像(real image)加虚数图像(complex image),或者幅度图像(magitude image)加相位图像(phase image)形式。
在实际的图像处理过程中,仅仅使用了幅度图像,因为幅度图像包含了原图像的几乎所有我们需要的几何信息。但是如果想通过修改幅度图像或相位图像的方法来简洁修改原空间图像,需要使用逆傅里叶变换得到修改之后的图像,这样就必须同时保留幅度图像和相位图像了。
在频域中,对一幅图像,高频部分代表了图像的细节、纹理信息;低频部分代表了图像的轮廓信息。如果对一幅精细的图像使用低通滤波器,那么滤波之后就只剩下轮廓了。如果图像的噪声恰好位于某个特定的频率范围之内,则可以通过滤波器来恢复原来的图像。
傅里叶变换在图像处理中可以做到图像增强与图像去噪、图像分割值边缘检测、图像特征提取、图像压缩等。
dft函数的作用是对一维或二维浮点数数组进行正向或反向离散傅里叶变换
C++: void dft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0);
返回给定向量尺寸经过DFT变换后结果的最优尺寸大小。其函数定义如下:
C++: int getOptimalDFTSize(int vecsize);
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得到。
C++: void magnitude(InputArray x, InputArray y, OutputArray magnitude);
参数解释:
其计算公式如下:
扩充图像边界,其函数定义如下:
C++: void copyMakeBorder(InputArray src, OutputArray dst, int top, int bottom, int left, int right, int borderType, const Scalar& value=Scalar() );
参数解释:
归一化就是把要处理的数据经过某种算法的处理限制在所需要的范围内。首先归一化是为了后面数据处理的方便,其次归一化能够保证程序运行时收敛加快。归一化的具体作用是归纳同意样本的统计分布性,归一化在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() )
参数解释:
代码实现傅里叶变换:
#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;
}
结果: