opencv基础知识好资料.docx
- 文档编号:11623353
- 上传时间:2023-03-29
- 格式:DOCX
- 页数:66
- 大小:173.68KB
opencv基础知识好资料.docx
《opencv基础知识好资料.docx》由会员分享,可在线阅读,更多相关《opencv基础知识好资料.docx(66页珍藏版)》请在冰豆网上搜索。
opencv基础知识好资料
OpenCV的基本数据类型
OpenCV提供了多种基本数据类型。
虽然这些数据类型在C语言中不是基本类型,但结构都很简单,可将它们作为原子类型。
可以在“…/OpenCV/cxcore/include”目录下的cxtypes.h文件中查看其详细定义。
在这些数据类型中最简单的就是CvPoint。
CvPoint是一个包含integer类型成员x和y的简单结构体。
CvPoint有两个变体类型:
CvPoint2D32f和CvPoint3D32f。
前者同样有两个成员x,y,但它们是浮点类型;而后者却多了一个浮点类型的成员z。
CvSize类型与CvPoint非常相似,但它的数据成员是integer类型的width和height。
如果希望使用浮点类型,则选用CvSize的变体类型CvSize2D32f。
CvRect类型派生于CvPoint和CvSize,它包含4个数据成员:
x,y,width和height。
(正如你所想的那样,该类型是一个复合类型)。
下一个(但不是最后一个)是包含4个整型成员的CvScalar类型,当内存不是问题时,CvScalar经常用来代替1,2或者3个实数成员(在这个情况下,不需要的分量被忽略)。
CvScalar有一个单独的成员val,它是一个指向4个双精度浮点数数组的指针。
所有这些数据类型具有以其名称来定义的构造函数,例如cvSize()。
(构造函数通常具有与结构类型一样的名称,只是首字母不大写)。
记住,这是C而不是C++,所以这些构造函数只是内联函数,它们首先提取参数列表,然后返回被赋予相关值的结构。
【31】
各数据类型的内联构造函数被列在表3-1中:
cvPointXXX(),cvSize(),cvRect()和cvScalar()。
这些结构都十分有用,因为它们不仅使代码更容易编写,而且也更易于阅读。
假设要在(5,10)和(20,30)之间画一个白色矩形,只需简单调用:
cvRectangle(
myImg,
cvPoint(5,10),
cvPoint(20,30),
cvScalar(255,255,255)
);
表3-1:
points,size,rectangles和calar三元组的结构
结构
成员
意义
CvPoint
intx,y
图像中的点
CvPoint2D32f
floatx,y
二维空间中的点
CvPoint3D32f
floatx,y,z
三维空间中的点
CvSize
intwidth,height
图像的尺寸
CvRect
intx,y,width,height
图像的部分区域
CvScalar
doubleval[4]
RGBA值
cvScalar是一个特殊的例子:
它有3个构造函数。
第一个是cvScalar(),它需要一个、两个、三个或者四个参数并将这些参数传递给数组val[]中的相应元素。
第二个构造函数是cvRealScalar(),它需要一个参数,它被传递给给val[0],而val[]数组别的值被赋为0。
最后一个有所变化的是cvScalarAll(),它需要一个参数并且val[]中的4个元素都会设置为这个参数。
矩阵和图像类型
图3-1为我们展示了三种图像的类或结构层次结构。
使用OpenCV时,会频繁遇到IplImage数据类型,第2章已经出现多次。
IplImage是我们用来为通常所说的“图像”进行编码的基本结构。
这些图像可能是灰度,彩色,4通道的(RGB+alpha),其中每个通道可以包含任意的整数或浮点数。
因此,该类型比常见的、易于理解的3通道8位RGB图像更通用。
OpenCV提供了大量实用的图像操作符,包括缩放图像,单通道提取,找出特定通道最大最小值,两个图像求和,对图像进行阈值操作,等等。
本章我们将仔细介绍这类操作。
【32】
图3-1:
虽然OpenCV是由C语言实现的,但它使用的结构体也是遵循面向对象的思想设计的。
实际上,IplImage由CvMat派生,而CvMat由CvArr派生
在开始探讨图像细节之前,我们需要先了解另一种数据类型CvMat,OpenCV的矩阵结构。
虽然OpenCV完全由C语言实现,但CvMat和IplImage之间的关系就如同C++中的继承关系。
实质上,IplImage可以被视为从CvMat中派生的。
因此,在试图了解复杂的派生类之前,最好先了解基本的类。
第三个类CvArr,可以被视为一个抽象基类,CvMat由它派生。
在函数原型中,会经常看到CvArr(更准确地说,CvArr*),当它出现时,便可以将CvMat*或IplImage*传递到程序。
CvMat矩阵结构
在开始学习矩阵的相关内容之前,我们需要知道两件事情。
第一,在OpenCV中没有向量(vector)结构。
任何时候需要向量,都只需要一个列矩阵(如果需要一个转置或者共轭向量,则需要一个行矩阵)。
第二,OpenCV矩阵的概念与我们在线性代数课上学习的概念相比,更抽象,尤其是矩阵的元素,并非只能取简单的数值类型。
例如,一个用于新建一个二维矩阵的例程具有以下原型:
cvMat*cvCreateMat(introws,intcols,inttype);
这里type可以是任何预定义类型,预定义类型的结构如下:
CV_
于是,矩阵的元素可以是32位浮点型数据(CV_32FC1),或者是无符号的8位三元组的整型数据(CV_8UC3),或者是无数的其他类型的元素。
一个CvMat的元素不一定就是个单一的数字。
在矩阵中可以通过单一(简单)的输入来表示多值,这样我们可以在一个三原色图像上描绘多重色彩通道。
对于一个包含RGB通道的简单图像,大多数的图像操作将分别应用于每一个通道(除非另有说明)。
实质上,正如例3-1所示,CvMat的结构相当简单,(可以自己打开文件…/opencv/cxcore/include/cxtypes.h查看)。
矩阵由宽度(width)、高度(height)、类型(type)、行数据长度(step,行的长度用字节表示而不是整型或者浮点型长度)和一个指向数据的指针构成(现在我们还不能讨论更多的东西)。
可以通过一个指向CvMat的指针访问这些成员,或者对于一些普通元素,使用现成的访问方法。
例如,为了获得矩阵的大小,可通过调用函数vGetSize(CvMat*),返回一个CvSize结构,便可以获取任何所需信息,或者通过独立访问高度和宽度,结构为matrix->height和matrix->width。
【33~34】
例3-1:
CvMat结构:
矩阵头
typedefstructCvMat{
inttype;
intstep;
int*refcount; //forinternaluseonly
union{
uchar* ptr;
short* s;
int* i;
float* fl;
double*db;
}data;
union{
introws;
intheight;
};
union{
intcols;
intwidth;
};
}CvMat;
此类信息通常被称作矩阵头。
很多程序是区分矩阵头和数据体的,后者是各个data成员所指向的内存位置。
矩阵有多种创建方法。
最常见的方法是用cvCreateMat(),它由多个原函数组成,如cvCreateMatHeader()和cvCreateData()。
cvCreateMatHeader()函数创建CvMat结构,不为数据分配内存,而cvCreateData()函数只负责数据的内存分配。
有时,只需要函数cvCreateMatHeader(),因为已因其他理由分配了存储空间,或因为还不准备分配存储空间。
第三种方法是用函数cvCloneMat(CvMat*),它依据一个现有矩阵创建一个新的矩阵。
当这个矩阵不再需要时,可以调用函数cvReleaseMat(CvMat*)释放它。
【34】
例3-2概述了这些函数及其密切相关的其他函数。
例3-2:
矩阵的创建和释放
//Createanewrowsbycolsmatrixoftype'type'.
//
CvMat*cvCreateMat(introws,intcols,inttype);
//Createonlymatrixheaderwithoutallocatingdata
//
CvMat*cvCreateMatHeader(introws,intcols,inttype);
//InitializeheaderonexistiongCvMatstructure
//
CvMat*cvInitMatHeader(
CvMat*mat,
int rows,
int cols,
int type,
void*data=NULL,
int step=CV_AUTOSTEP
);
//LikecvInitMatHeader()butallocatesCvMataswell.
//
CvMatcvMat(
int rows,
int cols,
int type,
void*data=NULL
);
//Allocateanewmatrixjustlikethematrix'mat'.
//
CvMat*cvCloneMat(constcvMat*mat);
//Freethematrix'mat',bothheaderanddata.
//
voidcvReleaseMat(CvMat**mat);
与很多OpenCV结构类似,有一种构造函数叫cvMat,它可以创建CvMat结构,但实际上不分配存储空间,仅创建头结构(与cvInitMatHeader()类似)。
这些方法对于存取到处散放的数据很有作用,可以将矩阵头指向这些数据,实现对这些数据的打包,并用操作矩阵的函数去处理这些数据,如例3-3所示。
例3-3:
用固定数据创建一个OpenCV矩阵
//CreateanOpenCVMatrixcontainingsomefixeddata.
//
floatvals[]={0.866025,-0.500000,0.500000,0.866025};
CvMatrotmat;
cvInitMatHeader(
&rotmat,
2,
2,
CV_32FC1,
vals
);
一旦我们创建了一个矩阵,便可用它来完成很多事情。
最简单的操作就是查询数组定义和数据访问等。
为查询矩阵,我们可以使用函数cvGetElemType(constCvArr*arr),cvGetDims(constCvArr*arr,int*sizes=NULL)和cvGet-DimSize(constCvArr*arr,intindex)。
第一个返回一个整型常数,表示存储在数组里的元素类型(它可以为CV_8UC1和CV_64FC4等类型)。
第二个取出数组以及一个可选择的整型指针,它返回维数(我们当前的实例是二维,但是在后面我们将遇到的N维矩阵对象)。
如果整型指针不为空,它将存储对应数组的高度和宽度(或者N维数)。
最后的函数通过一个指示维数的整型数简单地返回矩阵在那个维数上矩阵的大小。
【35~36】
矩阵数据的存取
访问矩阵中的数据有3种方法:
简单的方法、麻烦的方法和恰当的方法。
简单的方法
从矩阵中得到一个元素的最简单的方法是利用宏CV_MAT_ELEM()。
这个宏(参见 例3-4)传入矩阵、待提取的元素的类型、行和列数4个参数,返回提取出的元素 的值。
例3-4:
利用CV_MAT_ELEM()宏存取矩阵
CvMat*mat=cvCreateMat(5,5,CV_32FC1);
floatelement_3_2=CV_MAT_ELEM(*mat,float,3,2);
更进一步,还有一个与此宏类似的宏,叫CV_MAT_ELEM_PTR()。
CV_MAT_ELEM_PTR()(参见例3-5)传入矩阵、待返回元素的行和列号这3个参数,返回指向这个元素的指针。
该宏和CV_MAT_ELEM()宏的最重要的区别是后者在指针解引用之前将其转化成指定的类型。
如果需要同时读取数据和设置数据,可以直接调用CV_MAT_ELEM_PTR()。
但在这种情况下,必须自己将指针转化成恰当的 类型。
例3-5:
利用宏CV_MAT_ELEM_PTR()为矩阵设置一个数值
CvMat*mat=cvCreateMat(5,5,CV_32FC1);
floatelement_3_2=7.7;
*((float*)CV_MAT_ELEM_PTR(*mat,3,2))=element_3_2;
【36】
遗撼的是,这些宏在每次调用的时候都重新计算指针。
这意味着要查找指向矩阵基本元素数据区的指针、计算目标数据在矩阵中的相对地址,然后将相对位置与基本位置相加。
所以,即使这些宏容易使用,但也不是存取矩阵的最佳方法。
在计划顺序访问矩阵中的所有元素时,这种方法的缺点尤为突出。
下面我们将讲述怎么运用最好的方法完成这个重要任务。
麻烦的方法
在“简单的方法”中讨论的两个宏仅仅适用于访问1维或2维的数组(回忆一下,1维的数组,或者称为“向量”实际只是一个n×1维矩阵)。
OpenCV提供了处理多维数组的机制。
事实上,OpenCV可以支持普通的N维的数组,这个N值可以取值为任意大的数。
为了访问普通矩阵中的数据,我们可以利用在例3-6和例3-7中列举的cvPtr*D和cvGet*D…等函数族。
cvPtr*D家族包括cvPtr1D(),cvPtr2D(),cvPtr3D()和cvPtrND()…。
这三个函数都可接收CvArr*类型的矩阵指针参数,紧随其后的参数是表示索引的整数值,最后是一个可选的参数,它表示输出值的类型。
函数返回一个指向所需元素的指针。
对于cvPtrND()来说,第二个参数是一个指向一个整型数组的指针,这个数组中包含索引的合适数字。
后文会再次介绍此函数(在这之后的原型中,也会看到一些可选参数,必要时会有讲解)。
例3-6:
指针访问矩阵结构
uchar*cvPtr1D(
constCvArr* arr,
int idx0,
int* type=NULL
);
uchar*cvPtr2D(
constCvArr* arr,
int idx0,
int idx1,
int* type=NULL
);
uchar*cvPtr3D(
constCvArr* arr,
int idx0,
int idx1,
int idx2,
int* type=NULL
);
uchar*cvPtrND(
constCvArr* arr,
int* idx,
int* type =NULL,
int create_node =1,
unsigned* precalc_hashval=NULL
); 【37~38】
如果仅仅是读取数据,可用另一个函数族cvGet*D。
如例3-7所示,该例与例3-6类似,但是返回矩阵元素的实际值。
例3-7:
CvMat和IPlImage元素函数
doublecvGetReal1D(constCvArr*arr,intidx0);
doublecvGetReal2D(constCvArr*arr,intidx0,intidx1);
doublecvGetReal3D(constCvArr*arr,intidx0,intidx1,intidx2);
doublecvGetRealND(constCvArr*arr,int*idx);
CvScalarcvGet1D(constCvArr*arr,intidx0);
CvScalarcvGet2D(constCvArr*arr,intidx0,intidx1);
CvScalarcvGet3D(constCvArr*arr,intidx0,intidx1,intidx2);
CvScalarcvGetND(constCvArr*arr,int*idx);
cvGet*D中有四个函数返回的是整型的,另外四个的返回值是CvScalar类型的。
这意味着在使用这些函数的时候,会有很大的空间浪费。
所以,只是在你认为用这些函数比较方便和高效率的时候才用它们,否则,最好用cvPtr*D。
用cvPtr*D()函数族还有另外一个原因,即可以用这些指针函数访问矩阵中的特定的点,然后由这个点出发,用指针的算术运算得到指向矩阵中的其他数据的指针。
在多通道的矩阵中,务必记住一点:
通道是连续的,例如,在一个3通道2维的表示红、绿、蓝(RGB)矩阵中。
矩阵数据如下存储rgbrgbrgb...。
所以,要将指向该数据类型的指针移动到下一通道,我们只需要将其增加1。
如果想访问下一个“像素”或者元素集,我们只需要增加一定的偏移量,使其与通道数相等。
另一个需要知道的技巧是矩阵数组的step元素(参见例3-1和例3-3),step是矩阵中行的长度,单位为字节。
在那些结构中,仅靠cols或width是无法在矩阵的不同行之间移动指针的,出于效率的考虑,矩阵或图像的内存分配都是4字节的整数倍。
所以,三个字节宽度的矩阵将被分配4个字节,最后一个字节被忽略。
因此,如果我们得到一个字节指针,该指针指向数据元素,那么我们可以用step和这个指针相加以使指针指向正好在我们的点的下一行元素。
如果我们有一个整型或者浮点型的矩阵,对应的有整型和浮点型的指针指向数据区域,我们将让step/4与指针相加来移到下一行,对双精度型的,我们让step/8与指针相加(这里仅仅考虑了C将自动地将差值与我们添加的数据类型的字节数 相乘)。
【38】
例3-8中的cvSet*D和cvGet*D多少有些相似,它通过一次函数调用为一个矩阵或图像中的元素设置值,函数cvSetReal*D()和函数cvSet*D()可以用来设置矩阵或者图像中元素的数值。
例3-8:
为CvMat或者IplImage元素设定值的函数
voidcvSetReal1D(CvArr*arr,intidx0,doublevalue);
voidcvSetReal2D(CvArr*arr,intidx0,intidx1,doublevalue);
voidcvSetReal3D(
CvArr*arr,
intidx0,
intidx1,
intidx2,
doublevalue
);
voidcvSetRealND(CvArr*arr,int*idx,doublevalue);
voidcvSet1D(CvArr*arr,intidx0,CvScalarvalue);
voidcvSet2D(CvArr*arr,intidx0,intidx1,CvScalarvalue);
voidcvSet3D(
CvArr*arr,
intidx0,
intidx1,
intidx2,
CvScalarvalue
);
voidcvSetND(CvArr*arr,int*idx,CvScalarvalue);
为了方便,我们也可以使用cvmSet()和cvmGet(),这两个函数用于处理浮点型单通道矩阵,非常简单。
doublecvmGet(constCvMat*mat,introw,intcol)
voidcvmSet(CvMat*mat,introw,intcol,doublevalue)
以下函数调用cvmSet():
cvmSet(mat,2,2,0.5000);
等同于cvSetReal2D函数调用:
cvSetReal2D(mat,2,2,0.5000);
恰当的方法
从以上所有那些访问函数来看,你可能会想,没有必要再介绍了。
实际上,这些set和get函数很少派上用场。
大多数时侯,计算机视觉是一种运算密集型的任务,因而你想尽量利用最有效的方法做事。
毋庸置疑,通过这些函数接口是不可能做到十分高效的。
相反地,应该定义自己的指针计算并且在矩阵中利用自己的方法。
如果打算对数组中的每一个元素执行一些操作,使用自己的指针是尤为重要的(假设
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- opencv 基础知识 资料
![提示](https://static.bdocx.com/images/bang_tan.gif)