Opencv学习八离散傅里叶变换

**

前言

**
可恶,是傅里叶。
很多数学上的处理已经封装起来了,但是要熟练的掌握其使用的话,那么这些数学的处理也要弄明白怎么回事。
我也在努力学习中。
在这里插入图片描述
**

离散傅里叶变换

**
离散傅里叶变换(Discrete Fourier Transform, D F T DFT ),是指傅里叶变换在时域(t,时间)和频域(ω,频率)中都呈现离散的形式,将时域信号的采样变换为在离散时间里傅里叶变换( D F T DFT )频域的采样。
形式上,变换两端(时域和频域)的序列是有限长的,而实际这两组序列都应该被认为是离散周期信号的主值序列。即使对有限长的离散信号做DFT,也应当对其经过周期延拓成为周期信号再进行变换。
在实际应用中,通常采用快速傅里叶变换来高效计算 D F T DFT

离散傅里叶变换原理

简单来说,对一张图像使用傅里叶变换就是将它分解成正弦和余弦两部分,也就是将图像从空间域(spatial
domain)转换到频域(frequency domain)。
这一变换的理论基础为:任一函数都可以表示为无数个正弦和余弦函数的和的形式。所以,傅里叶变换就是一个用来将函数分解的工具。

所谓的频域和时域看下面这幅图应该能了解一点:
在这里插入图片描述
在这里插入图片描述
基本就是用傅里叶变换后,一切变得好求多了。

二维图像的傅里叶变换可以用以下数学公式表达:
F ( k , l ) = i = 0 N 1 i = 0 N 1 f ( i , j ) e i 2 π ( k i N + l j N ) F(k,l)= \sum_{i=0}^{N-1} \sum_{i=0}^{N-1} f(i,j) e^{ -i2π(\frac{ki}{N} + \frac{lj}{N}) }
e i x = c o s x + i s i n x e^ix^=cosx+isinx

式中 f f 是空间域(spatial domain)值, F F 是频域(frequency domain)值。转换之后的频域值是复数,因此,显示傅里叶变换后的结果需要使用实数图像(real image)加复数图像(complex image),或者幅度图像(magitude image)加相位图像(phase image)的形式。
在实际的图像处理中,仅仅使用了幅度图像,因为幅图图像包含了源图像几乎所有我们需要的集合信息。然而,如果想通过修改幅度图像或者相位图像的方法间接修改原空间图像,需要使用傅里叶逆变换得到修改后的空间图像,这样就必须同时保留幅度图像和相位图像了。

在频域里面,对于一副图像,高频部分代表了图像的细节、纹理信息。低频部分代表了图像的轮廓信息。如果对于一副精细的图像使用低通滤波器,那么滤波后的结果就只有轮廓了。
这与信号处理的基本思想是相通的。如果图像受到的噪声恰好位于某个特定的“频率”内,则可以通过滤波器来恢复原来的图像。傅里叶变换在图像中可以做到图像增强与图像去躁、图像分割之边缘检测、图像特征提取、图像压缩等。

在之后的示例中,会演示如何计算以及显示傅里叶变换后的幅度图像。
由于数字图像的离散性,像素值的取值范围也是有限的。比如在一张灰度图像中,像素灰度值一般在0~255之间。因此,这里讨论的仅仅是离散傅里叶变换( D F T DFT )。

**

d f t ( ) dft()

**

void dft(InputArray src,  //输入矩阵,可以为实数、虚数
        OutputArray dst,//输出矩阵,尺寸类型取决于标识符flags
               int flags = 0, //转换的标识符
           int nonzeroRows = 0);//

d f t dft标识符取值列表

标识符 意义
DFT_INVERSE 用一维或二维逆变换代替默认的正向变换
DFT_SCALE 缩放比例标识符,输出的结果都会以 1 N \frac{1}{N} 进行缩放,通常会结合DFT_INVERSE一起使用
DFT_ROWS 对输入矩阵的每行进行正向或反向的变换,此标识符可以在处理多种矢量的时候用于减少资源开销,这些处理常常是3维或高维变换等复杂操作
DFT_COMPLEX_OUTPUT 进行一维或二维实数数组正变换。这样的结果虽然是复数阵列,但拥有复数的共轭对称性 C C S CCS ,所以可以被写成一个拥有同样尺寸的实数阵列
DFT_REAL_OUTPUT 进行一维或二维复数数组反变换。这样的结果通常是一个大小相同的复矩阵。如果输入的矩阵有复数的共轭对称性(比如是一个带有DFT_COMPLEX_OUTPUT表示符的正变换结果 ),便会输出实矩阵
DFT_COMPLEX_INPUT 进行反向一维或二维反变换,而不是默认的正变换
DCT_INVERSE DFT_INVERSE
DCT_ROWS DFT_ROWS

让我捋一捋

