您的位置:首页 > 运维架构

【第二部分-图像处理】第2章 Opencv图像处理初阶(core组件)

2017-11-01 21:02 701 查看

2.1图像混合

2.1.1设定感兴趣区域——ROI(region of interest)

在图像处理领域,我们常常需要设置感兴趣区域(ROI,region of interest),来专注或者简化我们的工作过程 。也就是从图像中选择的一个图像区域,这个区域是我们图像分析所关注的重点。我们圈定这个区域,以便进行进一步处理。而且,使用ROI指定我们想读入的目标,可以减少处理时间,增加精度,给图像处理来带不小的便利。

 ROI区域定义的两种方法

定义ROI区域有两种方法,第一种是使用cv:Rect.顾名思义,cv::Rect表示一个矩形区域。指定矩形的左上角坐标(构造函数的前两个参数)和矩形的长宽(构造函数的后两个参数)就可以定义一个矩形区域。

//定义一个Mat类型并给其设定ROI区域
Mat imageROI;
//方法一
imageROI=image(Rect(500,250,logo.cols,logo.rows));


另一种定义ROI的方式是指定感兴趣行或列的范围(Range)。Range是指从起始索引到终止索引(不包括终止索引)的一连段连续序列。cv::Range可以用来定义Range。如果使用cv::Range来定义ROI,那么前例中定义ROI的代码可以重写为:

//方法二
imageROI=srcImage3(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));


好了,下面我们来看一个实例,显示如何利用ROI将一幅图加到另一幅图的指定位置。大家如果需要拷贝如下的函数中的代码直接运行的话,自己建一个基于console的程序,然后把函数体中的内容拷贝到main函数中,然后找两幅大小合适的图片,加入到工程目录下,并和代码中读取的文件名一致即可。

在下面的代码中,我们通过一个图像掩膜(mask),直接将插入处的像素设置为logo图像的像素值,这样效果会很赞很逼真。

/**
* @brief     线性混合实现函数,指定区域线性图像混合.利用addWeighted()函数结合定义
*            感兴趣区域ROI,实现自定义区域的线性混合
* @param     None
* @retval    bool
*/

{
// 【1】读入图像
Mat srcImage1= imread("pa.jpg");
Mat logoImage= imread("logo.jpg");
if( !srcImage1.data ) { printf("读取srcImage1错误~! \n"); return false; }
if( !logoImage.data ) { printf("读取logoImage错误~! \n"); return false; }
// 【2】定义一个Mat类型并给其设定ROI区域
Mat imageROI= srcImage1(Rect(200,250,logoImage.cols,logoImage.rows));
// 【3】加载掩模(必须是灰度图)
Mat mask= imread("logo.jpg",0);
// 【4】将掩膜拷贝到ROI
logoImage.copyTo(imageROI,mask);
// 【5】将一个Mat图像输出到图像文件
imwrite("srcImage1.jpg",srcImage1);
// 【6】显示结果
namedWindow("<1>利用ROI实现图像叠加示例窗口");
imshow("<1>利用ROI实现图像叠加示例窗口",srcImage1);
return true;
}


这个函数首先是载入了两张jpg图片到srcImage1和logoImage中,然后定义了一个Mat类型的imageROI,并使用cv::Rect设置其感兴趣区域为srcImage1中的一块区域,将imageROI和srcImage1关联起来。接着定义了一个Mat类型的的mask并读入dota_logo.jpg,顺势使用Mat:: copyTo把mask中的内容拷贝到imageROI中,于是就得到了最终的效果图,namedWindow和imshow配合使用,显示出最终的结果。

运行结果如下。



图1

这里白色的logo,就是通过操作之后加上去的图像。

2.1.2初级图像混合——线性混合操作

线性混合操作是一种典型的二元(两个输入)的像素操作,它的理论公式是这样的:

g(x)=(1−α)f0(x)+αf1(x)

