基于C++的opencv(七)core组件

一、访问图像中的像素

1.1.图像在内存中的存储方式

opencv中子列的通道顺序是反过来的——BGR而不是RGB。很多情况下,因为内存足够大,可实现连续存储,因此,图像中的各行就能一行一行地连接起来,形成一个长行。连续存储有助于提升图像扫描速度,我们可以使用isContinuous()来判断矩阵是否是连续存储的。

1.2.颜色空间缩减

若矩阵元素存储的是单通道像素,使用c或c++的无符号字符类型,那么像素可有256个不同值。

颜色空间缩减(color space reduction)的做法是:将现有颜色空间值除以某个输入值,以获得较少的颜色数。

简单的颜色空间缩减算法可由下面两步组成:
1.遍历图像矩阵的每一个像素;
2.对像素应用上述公式。

1.3.LUT函数:Look up table操作

//首先建立一个mat型用于查表

Mat lookUpTable(1,256,CV_8U);
uchar*p=lookUpTable.data;
for(int i=0;i<256;++i)
	p[i]=table[i];
//然后我们调用函数(I是输入,J是输出)
for(int i=0;i<times;++i)
	LUT(I,lookUpTable,J)

1.4.计时函数

计时函数:
getTickCount()函数返回CPU自某个事件以来走过的时钟周期数;
getTickFrequency()函数返回CPU一秒钟所走的时钟周期数。

double time0=static_cast<double>(getTickCount());//记录起始时间
//进行图像处理操作
time0=((double)getTickCount()-timeo)/getTickFrequency();
cout<<"此方法运行时间为:“<秒”<<endl;//输出运行时间

1.5.访问图像中像素二点三种方法

opencv中三种访问每个像素地方法:
1)指针访问:C操作符[ ];
2)迭代器iterator;
3)动态地址计算。

#include
#include
#include

using namespace std;
using namespace cv;

void colorReduce(Mat& inputImage,Mat& outputImage,int div);

int main()
{
	Mat srcImage=imread("1.jpg");
	imshow("原图",srcImage);

	Mat dstImage;
	dstImage.create(srcImage.rows,srcImage.cols,srcImage.type());//效果图的大小、类型与原图片相同
	//记录起始时间
	double time0=static_cast<double>(getTickCount());
	colorReduce(srcImage,dstImage,32);
	//计算运行时间并输出
	time0=((double)getTickCount()-time0)/getTickFrequency();
	cout<<"此方法运行时间为:"<<time0<<"秒"<<endl;

	imshow("效果图“,dstImage);
	waitKey(0);
}

【一】用指针访问像素

void colorReduce(Mat & InputImage,Mat & outputImage,int div)
{
	outputImage=inputImage.clone();//复制实参到临时变量
	int rowNumber=outputImage.rows;
	int colNumber=outputImage.cols*outputImage.channels();

	for(int i=0;i<rowNumber;i++)
	{
		uchar *data=outputImage.ptr<uchar>(i);
		for(int j=0;j<colNumber;j++)
		{
			data[j]=data[j]/div*div+div/2;
		}
	}
}

Mat类有若干成员函数可以获取图像的属性。公有成员变量cols和rows给出了图像的宽和高,而成员函数channels()用于返回图像的通道数。灰度图的通道数为1,彩色图的通道数为3.

int colNumber=outputImage.cols*outputImage.channels();

Mat类提供了ptr函数可以得到图像任意行的首地址。ptr是一个模板函数,他返回第i行的首地址。

uchar *data=outputImage.ptr<uchar>(i);

【二】用迭代器操作像素
在迭代法中,所需要做的仅仅是获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向二点内容。

void colorReduce(Mat &inputImage,Mat & outputImage,int div)
{
	outputImage=inputImage,clone();
	Mat_<Vec3b>::iterator it=outputImage.begin<Vec3b>();
	Mat_<Vec3b>::iterator itend=outputImage.end<Vec3b>();

	for(;it!=itend;++it)
	{
		(*it)[0]=(*it)[0]/div*div+div/2;
		(*it)[1]=(*it)[1]/div*div+div/2;
		(*it)[2]=(*it)[2]/div*div+div/2;
	}
}

【三】动态地址计算
使用动态地址运算配合at方法。

void colorReduce(Mat &inputImage,Mat & outputImage,int div)
{
	outputImage=inputImage.clone();
	int rowNumber=outputImage.rows;
	int colNumber=outputImage.cols;

	for(int i=0;i<rowNumber;i++)
	{
		for(int j=0;j<colNumber;j++)
		{
			outputImage.at<Vec3b>(i,j)[0]=outputImage.at<Vec3b>(i,j)[0]/div*div+div/2;
			outputImage.at<Vec3b>(i,j)[1]=outputImage.at<Vec3b>(i,j)[1]/div*div+div/2;
			outputImage.at<Vec3b>(i,j)[2]=outputImage.at<Vec3b>(i,j)[2]/div*div+div/2;
		}
	}
}

Mat类中的cols和rows给出了图像的宽和高。而成员函数at(int x,int y)可以用来存取图像元素,但是必须在编译期知道图像的数据类型。一定要确保指定的数据类型要和矩阵中的数据类型相符合,因为at方法本身不会对任何数据类型进行转换。

对于彩色图像,每个像素由三个部分构成:蓝色通道、绿色通道和红色通道(BGR)。对于一个包含彩色图像的Mat,会返回一个由三个8位数组成的向量,opencv将此类型的向量定义为Vec3b,即由三个unsigned char组成的向量。

image.at<Vec3b>(j,i)[channel]=value;

其中,索引值channel标明了颜色通道号。

二、ROI区域图像叠加&图像混合

2.1.感兴趣区域:ROI

感兴趣区域(ROI,region of interest)

定义ROI区域的两种方法:
1.使用矩形区域的Rect。它指定矩形的左上角坐标(构造函数的前两个参数)和矩形的长宽(构造函数的后两个参数)以定义一个矩形区域;

Mat imageROI;
imageROI=image(Rect(500,250,logo.cols,logo.rows));

2.指定感兴趣行或列的范围(Range)。Range是指从起始索引到终止索引(不包含终止索引)的一系列连续序列。cRange可以用来定义Range。

imageROI=image(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));