在这里插入图片描述

  • 第四个参数,int类型的nonzeroRows。当此参数输入非零时(最好是取值为想要处理的那一行的值,比如C.rows),函数会假设只有输入矩阵的第一个非零行包含非零元素(没有设置DFT_INVERSE),或只有输入矩阵的第一个非零行包括非零元素(设置了DFT_INVERSE)。这样的话,函数就可以对其他行进行更高效的出来,以节省时间开销。这项技术在采用DFT计算矩阵互相关或卷积时非常有效。

上面的dft()函数就是傅里叶变换的函数,下面再列举几个其他的:
getOptimalDFTSize()
返回DFT最优尺寸。为了提高离散傅里叶变换的运行速度,需要扩充图像,而具体扩充多少,由这个函数得到。

int getOptimalDFTSize(int vecsize);

copyMakeBorder()
扩充图像边界

void copyMakeBorder(InputArray src,//源图像
			 		OutputArray dst,/*运算后结果。大小Size(src.cols + left+right,src.rows + top + bottom)*/
                     		int top, //分别向4个方向扩充的像素个数
                     		int bottom, 
                     		int left, 
                     		int right,
                           	int borderType, //边界类型
                           	const Scalar& value = Scalar() );//边界值

magnitude()
计算二维矢量的幅值
原理,就是长度:
d s t ( c o m p l e x ) = r e a l ( c o m p l e x ) 2 + i m a g i n a r y ( c o m p l e x ) 2 dst(complex)=\sqrt{real(complex)^{2}+imaginary(complex)^{2}}

void magnitude(InputArray x, //矢量浮点型值,就是实部
							InputArray y, //矢量浮点型值,就是虚部
							OutputArray magnitude);//输出的幅值
  • 一般像上面这种情况,输出的图像的大小和类型都是和第一个输入的图像相匹配的

log()
计算自然(以e为底)对数

void log(InputArray src, OutputArray dst);

normalize()
矩阵归一化。

void normalize( InputArray src, //输入矩阵
			InputOutputArray dst, //输出矩阵
			double alpha = 1, //下边界
			double beta = 0,//上边界
			int norm_type = NORM_L2, //归一化类型
			int dtype = -1, //负(dst类型与src相同),正(dst通道数与src相同)
			InputArray mask = noArray());//掩码MASK

下面是一个对图像傅里叶变换的示例:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

int main() {

	//以灰度模式读取原始图像并显示
	Mat src = imread("E:/wumo.jpg", IMREAD_GRAYSCALE);
	if (!src.data) {
		cout << "error..";
		system("pause");
	}
	imshow("原始图像", src);


	//将图像扩大到合适的尺寸,边界用0补充
	int m = getOptimalDFTSize(src.rows);
	int n = getOptimalDFTSize(src.cols);
	//将添加的像素初始化为0
	Mat padded;
	copyMakeBorder(src,  
		padded,
		0,
		m - src.rows,
		0,
		n - src.cols,
		BORDER_CONSTANT,
		Scalar::all(0));

	//为傅里叶变换分配空间
	//将planes数组组合成一股多通道的数组complexM
	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
	Mat complexM;
	merge(planes, 2, complexM);

	//进行傅里叶变换
	dft(complexM, complexM);

	//将复数转换为幅值
	split(complexM, planes);//将多通道数组分离成单通道
	magnitude(planes[0], planes[1], planes[0]);
	Mat magnitudeImg = planes[0];


	//进行对数尺度(logarithmic scale)缩放
	magnitudeImg += Scalar::all(1);

	//求自然对数
	log(magnitudeImg, magnitudeImg);


	//剪切和重分布幅度图像限
	//若有奇数行或奇数列,进行频谱裁剪
	magnitudeImg = magnitudeImg(Rect(0, 0, magnitudeImg.cols & -2 , magnitudeImg.rows & -2));//宽高与上 1110


	//重新排列傅里叶图像中的象限,使原点位于图像中心
	int cx = magnitudeImg.cols / 2;
	int cy = magnitudeImg.rows / 2;
	Mat q0(magnitudeImg, Rect(0, 0, cx, cy));//ROI=左上
	Mat q1(magnitudeImg, Rect(cx, 0, cx, cy));//ROI=右上
	Mat q2(magnitudeImg, Rect(0, cy, cx, cy));//ROI=左下
	Mat q3(magnitudeImg, Rect(cx, cy, cx, cy));//ROI=右下

	//交换象限(交叉交换)
	Mat temp;
	q0.copyTo(temp);
	q3.copyTo(q0);
	temp.copyTo(q3);

	q1.copyTo(temp);
	q2.copyTo(q1);
	temp.copyTo(q2);



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

	//显示
	imshow("频谱幅值", magnitudeImg);


	waitKey(0);

}

运行结果:
在这里插入图片描述
话说,这得到的又是个啥?

上面的代码是以 d f t ( ) dft() 为核心,对图像进行傅里叶变换的代码。展示了如何计算以及显示傅里叶变换后的幅度图像。