我们通过在范围0到1之间改变alpha值,来对两幅图像(f0(x)和f1(x))或两段视频(同样为(f0(x)和f1(x))产生时间上的画面叠化(cross-dissolve)效果,就像幻灯片放映和电影制作中的那样。即在幻灯片翻页时设置的前后页缓慢过渡叠加效果,以及电影情节过渡时经常出现的画面叠加效果。

实现方面,我们主要运用了OpenCV中addWeighted函数,我们来全面的了解一下它:

 addWeighted函数

这个函数的作用是,计算两个数组(图像阵列)的加权和。

C++: void addWeighted(InputArray src1,
double alpha,
InputArray src2,
double beta,
double gamma,
OutputArray dst,
int dtype=-1)


【参数】

第一个参数,InputArray类型的src1,表示需要加权的第一个数组,常常填一个Mat。

第二个参数,alpha,表示第一个数组的权重

第三个参数,src2,表示第二个数组,它需要和第一个数组拥有相同的尺寸和通道数。

第四个参数,beta,表示第二个数组的权重值。

第五个参数,gamma,一个加到权重总和上的标量值。看下面的式子自然会理解。

第六个参数,dst,输出的数组,它和输入的两个数组拥有相同的尺寸和通道数。

第七个参数,dtype,输出阵列的可选深度,有默认值-1。;当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。

如果用数学公式来表达,addWeighted函数计算如下两个数组(src1和src2)的加权和,得到结果输出给第四个参数。即addWeighted函数的作用可以被表示为为如下的矩阵表达式为:

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

其中的I,是多维数组元素的索引值。而且,在遇到多通道数组的时候,每个通道都需要独立地进行处理。另外需要注意的是,当输出数组的深度为CV_32S时,这个函数就不适用了,这时候就会内存溢出或者算出的结果压根不对。理论和函数的讲解就是上面这些,接着我们来看代码实例,以融会贯通。

//---------------------------------【LinearBlending()函数】--------------------
// 函数名:LinearBlending()
// 描述:利用cv::addWeighted()函数实现图像线性混合
//--------------------------------------------------------------------------------------------
bool  LinearBlending()
{
//【0】定义一些局部变量
double alphaValue = 0.5;
double betaValue;
Mat srcImage2, srcImage3, dstImage;
// 【1】读取图像 ( 两幅图片需为同样的类型和尺寸 )
srcImage2 = imread("mogu.jpg");
srcImage3 = imread("rain.jpg");
if( !srcImage2.data ) { printf("读取srcImage2错误~! \n"); return false; }
if( !srcImage3.data ) { printf("读取srcImage3错误~! \n"); return false; }
// 【2】进行图像混合加权操作
betaValue = ( 1.0 - alphaValue );
addWeighted( srcImage2, alphaValue, srcImage3, betaValue, 0.0, dstImage);
// 【3】创建并显示原图窗口
namedWindow("<2>线性混合示例窗口【原图】 ", 1);
imshow( "<2>线性混合示例窗口【原图】 ", srcImage2 );
// 【4】将一个Mat图像输出到图像文件
imwrite("dstImage.jpg",dstImage);
// 【5】显示结果
namedWindow("<3>线性混合示例窗口【效果图】", 1);
imshow( "<3>线性混合示例窗口【效果图】", dstImage );
return true;
}


【代码详解】

<0>首先当然是定义一些局部变量,alpha值beta值,三个Mat类型的变量。

//【0】定义一些局部变量

double alphaValue = 0.5;

double betaValue;

Mat srcImage2, srcImage3, dstImage;

在这里我们设置alpha值为0.5。

<1>读取两幅图像并作错误处理

这步很简单,直接上代码:

//读取图像 ( 两幅图片需为同样的类型和尺寸 )
srcImage2= imread("mogu.jpg");
srcImage3= imread("rain.jpg");
if(!srcImage2.data ) { printf("读取srcImage2错误~! \n"); return false; }
if(!srcImage3.data ) { printf("读取srcImage3错误~! \n"); return false; }


在这里需要注意的是,因为我们是对 srcImage1和srcImage2求和,所以它们必须要有相同的尺寸(宽度和高度)和类型,不然多余的部分没有对应的“伴”,肯定会出问题。

<2> 进行图像混合加权操作

载入图像后,我们就可以来生成混合图像,也就是之前公式中的g(x)。为此目的,使用函数 addWeighted 可以很方便地实现,也就是因为 addWeighted 进行了如下计算:

这里的对应于addWeighted的第2个参数alpha

这里的对应于addWeighted的第4个参数beta

这里的对应于addWeighted的第5个参数,在上面代码中被我们设为0.0。

代码其实很简单,就是这样:

//【2】进行图像混合加权操作
betaValue = ( 1.0 - alphaValue );
addWeighted( srcImage2, alphaValue, srcImage3,betaValue, 0.0, dstImage);


其中beta值为1-alpha,gamma值为0。

<3>创建显示窗口,显示图像。

// 【3】创建并显示原图窗口
namedWindow("<2>线性混合示例窗口【原图】 ", 1);
imshow( "<2>线性混合示例窗口【原图】 ", srcImage2 );
// 【4】将一个Mat图像输出到图像文件
imwrite("dstImage.jpg",dstImage);
// 【5】显示结果
namedWindow("<3>线性混合示例窗口【效果图】", 1);
imshow( "<3>线性混合示例窗口【效果图】", dstImage );


接着来看一下运行效果图。




图2

2.1.3综合示例

在前面分别介绍的设定感兴趣区域ROI和使用addWeighted函数进行图像线性混合的基础上,我们还将他们两者中和起来使用,也就是先指定ROI,并用addWeighted函数对我们指定的ROI区域的图像进行混合操作,我们将其封装在了一个名为ROI_LinearBlending的函数中,方便大家分块学习。

//---------------------------------【ROI_LinearBlending()】-------------------------------------
// 函数名:ROI_LinearBlending()
// 描述:线性混合实现函数,指定区域线性图像混合.利用cv::addWeighted()函数结合定义
//            感兴趣区域ROI,实现自定义区域的线性混合
//--------------------------------------------------------------------------------------------
bool  ROI_LinearBlending()
{
//【1】读取图像
Mat srcImage4= imread("pa.jpg",1);
Mat logoImage= imread("logo.jpg");
if( !srcImage4.data ) { printf("读取srcImage4错误~! \n"); return false; }
if( !logoImage.data ) { printf("读取logoImage错误~! \n"); return false; }
//【2】定义一个Mat类型并给其设定ROI区域
Mat imageROI;
//方法一
imageROI= srcImage4(Rect(200,250,logoImage.cols,logoImage.rows));
//方法二
//imageROI= srcImage4(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));
//【3】将logo加到原图上
addWeighted(imageROI,0.5,logoImage,0.3,0.,imageROI);

//【4】将一个Mat图像输出到图像文件
imwrite("srcImage4.jpg",srcImage4);
//【5】显示结果
namedWindow("<4>区域线性图像混合示例窗口");
imshow("<4>区域线性图像混合示例窗口",srcImage4);
return true;
}


从这篇文章开始,如果不出意外的话,为了方便大家分块各个击破学习,每讲一个部分,示例代码都将封装在一个函数中,免得大家像学习各种不是特别地道的OpenCV教程时一样,看到代码全放在main函数中,心都碎了。具体代码【参看附件demo1】。

2.2图像分离

2.2.1分离颜色通道

就让我们来详细介绍一下这两个互为冤家的函数。首先是进行通道分离的split函数。

<1>split函数详解

将一个多通道数组分离成几个单通道数组。ps:这里的array按语境译为数组或者阵列。

这个split函数的C++版本有两个原型,他们分别是:

C++: void split(const Mat& src,
Mat*mvbegin);
C++: void split(InputArray m,
OutputArrayOfArrays mv);


【参数】

第一个参数,InputArray类型的m或者const Mat&类型的src,填我们需要进行分离的多通道数组。

第二个参数,OutputArrayOfArrays类型的mv,填函数的输出数组或者输出的vector容器。

split函数分割多通道数组转换成独立的单通道数组,按公式来看就是这样:

mv[c](I)=src(I)c

最后看一个示例吧:

Mat srcImage;
Mat imageROI;
vector<Mat> channels;
srcImage= cv::imread("dota.jpg");
// 把一个3通道图像转换成3个单通道图像
split(srcImage,channels);//分离色彩通道
imageROI=channels.at(0);
addWeighted(imageROI(Rect(385,250,logoImage.cols,logoImage.rows)),1.0,
logoImage,0.5,0.,imageROI(Rect(385,250,logoImage.cols,logoImage.rows)));

merge(channels,srcImage4);

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


将一个多通道数组分离成几个单通道数组的split()函数的内容大概就是这些了,下面我们来看一下和他亲如手足或者说是他的死对头——merge()函数。

<2>merge函数详解

merge()函数的功能是split()函数的逆向操作,将多个数组组合合并成一个多通道的数组。它通过组合一些给定的单通道数组,将这些孤立的单通道数组合并成一个多通道的数组,从而创建出一个由多个单通道阵列组成的多通道阵列。它有两个基于C++的函数原型:

C++: void merge(const Mat* mv,
size_tcount,
OutputArray dst)
C++: 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)。

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