利用ROI将一幅画加到另一幅图的指定位置。通过一个图像淹没(mask),直接将插入处的像素设置为logo图像额像素值。

bool ROI_AddImage()
{
	Mat srcImage1=imread("dota_pa.jpg");
	Mat logoImage=imread("dota_logo.jpg");
	if(!srcImage1.data)
	{
		printf("读取srcImage1错误~! \n");
		return false;
	}
	if(!logoImage.data)
	{
		printf("读取logoImage错误~! \n");
		return false;
	}
	Mat imageROI=srcImage1(Rect(200,250,logoImage.cols,logoImage.rows));
	//加载掩膜(必须是灰度图)
	Mat mask=imread("dota_logo.jpg",0);
	//将掩膜复制到ROI
	logoImage.copyTo(imageROI,mask);

	namedWindow("<1>利用ROI实现图像叠加示例窗口");
	imshow("<1>利用ROI实现图像叠加示例窗口",srcImage1);

	return true:
}

先载入两张jpg图片,然后定义一个Mat类型的imageROI,并使用Rect设置其感兴趣区域为srcImage1中的一块区域,将imageROI和srcImage1关联起来。

2.2.线性混合操作

线性混合操作是一种典型的二元(两个输入)的像素操作。
主要运用OpenCV中addWeighted函数。

2.3.计算数组加权和:addWeighted()函数

void(InputArray src1,double alpha,InputArray src2,double beta,double gamma,OutputArray dst,int dtype=-1);

第一个参数,InputArray类型的是src1,表示需要加权的第一个数组,常常填一个Mat;
第二个参数,double类型的alpha,表示第一个数组的权重;
第三个参数,InputArray类型的src2,表示第二个数组,它需要和第一个数组拥有相同的尺寸和通道数;
第四个参数,double类型的beta,表示第二个数组的权重值;
第五个参数,double类型的gamma,一个加到权重总和上的标量值。
第六个参数,OutputArray类型的dst,输出的数组,它和输入的两个数组拥有相同的尺寸和通道数;
第七个参数,int类型的dtype,输出阵列的可选深度,有默认值-1。当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。

dst=src1[I]*alpha+src2[I]*beta+gamma;

其中I是多维数组元素的索引值。当输出数组的深度位CV_32S时,这个函数不适用,这时候会内存溢出或者算出的结果不对。