由于数字的离散性,像素值的取值范围也是有限的。比如在一张灰度图像中,像素灰度值一般在0~255之间。
这里讨论的仅仅是 D F T DFT

如果需要得到图像的几何结构信息,就要用到 D F T DFT

解释
上面的是以输入图像为单通道的灰度图像为例。

1、载入原始图像用到IMREAD_GRAYSCALE
2、将图像扩大到合适的尺寸

离散傅里叶变换的运算速度与图片尺寸有关。当图像的尺寸是2、3、5的整数倍时,计算速度最快。所以,为了达到快速计算的目的,通常是添加新的边缘像素的方法来获取最佳图像尺寸。getOptimalDFTSize()返回最佳尺寸,copyMakeBorder()填充边缘像素。

	//获取最佳的尺寸
	int m = getOptimalDFTSize(src.rows);
	int n = getOptimalDFTSize(src.cols);
	//将添加的像素初始化为0
	Mat padded;
	copyMakeBorder(src,
		padded,
		0,    //上
		m - src.rows, //下
		0,  //左
		n - src.cols,  //右
		BORDER_CONSTANT,   
		Scalar::all(0));

3、为傅里叶变换的结果(实部和虚部)分配空间

傅里叶变换的结果是复数,就是说对于每个源图像值,结果会有两个图像值。此外,频域值范围远远超过空间值范围,因此至少要将频域存储在float格式中。所以需要将输入图像转换成浮点类型,并增加一个额外通道来存储复数部分。

	Mat planes[] = { Mat_<float>(padded),   //转换成float型
					Mat::zeros(padded.size(), CV_32F) };//增加一个通道
	Mat complexM;
	merge(planes, 2, complexM);//合并

4、傅里叶变换

dft(complexM,complexM);

5、将复数转换成幅值

复数包括Re和imaginary-Im。离散傅里叶变换的结果是复数,对应的幅度可以表示为:
M = R e ( D F T ( c o m p l e x ) ) 2 + I m ( D F T ( c o m p l e x ) ) 2 M=\sqrt{Re(DFT(complex))^{2} + Im(DFT(complex))^{2} }

	//将复数转换为幅值
	split(complexM, planes);//将多通道数组分离成单通道
	magnitude(planes[0], planes[1], planes[0]);
	Mat magnitudeImg = planes[0];

6、进行对数尺寸(logarithmic scale)缩放

傅里叶变换的幅度值范围大到不适合在屏幕上显示。高值在屏幕上显示为白点,低值显示为黑点。高低值的变化无法有效分辨。为了在屏幕上凸显出高低变化的连续性,可以用对数尺度来替换线性尺寸。
M = log ( 1 + M ) M=\sqrt{\log{(1+M)}}

	//进行对数尺度(logarithmic scale)缩放
	magnitudeImg += Scalar::all(1);

	//求自然对数
	log(magnitudeImg, magnitudeImg);

7、剪切和重分布幅度图象限

第二步中中延扩了图像,所以是时候将新添加的像素剔除了。为了方便显示,可以重新分布幅度图象限位置(注:将第五步得到的幅度图从中间划开,得到4张1/4子图像,在交换他们的位置,使4个角点重叠到图片中心)。这样的话,原点(0,0)------左上角-------就位移到图像中心了


	//重新排列傅里叶图像中的象限,使原点位于图像中心
	int cx = magnitudeImg.cols / 2;
	int cy = magnitudeImg.rows / 2;
	Mat q0(magnitudeImg, Rect(0, 0, cx, cy));//ROI=左上
	Mat q1(magnitudeImg, Rect(cx, 0, cx, cy));//ROI=右上
	Mat q2(magnitudeImg, Rect(0, cy, cx, cy));//ROI=左下
	Mat q3(magnitudeImg, Rect(cx, cy, cx, cy));//ROI=右下

	//交换象限(交叉交换)
	Mat temp;
	q0.copyTo(temp);
	q3.copyTo(q0);
	temp.copyTo(q3);

	q1.copyTo(temp);
	q2.copyTo(q1);
	temp.copyTo(q2);

为了看看分布前的图像,可以用一个中间变量复制magnitudeImg,再归一化,然后显示。

	Mat pImg;
	magnitudeImg.copyTo(pImg);
	normalize(pImg, pImg, 0, 1, NORM_MINMAX);
	imshow("对数尺度缩放后", pImg);

在这里插入图片描述

8、归一化

这一步仍然是为了显示。现在有了重分布后的幅度图,但是幅度值仍然超过可以显示的范围【0,1】。需要使用normalize()将幅度归一化到可显示范围

  • 相当于由向量求其方向上的单位向量。
normalize(magnitudeImg, magnitudeImg, 0, 1, NORM_MINMAX);

参考:《Opencv3编程入门》

猜你喜欢

转载自blog.csdn.net/weixin_41374099/article/details/86583770
今日推荐