【示例】

vector<Mat> channels;
Mat imageBlueChannel;
Mat imageGreenChannel;
Mat imageRedChannel;
srcImage4= imread("dota.jpg");
// 把一个3通道图像转换成3个单通道图像
split(srcImage4,channels);//分离色彩通道
imageBlueChannel = channels.at(0);
imageGreenChannel = channels.at(1);
imageRedChannel = channels.at(2);


上面的代码先做了相关的类型声明,然后把载入的3通道图像转换成3个单通道图像,放到vector类型的channels中,接着进行引用赋值。

根据OpenCV的BGR色彩空间(bule,Green,Red,蓝绿红),其中channels.at(0)就表示引用取出channels中的蓝色分量,channels.at(1)就表示引用取出channels中的绿色色分量,channels.at(2)就表示引用取出channels中的红色分量。

一对做相反操作的plit()函数和merge()函数和用法就是这些了。另外提一点,如果我们需要从多通道数组中提取出特定的单通道数组,或者说实现一些复杂的通道组合,可以使用mixChannels()函数。

2.2.2多通道图像混合示例程序

我们把多通道图像混合的实现代码封装在了名为MultiChannelBlending()的函数中。直接上代码吧。

【代码-参看附件demo2】。

可以发现,其实多通道混合的实现函数中的代码大体分成三部分,分别对蓝绿红三个通道进行处理,唯一不同的地方是在取通道分量时取的是channels.at(0),channels.at(1)还是channels.at(2)。