bool LinearBlending()
{
	double alphaValue=0.5;
	double betaValue;
	Mat srcImage2,srcImage3,dstImage;

	srcImage2=imread("mogu.jpg");
	srcImage3=imread("rain.jpg");

	if(!srcImage2.data)
	{
		printf("读取srcImage2错误~! \n");
		return false;
	}
	if(!srcImage3.data)
	{
		printf("读取srcImage3错误~! \n");
		return false;
	}

	betaValue=(1.0-alphaValue);
	addWeighted(srcImage2,alphaValue,srcImage3,betaValue,0.0,dstImage);
	namedWindow("<2>线性混合窗口【原图】",1);
	imshow("<2>线性混合窗口【原图】“,srcImage2);

	namedWindow("<3>线性混合窗口【效果图】",1)
	imshow("<3>线性混合窗口【效果图】",dstImage);

	return true;
}

2.4.初级图像混合示例

bool ROI_LinearBlending()
{
	Mat srcImage4=imread("dota_pa.jpg",1);
	Mat logoImage=imread("dota_logo.jpg");

	if(!srcImage4.data)
	{
		printf("读取srcImage4错误~! \n");
		return false;
	}
	if(!logoImage.data)
	{
		printf("读取logoImage错误~! \n");
		return false;
	}

	Mat imageROI;
	imageROI=srcImage4(Rect(200,250,logoImage.cols,logoImage.rows));
	//imageROI=srcImage4(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));

addWeighted(imageROI,0.5,logoImage,0.3,0.,imageROI);

namedWindow("区域线性图像混合窗口“);
imshow("区域线性图像混合窗口”,srcImage4);

return true;
}
#include
#include
#include

using namespace cv;
using namespace std;

bool ROI_AddImage();
bool LinearBlending();
bool ROI_LinearBlending();

int main()
{
	system("color 5E");
	if(ROI_AddImage()&&LinearBlending()&&ROI_LinearBlending())
	{
		cout<<endl<<"运行成功,得出了你需要的图像~!:)";

	}
	waitKey(0);
	return 0;
}

bool ROI_AddImage()
{
	Mat srcImage=imread("dota_pa.jpg");
	Mat logoImage=imread("dota_logo.jpg");
	
	if(!srcImage4.data)
	{
		printf("读取srcImage4错误~! \n");
		return false;
	}
	if(!logoImage.data)
	{
		printf("读取logoImage错误~! \n");
		return false;
	}

	Mat imageROI=srcImage(Rect(200,250,logoImage.cols,logoImage.rows));
	Mat mask=imread("dota_logo.jpg",0);
	logoImage.copyTo(imageROI,mask);

	nameWindow("<1>利用ROI实现图像叠加窗口");
	imshow("<1>利用ROI实现图像叠加窗口”,srcImage1);
	return true;
}

bool LinearBlending()
{
	double alphaValue=0.5;
	double betaValue;
	Mat srcImage2,srcImage3,dstImage;

	srcImage2=imread("mogu.jpg");
	srcImage3=imread("rain.jpg");
	
	if(!srcImage2.data)
	{
		printf("读取srcImage2错误~! \n");
		return false;
	}
	if(!srcImage3.data)
	{
		printf("读取srcImage3错误~! \n");
		return false;
	}

	betaValue=(1.0-alphaValue);
	addWeighted(srcImage2,alphaValue,srcImage3,betaValue,0.0,dstImage);
	namedWindow("<2>线性混合窗口【原图】“,1);
	imshow("<2>线性混合窗口【原图】”,srcImage2);

	namedWindow("<3>线性混合窗口【原图】“,1);
	imshow("<3>线性混合窗口【原图】”,dstImage);

	return true;
}

bool ROI_LinearBlending()
{
	Mat srcImage4=imread("dota_pa.jpg");
	Mat logoImage=imread("dota_logo.jpg");
	
	if(!srcImage4.data)
	{
		printf("读取srcImage4错误~! \n");
		return false;
	}
	if(!logoImage.data)
	{
		printf("读取logoImage错误~! \n");
		return false;
	}

	Mat imageROI;
	imageROI=srcImage4(Rect(200,250,logoImage.cols,logoImage.rows));
	//imageROI=srcImage4(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));

		addWeighted(imageROI,0.5,logoImage,0.3,0.,imageROI);
	namedWindow("<4>区域线性图像混合窗口");
	imshow("<4>区域线性图像混合窗口”,srcImage4);

	return true;
}

三、分离颜色通道、多通道图像混合

OpenCV的split和merge实现RGB三个颜色通道的分量进行分别显示和调整。

3.1.通道分离:split()函数

split函数用于将一个多通道数组分离成几个单通道数组。

void split(const Mat& src,Mat *mvbegin);
void split(InputArray m,OutputArrayOfArrays mw);

第一个参数,InputArray类型的m或者const Mat&类型的src,填我们需要进行分离的多通道数组。
第二个参数,OutputArrayOfArrays类型的mv,填函数的输出数组或者输出的vector容器。

Mat srcImage;
Mat imageROI;
vector<Mat> channels;
srcImage=cv::imread("dota.jpg");

split(srcImage,channels);
imageROI=channels.at(0);

addWeighted(imageROI(Rect(385,250,logoImage.cols,logoImage.rows)),1.0,logoImage,0.5,0.,imageROI(385,250,logoImage.cols,logoImage.rows)));

merge(channels,srcImage4);

namedWindow("sample");
imshow("sample",srcImage);

3.2.通道合并:merge()函数

merge()函数是split函数的逆行操作——将多个数组合并成一个多通道的数组。它通过组合一些给定的单通道数组,将这些孤立的单通道数组合并成一个多通道的数组,从而创建出一个由多个单通道阵列组成的多通道阵列。

void merge(const Mat*mv,size_tcount,OutputArray dst)
void merge(InputArrayOfArrays mv,OutputArray dst)

第一个参数,mv填需要被合并的输入矩阵或vector容器的阵列,这个mv参数中所有的矩阵必须有着一样的尺寸和深度。

第二个参数,count。当mv为一个空白的C数组时,代表输入矩阵的个数,这个参数显然必须大于1。
第三个参数,dst。即输出矩阵,和mv[0]拥有一样的尺寸和深度,并且通道的数量是矩阵阵列中的通道的总数。

merge函数的功能是将一些数组合并成一个多通道的数组。关于组合的细节,输出矩阵中的每个元素都将是输出数组的串接。其中,第i个输入数组的元素被视为mv[i]。C一般用其中的Mat::at()方法对某个通道进行存取,即:channels.at(0)。

Mat::at()方法返回一个引用到指定的数组元素。注意是引用,相当于两者等价,也就是修改其中一个,另一个也会随之改变。

vector<Mat> channels;
Mat imageBlueChannel;
Mat imageGreenChanel;
Mat imageRedChannel;
srcImage4=imread("dota.jpg");
split(srcImage4,channels);
imageBlueChannel=channels.at(0);
imageGreenChannel=channels.at(1);
imageRedChannel=channels.at(2);

把载入的3通道图像转换成3个单通道图像,放到vector类型的channels中,接着进行引用赋值。

3.3.多通道图像混合

#include
#include

using namespapce cv;
using namespace std;

bool ROI_AddImage();
bool LinearBlending();
bool ROI_LinearBlending();

int main()
{
	system("color 5E");
	if(ROI_AddImage()&&LinearBlending()&&ROI_LinearBlending())
	{
		cout<<endl<<"运行成功,得出了你需要的图像~:)";
	}
	waitKey(0);
	return 0;
}

bool ROI_AddImage()
{
	Mat srcImage1=imread("dota_pa.jpg");
	Mat logoImage=imread("dota_logo.jpg");

	if(!srcImage1.data)
	{
		printf("读取srcImage1错误~! \n");
		return false;
	}
	if(!logoImage.data)
	{
		printf("读取logoImage错误~! \n");
		return false;
	}

	Mat imageROI=srcImage(Rect(200,250,logoImage.cols,logoImage.rows));
	Mat mask=imread("dota_logo.jpg",0);
	logoImage.copyTo(imageROI,mask);
	namedWindow("<1>利用ROI实现图像叠加窗口“);
	imshow("<!>利用ROI实现图像叠加窗口”,srcImage1);

	return true;
}

bool Linearlending()
{
	double alphaValue=0.5;
	double betaValue;
	Mat srcImage2,srcImage3,dstImage;

	srcImage2=imread("mogu.jpg");
	srcImage3=imread("rain.jpg");

	if(!srcImage2.data)
	{
		printf("读取srcImage2错误~! \n");
		return false;
	}
	if(!srcImage3.data)
	{
		printf("读取srcImage3错误~! \n");
		return false;
	}

	betaValue=(1.0-alphaValue);
	addWeighted(srcImage2,alphaValue,srcImage3,betaImage3,betaValue,0.0,dstImage);
	namedWindow("<2>线性混合窗口【原图】",1);
	imshow("<2>线性混合窗口【原图】“,srcImage2);

	namedWindow("<3>线性混合窗口【效果图】",1);
	imshow("<3>线性混合窗口【效果图】”,dstImage);

	return true;
}

bool ROI_LinearBlending()
{
	Mat srcImage4=imread("dota_pa.jpg",1);
	Mat logoImage=imread("dota_logo.jpg");

	if(!srcImage4.data)
	{
		printf("读取srcImage4错误~! \n");
		return false;
	}
	if(!logoImage.data)
	{
		printf("读取logoImage错误~! \n");
		return false;
	}

	Mat imageROI;
	imageROI=srcImage4(Rect(200,250,logoImage.cols,logoImage.rows));
	//imageROI=srcImage4(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));
	addWeighted(imageROI,0.5,logoImage,0.3,0.,imageROI);

	namedWindow("<4>区域线性图像混合窗口“);
	imshow("<4>区域线性图像混合窗口”,srcImage4);
	return true;
}

四、图像对比度、亮度值调整

4.1.相关理论

一般的图像处理算子都是一个函数,它接受一个或多个输入图像,并产生输出图像。
图像亮度和对比度的调整操作,其实属于图像处理变换中比较简单的一种——点操作(pointoperators)。点操作有一个特点:仅仅根据输入像素值(有时可加上某些全局信息或参数),来计算相应的输出像素值。这类算子包括亮度(brightness)和对比度(contrast)调整、颜色校正(colorcorrection)和变换(transformations)。

两种最常用的点操作是乘上一个常数(对应对比度的调节)以及加上一个常熟(对应亮度值的调节)。

4.2.访问图像中的像素

对GBR图像进行运算,每个像素有三个值(G、B、R),所以我们必须分别访问(OpenCV中的图像存储模式为GBR)。

for(int y=0;y<image.rows;y++)
{
	for(int x=0;x<image.cols;x++)
	{
		for(int c=0;c<3;c++)
		{
			new_image.at<Vec3b>(y,x)[c]=saturate_cast<uchar>((g_nContrastValue*0.01)*(image.at<Vec3b>(y,x)[c])+g_nBrightValue);
			}
	}
}

使用image.at(y,x)[c]访问图像的每一个像素。
y是像素所在的行,x是像素所在的列,c是R、G、B(对应0,1,2)其中之一。

因为运算结果可能会超出像素取值范围(溢出),还可能是非整数(如果是浮点数的话),所以要用saturate_cast对结果进行转换,以确保它为有效值。
这里的a也是对比度,一般为了观察的效果,它的取值为0.0到3.0的浮点值,但是轨迹条一般取值都会取整数,因此在这里我们可以将其代表对比度值的nContrastValue参数设为0到300之间的整型在最后的式子中乘以一个0.01,这样就完成了轨迹条中300个不同取值的变化。

4.3.图像对比度、亮度值调整

#include
#include
#include

using namespace std;
using namespace cv;

static void on_ContrastAndBright(int,void*);
static void ShowHelpText();

int g_nContrastValue;
int g_nBrightValue;
Mat g_srcImage,g_dstImage;

int main()
{
	g_srcImage=imread("1.jpg");
	if(!g_srcImage.data)
	{
		printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~!“);
		return false;
	}

	g_nContrastValue=80;
	g_nBrightValue=80;

	namedWindow("【效果图窗口】",1);
	
	createTrackbar("对比度:","【效果图窗口】”,&g_nContrastValue,300,on_ContrastAndBright);
	createTrackbar("亮度:”,“【效果图窗口】”,&g_nBrightValue,200,on_ContrastAndBright(g_nBrightValue,0);

	while(char(waitKey(1)!='q')
	{
	}
	return 0;
}

static void on_ContrastAndBright(int,void*)
{
	namedWindow("【原始图窗口】“,1);
	for(int y=0;y<g_srcImage.rows;y++)
	{
		for(int x=0;x<g_srcImage.cols;x++)
		{
			for(int c=0;c<3;c++)
			{
				g_dstImage.at<Vec3b>(y,x)[c]=saturate_cast<uchar>((g_nContrastValue*0.01)*(g_srcImage.at<Vec3b>(y,x)[c])+g_nBrightValue);
			}
		}
	}

	imshow("【原始图窗口】",g_srcImage);
	imshow("【效果图窗口】",g_dstImage);
}

五、离散傅里叶变换

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

5.1.离散傅里叶变换的原理

对一张图像使用傅里叶变换就是将它分解成正弦和余弦两部分,也就是将图像从空间域(spatial domain)转换到频域(frequency domain).

理论基础:任一函数都可以表示成无数个正弦和余弦函数的和的形式。傅里叶变换就是一个用来将函数分解的工具。

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

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

5.2.dft()函数详解

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

void dft(InputArray src,OutputArray dst,int flags=0,int nonzeroRows=0)

第一个参数,InputArray类型的src。输出矩阵,可以为实数或者虚数。
第二个参数,OutputArray类型的dst。函数调用后的运算结果存在这里,其尺寸和类型取决于标识符,也就是第三个参数flags。
第三个参数,int类型的flags。
第四个参数,int类型的nonzeroRows,有默认值0。当此参数设为非零时(最好是取值为想要处理的那一行的值,比如C.rows),函数会假设只要输入矩阵的第一个非零行包含非零元素(没有设置DFT_INVERSE标识符),或只有输出矩阵的第一个非零行包含非零元素(设置了DFT_INVERSE标识符)。这样的话,函数就可对其他行进行更高效的处理,以节省时间开销。

void convolveDFT(InputArray A,InputArray B,OutputArray C)
{
	C.create(abs(A.rows-B.rows)+1,abs(A.cols-B.rows)+1,A.type());
	Size dftSize;

	dftSize.width=getOptimalDFTSize(A.cols+B.cols-1);
	dftSize.height=getOptimalDFTSize(A.rows+B.rows-1);

	Mat tempA(dftSize,A.type(),Scalar::all(0));
	Mat tempB(dftSize,B.type(),Scalar::all(0);

	Mat roiA(tempA,Rect(0,0,A.cols,A.rows);
	A.copyTo(roiA);
	Mat roiB(tempB,Rect(0,0,B.cols,B.rows));
	B.copyTo(roiB);

	dft(tempA,tempA,0,A.rows);
	dft(tempB,tempB,0,B.rows);

	mulSpectrums(tempA,tempB,tempA);

	dft(tempA,tempA,DFT_INVERSE+DFT_SCALE,C.rows);

	tempA(Rect(0,0,C.cols,C.rows)).copyTo(C);

}

5.3.返回DFT最有尺寸大小:getOptimalDFTSize()函数

getOptimalDFTSize()函数返回给定向量尺寸的傅里叶最优尺寸大小。为了提高离散傅里叶变换的运行速度,需要扩充图像,而具体扩充多少,就由这个函数来计算得到。

int getOptimalDFTSize(int vecsize)

此函数的唯一一个参数为int类型的vecsize,向量尺寸,即图片的rows、cols。

5.4.扩充图像边界:copyMakeBorder()函数

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

第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和源图片有一样的尺寸和类型,且size应该为Size(src.cols+left+right,src.rows+top+bottom)。
int类型的top、bottom、left、right,分别表示在源图像的四个方向上扩充多少像素。
第七个参数,borderType类型的,边界类型,常见取值为BORDER_CONSTANT。
第八个参数,const Scalar&类型的value,有默认值Scalar(),可以理解为默认值为0。当borderType取值为BORDER_CONSTANT时,这个参数表示边界值。

5.5.计算二维矢量的幅值:magnitude()函数

void magnitude(InputArray x,InputArray y,OutputArray magnitude)

第一个参数,InputArray类型的x,表示矢量的浮点型X坐标值,也就是实部。
第二个参数,InputArray类型的y,表示矢量的浮点型Y坐标值,也就是虚部。
第三个参数,OutputArray类型的magnitude,输出的幅值,它和第一个参数x有着同样的尺寸和类型。

5.6.计算自然对数:log()函数

log()函数的功能是计算每个数组元素绝对值的自然对数。

void log(InputArray src,OutputArray dst)

第一个参数为输入图像,第二个参数为得到的对数值。

5.7.矩阵归一化:normalize()函数

normalize()的作用是进行矩阵归一化。

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。输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst。函数调用后的运算结果存在这里,和源图片有一样的尺寸和类型。
第三个参数,double类型的alpha。归一化后的最大值,有默认值1。
第四个参数,double类型的beta。归一化后的最大值,有默认值0。
第五个参数,int类型的norm_type。归一化类型,有NORM_INF、NORM_L1、NORM_L2和NORM_MINMAX等参数可选,有默认值NORM_L2。
第六个参数,int类型的dtype,有默认值-1。当此参数取负值时,输出矩阵和src有同样的类型,否则,它和src有同样的通道数,且此时图像深度为CV_MAT_DEPTH(dtype)。
第七个参数,InputArray类型的mask,可选的操作掩膜,有默认值noArray()。

5.8.离散傅里叶变换

【1】载入原始图像

Mat srcImage=imread("1.jpg",0);
if(!srcImage.data)
{
	printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~!\n");
	return false;
}
imshow("原始图像”,srcImage);

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

int m=getOptimalDFTSize(I.rows);
int n=getOptimalDFTSize(I.cols);

Mat padded;
copyMakeBorder(I,padded,0,m-I.rows,0,m-I.cols,BORDER_CONSTANT,Scalar::all(0));

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

Mat planes[]={Mat_<float>(padded),Mat::zeros(padded.size(),CV_32F));
Mat complexI;
merge(planes,2,complexI);

【4】进行离散傅里叶变换

dft(complexI,complexI);

【5】将复数转换为幅值
复数包含实数部分(Re)和虚数部分(imaginary-Im)。离散傅里叶变换的结果是复数。

split(complexI,planes);
planes[0]=Re(DFT(I),planes[1]=Im(DFT(I))
magnitude(planes[0],planes[1],planes[0]);
//planes[0]=magnitude
Mat magnitudeImage=planes[0];

【6】进行对数尺度(logarithmic scale)缩放
傅里叶变换的幅度值范围大到不适合的屏幕上显示。高值在屏幕上显示为白点,而低值为黑点,高低值得变化无法有效分辨。为了在屏幕上凸显出高低变化的连续性,我们可以用对数尺度来替换线性尺度。

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));
Mat q1(magnitudeImage,Rect(cx,0,cx,cy));
Mat q2(magnitudeImage,Rect(0,cy,cx,cy));
Mat q3(magnitudeImage,Rect(cx,cy,cx,cy));

Mat tmp;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);

q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);

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

normalize(magnitudeImage,magnitudeImage,0,1,NORM_MINMAX);

【9】显示效果图

imshow("频谱幅值“,magnitudeImage);
#include
#include
#include
#include

using namespace cv;

int main()
{
	Mat srcImage=imread("1.jpg",0);
	if(!srcImage.data)
	{
		printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~!\n");
		return false;
	}
	imshow("原始图像”,srcImage);

	ShowHelpText();

	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));

	Mat planes[]={Mat_<float>(padded),Mat::zeros(padded.size(),CV_32F));
	Mat complexI;
	merge(planes,2,complexI);

	dft(complexI,complexI);

	split(complexI,planes);
	planes[0]=Re(DFT(I),planes[1]=Im(DFT(I))
	magnitude(planes[0],planes[1],planes[0]);
	//planes[0]=magnitude
	Mat magnitudeImage=planes[0];

	magnitudeImage+=Scalar::all(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));
	Mat q1(magnitudeImage,Rect(cx,0,cx,cy));
	Mat q2(magnitudeImage,Rect(0,cy,cx,cy));
	Mat q3(magnitudeImage,Rect(cx,cy,cx,cy));

	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);

	imshow("频谱幅值“,magnitudeImage);

	waitKey();
	return 0;
}

六、输入输出XML和YAML文件

6.1.XML和YAML文件简介

所谓XML,即eXtensible Markup Language,翻译成中文为“可扩展标识语言”。首先,XML是一种元标记语言。所谓“元标记”,就是开发者可以根据自身需要定义自身的标记,比如可以定义标记、。任何满足XML命名规则的名称都可以标记,这就向不同的应用程序打开了的大门。此外,XML是一种语义/结构化语言,它描述了文档的结构和语义。

YAML是“YAML Ain’t a Markup Language”(译为“YAML不是一种置标语言”)的递回缩写。在开发的这种语言时,YAML的原意是:“Yet Another Markup Language"(仍是一种置标语言),但为了强调这种语言以数据为中心,而不是以置标语言为重点,而用返璞词进行重新命名。YAML是一个可读性高,用来表达资料序列的格式。它参考了其他多种语言,包括:XML、C语言、Python、Perl,以及电子邮件格式RFC2822。

YAML试图用一种比XML更敏捷的方式,来完成XML所完成的任务。

6.2.FileStorage类操作文件的使用引导

XML和YAML是使用非常广泛的文件格式,可以利用XML或者YAML格式的文件存储和还原各式各样的数据结构与。当然,他们还可以存储和载入任意复杂的数据结构,其中就包括了OpenCV相关周边的数据结构,以及各种原始数据类型,如整数和浮点数字和文本字符串。

一般使用如下过程来写入或者读取数据到XML或YAML文件中。
(1)实例化一个FileStorage类的对象,用默认带参数的构造函数完成初始化,或者用FileStorage::open()成员函数辅助初始化。
(2)使用流操作符<<进行文件写入操作,或者>>进行文件读取操作,类似C+中的文件输入输出流。
(3)使用FileStorage::release()函数析构掉FileStorage类对象,同时关闭文件。

【1】XML、YAML文件的打开
1)准备文件写操作
FileStorage是OpenCV中XML和YAML文件的存储类,封装了所有相关的信息。它是OpenCV从文件中读数据或向文件中写数据时必须要使用的一个类。

FileStorage::FileStorage()

FileStorage::FileStorsge(const string& source,int flags,const string& encoding=string())

对于第二种带参数的构造函数:

FileStorage fs("abc.xml",FileStorage::WRITE);

对于第一种不带参数的构造函数,可以使用其成员函数FileStorage::open进行数据的写操作:

FileStorage fs;
fs.open("abc.xml",FileStorage::WRITE);

2)准备文件读操作

FileStorage fs("abc.xml",FileStorage::READ);

FileStorage fs;
fs.open("abc.xml",FileStorage::READ);

【2】进行文件读写操作
1)文本和数字的输入和输出
定义好FileStorage类对象之后,写入文件可以使用”<<"运算符

fs<<"iterationNr"<<100;

读取文件使用”>>"运算符:

int itNr;
fs["iterationNr"]>>itNr;
itNr=(int)fs["iterationNr"];

2)OpenCV数据结构的输入和输出

Mat R=Mat_<uchar>::eye(3,3),
Mat T=Mat_<double>::zero(3,1);

//向Mat中写入数据
fs<<"R"<<R;
FS<<"T"<<T;
//从Mat中读取数据
fs["R"]>>R;
fs["T"]>>T;

【3】vector(arrays)和maps的输入和输出
对于vector结构的输入和输出,要注意在第一个元素前加上“[",在最后一个元素前加上"]"。

fs<<"strings"<<"[";
fs<<"image1.jpg"<<"Awesomeness"<<"baboon.jpg";
fs<<"]";

而对于map结构的操作,使用的符合是“{”和“}”

fs<<"Mapping";
fs<<"{"<<"one"<<1;
fs<<"Two"<<2<<"}";

读取这些结构的时候,会用到FileNode和FileNodeIterator数据结构。对FileStorage类的”["、"]"操作符会返回FileNode数据类型:对于一连串的node,可以使用FileNodeIterator结构

FileNode n=fs["strings"];
if(n.type()=FileNode::SEQ)
{
	cerr<<"发生错误!字符串不是一个序列!"<<endl;
	return 1;
}

FileNodeIterator it=n.begin(),it_end=n.end();
for(;it!=it_end;++it)
cout<<(string)*it<<endl;

【4】文件关闭
文件关闭操作会在FileStorage类销毁时自动进行,但我们也可显式调用其析构函数FileStorage::release()实现。FileStorage::release()函数会析构掉FileStorage类对象,同时关闭文件。

fs.release();

6.3.XML和YAML文件的写入程序

#include
#include

using namespace cv;

int main()
{
	FileStorage fs("test.yaml",FileStorage::WRITE);

	fs<<"frameCount"<<5;
	time_t rawtime;
	time(&rawtime);
	fs<<"calibrationDate"<<asctime(localtime(&rawtime));
	Mat cameraMatrix=(Mat_<double>(3,3)<<1000,0,320,0,1000,240,0,0,1);
	Mat disCoeffs=(Mat_<double>(5,1)<<0.1,0.01,-0.001,0,0);
	fs<<"cameraMatrix"<<cameraMatrix<<"distCoeffs"<<distCoeffs;
	fs<<"features"<<"[";
	for(int i=0;i<3;i++)
	{
		int x=rand()%640;
		int y=rand()%480;
		uchar lbp=rand()%256;

		fs<<"{:"<<"x"<<x<<"y"<<y<<"lbp"<<"[:";
		for(int j=0;j<8;j++)
			fs<<((lbp>>j)&1);
		fs<<"]"<<"}";
	}
	fs<<"]";
	fs.release();

	printf("文件读写完毕,请在工程目录下查看生成的文件~“);
	getchar();

	return 0;
}

6.4.XML和YAML文件的读取程序

#include
#include

using namspace cv;
using namspace std;

int main()
{
	system("color 6F");
	
	FileStorage fs2("test.yaml",FileStorage::READ);
	
	int frameCount=(int)fs2["frameCount"];
	
	std::string date;
	fs2["calibrationDate"]>>date;
	
	Mat cameraMatrix2,distCoeffs2;
	fs2["cameraMatrix"]>>cameraMatrix2;
	fs2["distCoeffs"]>>distCoeffs2;

	cout<<"frameCount:"<<frameCount<<endl
	<<"calibration date:"<<date<<endl
	<<"camera matrix:"<<cameraMatrix2<<endl
	<<"distortion coeffs:"<<distCoeffs2<<endl;

	FlieNode features=fs2["features"];
	FileNodeIterator it=features.begin(),it_end=features.end();
	int idx=0;
	std::vector<uchar>lbpval;

	for(;it!=it_end;++it,idx++)
	{
		cout<<"feature#"<<idx<<":";
		cout<<"x="<<(int)(*it)["x"]<<",y="<<(int)(*it)["y"]<<",lbp:(";
		(*it)["lbp"]>>lbpval;
		for(int i=0;i<(int)lbpval.size();i++)
			cout<<" "<<(int)lbpval[i];

		cout<<")"<<endl;
	}
	fs2.release();
	printf("\n文件读取完毕,请输入任意键结束程序~”);
	getchar();

	return 0;
}

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