0
点赞
收藏
分享

微信扫一扫

OpenCV学习(22)

Ad大成 2022-01-24 阅读 85

离散傅里叶变换:示例程序

     在此示例中,将展示如何计算以及显示傅里叶变换后的幅度图像。由于数字图像的离散性,像素值的取值范围也是有限的。比如在一张灰度图像中,像素灰度值一般在0到255之间。因此,我们这里讨论的也仅仅是离散傅里叶变换(DFT)。如果需要得到图像中的几何结构信息,那么就要用到离散傅里叶变换了。下面的步骤将以输入图像为单通道的灰度图像Ⅰ为例,进行分步说明。
1.【第一步】载入原始图像
    我们在这一步以灰度模式读取原始图像,进行是否读取成功的检测,并显示出读取到的图像。代码如下。

//[1]以灰度模式读取原始图像并显示
	Mat srcImage = imread("E:/pictures/2.jpg", 0);
	imshow("原始图像", srcImage);

2.【第二步】将图像扩大到合适的尺寸
     离散傅里叶变换的运行速度与图片的尺寸有很大关系。当图像的尺寸是2、3、5的整数倍时,计算速度最快。因此,为了达到快速计算的目的,经常通过添凑新的边缘像素的方法获取最佳图像尺寸。函数getOptimalDFTSize()用于返回最佳尺寸,而函数copyMakeBorder()用于填充边缘像素,这一步代码如下。
 

//[2]将输入图像延扩到最佳尺寸,边界用0补充
	int m = getOptimalDFTSize(srcImage.rows);
	int n = getOptimalDFTSize(srcImage.cols);
	//将添加的像素初始化为0
	Mat padded;
	copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));

3.【第三步】为傅里叶变换的结果(实部和虚部)分配存储空间
    傅里叶变换的结果是复数,这就是说对于每个原图像值,结果会有两个图像值。此外,频域值范围远远超过空间值范围,因此至少要将频域储存在 float格式中。所以我们将输入图像转换成浮点类型,并多加一个额外通道来储存复数部分。

	//[3]为傅里叶变换的结果(实部和虚部)分配存储空间
	//将planes数组组合合并成一个多通道的数组complexI
	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);

4.【第四步】进行离散傅里叶变换
这里的离散傅里叶变换为图像就地计算模式( in-place,输入输出为同一图像)。

//[4]进行就地离散傅里叶变换
	dft(complexI, complexI);

5.【第五步】将复数转换为幅值
复数包含实数部分(Re)和虚数部分(imaginary - Im)。离散傅里叶变换的结果是复数,对应的幅度可以表示为:
M= √Re(DET(I))3+ Im(DFT(T))'
那么,转化为OpenCV代码,就是下面这样的。

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

6.【第六步】进行对数尺度(( logarithmic scale)缩放.
傅里叶变换的幅度值范围大到不适合在屏幕上显示。高值在屏幕上显示为白点,而低值为黑点,高低值的变化无法有效分辨。为了在屏幕上凸显出高低变化的连续性,我们可以用对数尺度来替换线性尺度,公式如下。
M=log(1+M)
而写成OpenCV代码,就是下面这样的。

	//[6]进行对数尺度缩放
	magnitudeImage += Scalar::all(1);
	log(magnitudeImage, magnitudeImage);

7.【第七步】剪切和重分布幅度图象限
因为在第二步中延扩了图像,那现在是时候将新添加的像素剔除了。为了方便显示,也可以重新分布幅度图象限位置(注:将第五步得到的幅度图从中间划开,得到4张1/4子图像,将每张子图像看成幅度图的一个象限,重新分布,即将4个角点重叠到图片中心)。这样的话原点(0,0)就位移到图像中心了。OpenCV代码如下。

//[7]剪切和重分布幅度图象限
	//若有奇数行或奇数列,进行频谱裁剪
	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);

8.【第八步】归一化
这一步仍然是为了显示。现在有了重分布后的幅度图,但是幅度值仍然超过可显示范围[0,1]。我们使用normalize()函数将幅度归一化到可显示范围。

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

9.【第九步】显示效果图
处理完成,最后进行显示即可。

源代码如下:
 

#include "opencv2\core\core.hpp"
#include "opencv2\imgproc\imgproc.hpp"
#include "opencv2\highgui\highgui.hpp"
#include <opencv2/core/utils/logger.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main()
{
	cv::utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);//控制台不在输出日志文件
	//[1]以灰度模式读取原始图像并显示
	Mat srcImage = imread("E:/pictures/2.jpg", 0);
	imshow("原始图像", srcImage);
	//[2]将输入图像延扩到最佳尺寸,边界用0补充
	int m = getOptimalDFTSize(srcImage.rows);
	int n = getOptimalDFTSize(srcImage.cols);
	//将添加的像素初始化为0
	Mat padded;
	copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));
	//[3]为傅里叶变换的结果(实部和虚部)分配存储空间
	//将planes数组组合合并成一个多通道的数组complexI
	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);
	//[4]进行就地离散傅里叶变换
	dft(complexI, complexI);
	//[5]将复数转换为幅值
	split(complexI, planes);//将多通道数组complexI分离成几个单通道数组
	magnitude(planes[0], planes[1], planes[0]);
	Mat magnitudeImage = planes[0];
	//[6]进行对数尺度缩放
	magnitudeImage += Scalar::all(1);
	log(magnitudeImage, magnitudeImage);
	//[7]剪切和重分布幅度图象限
	//若有奇数行或奇数列,进行频谱裁剪
	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);
	//[8]归一化
	normalize(magnitudeImage, magnitudeImage, 0, 1, NORM_MINMAX);
	//[9]显示效果图
	imshow("频谱幅值", magnitudeImage);
	waitKey();
	return 0;
}

运行结果如图:

 

 

举报

相关推荐

0 条评论