嗯,下面看一下运行截图。



图3



图4



图5

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

2.3.1亮度和对比度调整的理论依据

首先我们给出算子的概念。一般的图像处理算子都是一个函数,它接受一个或多个输入图像,并产生输出图像。下式给出了算子的一般形式:

g(x)=h(f0(x)......fn(x))

今天我们所讲解的图像亮度和对比度的调整操作,其实属于图像处理变换中比较简单的一种——点操作(pointoperators)。点操作有一个特点,仅仅根据输入像素值(有时可加上某些全局信息或参数),来计算相应的输出像素值。这类算子包括亮度(brightness)和对比度(contrast)调整,以及颜色校正(colorcorrection)和变换(transformations)。

最两种常用的点操作(或者说点算子),很显然,是乘上一个常数(对应对比度的调节)以及加上一个常数(对应亮度值的调节)。用公式表示出来就是这样:

g(x)=αf(x)+β

看到这个式子,我们关于图像亮度和对比度调整的策略就呼之欲出了。

其中:

参数f(x)表示源图像像素。

参数g(x) 表示输出图像像素。

参数a(需要满足a>0)被称为增益(gain),常常被用来控制图像的对比度。

参数b通常被称为偏置(bias),常常被用来控制图像的亮度。

而更近一步,我们这样改写这个式子:

g(x,j)=α⋅f(x,j)+β

其中,i 和 j 表示像素位于第i行 和 第j列 。

那么,这个式子就可以用来作为我们在OpenCV中控制图像的亮度和对比度的理论公式了。

2.3.2图像对比度、亮度值调整示例程序

这个示例程序用两个轨迹条分别控制对比度和亮度值,有一定的可玩性。废话不多说,上代码吧。

【代码-参看附件demo3】。

最后看一下运行截图,运行这个程序会得到两个图片显示窗口。第一个为原图窗口,第二个为效果图窗口。在效果图窗口中可以调节两个轨迹条,来改变当前图片的对比度和亮度。



图6

2.4图像的旋转、翻转、反转

2.4.1图像的旋转

旋转一般是指将图像围绕某一指定点旋转一定的角度,图像旋转后会有一部分图像转出显示区域,可以截图那部分,也可以改变图像的尺寸使得图像显示完全。在后文的反射变换会具体讲解。

2.4.2图像的翻转

OpenCV提供了最简单的图像翻转函数- flip()函数,可以在垂直、水平或两个轴上翻转二维数组。


 图像旋转:flip()函数

C++: void flip(InputArray src,
OutputArray dst,
int flipCode)


【参数】

第一个参数,src – 输入的矩阵;

第二个参数,dst – 输出与输入相同尺寸和类型的矩阵。

第三个参数,flipCode – 指定旋转的标志; 0意味着翻转x轴,正值(例如,1)意味着翻转y轴。负值(例如,- 1)意味着在两个轴上翻转(参见下面的讨论公式)。数组函数翻转翻转的三种不同的方式(行和列索引是基于0):

dstij=⎧⎩⎨⎪⎪srcsrc.rows−i−1,jsrci,src.cols−j−1srcsrc.rows−i−1,src.cols−j−1if flipCode = 0if flipCode > 0if flipCode < 0

接下来直接看代码吧。

代码参看附件【demo4】



图7水平翻转



图8垂直翻转



图9水平垂直翻转

2.4.3图像的反转

图像的操作也就是矩阵的操作,对于一个单通道的像素点,对其反转也就等同于二进制的0、1操作,OpenCV提供了以下API:bitwise_not,bitwise_xor,bitwise_or,bitwise_and在这里笔者带领大家先看看bitwise_not的使用,其余的请对着自行学习。

 图像反转:bitwise_not()函数

C++: void bitwise_not(InputArray src,
OutputArray dst,
InputArray mask=noArray())


【参数】

第一个参数,src – 输入的图像。

第二个参数,dst – 输出与输入相同大小和类型的图像。

第三个参数,mask – 可选的操作掩膜, 8-bit single channel array, that specifies elements of the output array to be changed.

函数计算每个位操作反转输入数组:



看看代码吧。代码参看附件【demo5】。



图10

2.5傅里叶变换

对一张图像使用傅立叶变换就是将它分解成正弦和余弦两部分。也就是将图像从空间域(spatial domain)转换到频域(frequency domain)。这一转换的理论基础来自于以下事实:任一函数都可以表示成无数个正弦和余弦函数的和的形式。傅立叶变换就是一个用来将函数分解的工具。 2维图像的傅立叶变换可以用以下数学公式表达:



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

在此示例中,我将展示如何计算以及显示傅立叶变换后的幅度图像。由于数字图像的离散性,像素值的取值范围也是有限的。比如在一张灰度图像中,像素灰度值一般在0到255之间。 因此,我们这里讨论的也仅仅是离散傅立叶变换(DFT)。 如果你需要得到图像中的几何结构信息,那你就要用到它了。

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

 傅里叶变换:dft()函数

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


【参数】

第一个参数,src:输入图像;

第二个参数,dst:输出图像;

第三个参数,flags –Transformation flags representing a combination of the following values:

DFT_INVERSE performs an inverse 1D or 2D transform instead of the default forward transform.

DFT_SCALE scales the result: divide it by the number of array elements. Normally, it is combined with DFT_INVERSE .

DFT_ROWS performs a forward or inverse transform of every individual row of the input matrix. This flag enables you to transform multiple vectors simultaneously and can be used to decrease the overhead (which is sometimes several times larger than the processing itself) to perform 3D and higher-dimensional transforms and so forth.

DFT_COMPLEX_OUTPUT performs a forward transformation of 1D or 2D real array. The result, though being a complex array, has complex-conjugate symmetry (CCS, see the function description below for details). Such an array can be packed into a real array of the same size as input, which is the fastest option and which is what the function does by default. However, you may wish to get a full complex array (for simpler spectrum analysis, and so on). Pass the flag to enable the function to produce a full-size complex output array.

DFT_REAL_OUTPUT performs an inverse transformation of a 1D or 2D complex array. The result is normally a complex array of the same size. However, if the source array has conjugate-complex symmetry (for example, it is a result of forward transformation with DFT_COMPLEX_OUTPUT flag), the output is a real array. While the function itself does not check whether the input is symmetrical or not, you can pass the flag and then the function will assume the symmetry and produce the real output array. Note that when the input is packed into a real array and inverse transformation is executed, the function treats the input as a packed complex-conjugate symmetrical array. So, the output will also be a real array.

第四个参数,nonzeroRows – When the parameter is not zero, the function assumes that only the first nonzeroRows rows of the input array ( DFT_INVERSE is not set) or only the first nonzeroRows of the output array ( DFT_INVERSE is set) contain non-zeros. Thus, the function can handle the rest of the rows more efficiently and save some time. This technique is very useful for computing array cross-correlation or convolution using DFT.

关于dft的介绍在帮助文档中有很详细的介绍,在这里就不讲解了。

http://docs.opencv.org/2.4/modules/core/doc/operations_on_arrays.html#dft

 返回最佳的尺寸大小: getOptimalDFTSize()函数

【功能】返回给定向量尺寸的傅里叶最优尺寸大小,意思就是为了提高离散傅立叶变换的运行速度,需要扩充图像,需要扩充多少,就由这个函数计算得到。

C++: int getOptimalDFTSize(int vecsize)


【参数】

vecsize:向量尺寸,即图片的rows,cols。

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

C++: 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);

第三、四、五、六个参数top 、bottom、left 、right : 这四个参数风别表示在源图像的四个方向上分别扩充多少像素,例如top=1, bottom=1, left=1, right=1 意味着在源图像的上下左右各扩充一个像素宽度的边界。

第七个参数borderType – 边界类型;

第八个参数value – 边界值,如果borderType==BORDER_CONSTANT 。

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

C++: void magnitude(InputArray x,
InputArray y,
OutputArray magnitude)


【参数】

第一个参数,x:实部

第二个参数,y:虚部,size和x一样

第三个参数,magnitude:输出图像,和x有同样的size和type

【原理】



 计算对数:log()函数

C++: void log(InputArray src,
OutputArray dst)


【参数】

第一个参数,src:输入图像

第二个参数,dst:输出图像

【原理】



C是一个很大的负数。

 矩阵归一化:normalize()函数

C++: void normalize(const InputArray src,
OutputArray dst,
double alpha=1,
double beta=0,
int normType=NORM_L2,
int rtype=-1,
InputArray mask=noArray())


【参数】

第一个参数,src:输入矩阵;

第二个参数,dst:输出矩阵

第三个参数,alpha:归一化后的最大值

第四个参数,beta:归一化后的最小值

第五个参数,norm_type:归一化类型,有NORM_INF, NORM_L1, NORM_L2和NORM_MINMAX

第六个参数,rtype – When the parameter is negative, the destination array has the same type as src. Otherwise, it has the same number of channels as src and the depth =CV_MAT_DEPTH(rtype) .

第七个参数,mask – Optional operation mask。

【代码-参看附件demo6】。

运行效果如下。



图11

2.6输入、输出XML和YAMl文件

2.6.1简介

在OpenCV的学习中,经常会用到文件的输入输出,特别是XML和YAML格式的输出文件,如果大家有做过人脸识别就可以体会到,用主分量分析法得到的特征脸、平均脸等等数据都会被保存成为XML格式,方便下次使用时调用,OpenCV2版本比OpenCV1版本省去了很多函数,大家通过opencv中文网就可以查到,而且OpenCV2版本的输入输出使用与STL相同的 << />> 输入/输出操作符,实现起来非常简单,在学习的过程中要特别注意,输入输出是map数据结构还是sequence数据结构,这两种结构上的操作会有所不同。

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

XML可利用于数据交换 主要是因为XML表示的信息独立于平台的,这里的平台即可以理解为不同的应用程序也可以理解为不同的操作系统;它描述了一种规范,利用它Microsoft的word文档可以和Adobe 的Acrobat交换信息,可以和数据库交换信息。

XML表示的结构化数据。 对于大型复杂的文档,xml 是一种理想语言,不仅允许指定文档中的词汇,还允许指定元素之间的关系。比如可以规定一个author元素必须有一个name子元素。可以规定企业的业务必须有包括什么子业务。

XML文档。 XML文档有DTD和XML文本组成,所谓DTD(Document Type Definition ),简单的说就是一组标记符的语法规则.,表明XML文本是怎么样组织的,比如DTD可以表示一个< book>必须有一个子标记 < author>, 可以有或者没有子标记< pages> 等等。当然一个简单的XML文本可以没有DTD。下面是一个简单的xml文本。 < ? Xml version=”1.0” standalone=”yes”> < book> haha 其中以?开始并结尾的是进程说明。Standalone表示外围设备。这里外围设备可以理解为该XML文本没有应用其他的文件。因为XML文件可以外部应用DTD等外部数据。

和GNU一样,YAML是一个递归着说“不”的名字。不同的是,GNU对UNIX说不,YAML说不的对象是XML。YAML不是XML。YAML的可读性好。YAML和脚本语言的交互性好。YAML使用实现语言的数据类型。YAML有一个一致的信息模型。YAML易于实现。

上面5条也就是XML不足的地方。同时,YAML也有XML的下列优点:YAML可以基于流来处理;YAML表达能力强,扩展性好。

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

2.6.2 XML和YAMl文件的写入和读取

FileStorage类是操作文件的操作类。

1、 XML和YAML文件的打开

(1)准备文件写操作

构造函数在实际中有两种使用方式:

方法一:

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


方法二:

FileStorage fs;
fs.open("test.yaml", FileStorage::WRITE);


(2)准备文件读操作

构造函数在实际读操作中有两种使用方式:

方法一:

FileStorage fs("test.yaml", FileStorage::READ);


方法二:

FileStorage fs;
fs.open("test.yaml", FileStorage::READ);


2、进行文件的读写操作

(1)文本的输入和输出

文件的写入用“<<”运算符。

fs << "iterationNr" << 100;


而读取文件,用“>>”运算符。

// 第一种方法
int frameCount = (int)fs2["frameCount"];
// 第二种方法
std::string date;
fs2["calibrationDate"] >> date;


(2)Opencv数据结构的输入和输出

 写入操作

Mat cameraMatrix = (Mat_<double>(3,3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1);
Mat distCoeffs = (Mat_<double>(5,1) << 0.1, 0.01, -0.001, 0, 0);
fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;


 读出操作

Mat cameraMatrix2, distCoeffs2;
fs2["cameraMatrix"] >> cameraMatrix2;
fs2["distCoeffs"] >> distCoeffs2;


3、vector和maps的输入和输出

对于verctor要用“[”开始,“]”结尾。

fs << "strings" << "[";     // text - string sequence
fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
fs << "]";                  // close sequence
对于map要用“{”开始,“}”结尾。
fs << "Mapping";            // text - mapping
fs << "{" << "One" << 1;
fs <<        "Two" << 2 << "}";


读取这些结构会用到FileNode 和FileNodeIterator 。

FileNode n = fs["strings"];                         // Read string sequence - Get node
if (n.type() != FileNode::SEQ)
{
cerr << "strings is not a sequence! FAIL" << endl;
return 1;
}
FileNodeIterator it = n.begin(), it_end = n.end(); //遍历节点
for (; it != it_end; ++it)
cout << (string)*it << endl;


4、文件关闭

文件的关闭非常简单。如下语句。

fs.release();


文件写入的实例程序在【demo7】。

运行后会有一个名为test.yaml的文件。文件内容如下。

%YAML:1.0
frameCount: 5
calibrationDate: "Thu Jul 13 17:48:44 2017\n"
cameraMatrix: !!opencv-matrix
rows: 3
cols: 3
dt: d
data: [ 1000., 0., 320., 0., 1000., 240., 0., 0., 1. ]
distCoeffs: !!opencv-matrix
rows: 5
cols: 1
dt: d
data: [ 1.0000000000000001e-001, 1.0000000000000000e-002,
-1.0000000000000000e-003, 0., 0. ]
features:
- { x:41, y:227, lbp:[ 0, 1, 1, 1, 1, 1, 0, 1 ] }
- { x:260, y:449, lbp:[ 0, 0, 1, 1, 0, 1, 1, 0 ] }
- { x:598, y:78, lbp:[ 0, 1, 0, 0, 1, 0, 1, 0 ] }


接着我们再看看读取程序。需要用到上一个程序产生的文件。参看【demo8】

运行结果如下所示。



图12

参考链接:

英文:https://docs.opencv.org/master/de/d7a/tutorial_table_of_content_core.html

中文:

http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/table_of_content_core/table_of_content_core.html#table-of-content-core

本章附件:

请点击代码链接

【注意】博主在附件中的代码只有Linux版本的,如何使用Windows使用该代码请参看博主的另一篇博文

Opencv环境搭建(Visual Studio+Windows)- 请点击

有任何问题请联系博主。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