opencv基础教程----cvmat---iplimage--mat
cvAdd,cvAddS, cvAddWeighted和alpha融合...23
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 |
int x, y |
图像中的点 |
CvPoint2D32f |
float x, y |
二维空间中的点 |
CvPoint3D32f |
float x, y, z |
三维空间中的点 |
CvSize |
int width, height |
图像的尺寸 |
CvRect |
int x, y, width, height |
图像的部分区域 |
CvScalar |
double val[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 ( int rows, intcols, int type );
这里type可以是任何预定义类型,预定义类型的结构如下:CV_<bit_depth> (S|U|F)C<number_of_channels>。于是,矩阵的元素可以是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结构:矩阵头
typedefstruct CvMat {
int type;
int step;
int* refcount; // for internal use only
union {
uchar* ptr;
short* s;
int* i;
float* fl;
double* db;
} data;
union {
int rows;
int height;
};
union {
int cols;
int width;
};
}CvMat;
此类信息通常被称作矩阵头。很多程序是区分矩阵头和数据体的,后者是各个data成员所指向的内存位置。
矩阵有多种创建方法。最常见的方法是用cvCreateMat(),它由多个原函数组成,如cvCreateMatHeader()和cvCreateData()。cvCreateMatHeader()函数创建CvMat结构,不为数据分配内存,而cvCreateData()函数只负责数据的内存分配。有时,只需要函数cvCreateMatHeader(),因为已因其他理由分配了存储空间,或因为还不准备分配存储空间。第三种方法是用函数cvCloneMat (CvMat*),它依据一个现有矩阵创建一个新的矩阵。当这个矩阵不再需要时,可以调用函数cvReleaseMat(CvMat*)释放它。 【34】
例3-2概述了这些函数及其密切相关的其他函数。
例3-2:矩阵的创建和释放
//Createa new rows by cols matrix of type 'type'.
//
CvMat*cvCreateMat( int rows, int cols, int type );
//Createonly matrix header without allocating data
//
CvMat*cvCreateMatHeader( int rows, int cols, int type );
//Initializeheader on existiong CvMat structure
//
CvMat*cvInitMatHeader(
CvMat* mat,
int rows,
int cols,
int type,
void* data = NULL,
int step = CV_AUTOSTEP
);
//LikecvInitMatHeader() but allocates CvMat as well.
//
CvMatcvMat(
int rows,
int cols,
int type,
void* data = NULL
);
//Allocatea new matrix just like the matrix 'mat'.
//
CvMat*cvCloneMat( const cvMat* mat );
//Free the matrix 'mat', both header and data.
//
voidcvReleaseMat( CvMat** mat );
与很多OpenCV结构类似,有一种构造函数叫cvMat,它可以创建CvMat结构,但实际上不分配存储空间,仅创建头结构(与cvInitMatHeader()类似)。这些方法对于存取到处散放的数据很有作用,可以将矩阵头指向这些数据,实现对这些数据的打包,并用操作矩阵的函数去处理这些数据,如例3-3所示。
例3-3:用固定数据创建一个OpenCV矩阵
//Createan OpenCV Matrix containing some fixed data.
//
floatvals[] = { 0.866025, -0.500000, 0.500000, 0.866025 };
CvMatrotmat;
cvInitMatHeader(
&rotmat,
2,
2,
CV_32FC1,
vals
);
一旦我们创建了一个矩阵,便可用它来完成很多事情。最简单的操作就是查询数组定义和数据访问等。为查询矩阵,我们可以使用函数cvGetElemType(const CvArr* arr),cvGetDims(const CvArr* arr, int* sizes=NULL)和cvGet- DimSize(const CvArr* arr,int index)。第一个返回一个整型常数,表示存储在数组里的元素类型(它可以为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(
const CvArr* arr,
int idx0,
int* type =NULL
);
uchar*cvPtr2D(
const CvArr* arr,
int idx0,
int idx1,
int* type =NULL
);
uchar*cvPtr3D(
const CvArr* arr,
int idx0,
int idx1,
int idx2,
int* type =NULL
);
uchar*cvPtrND(
const CvArr* 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( const CvArr* arr, int idx0 );
doublecvGetReal2D( const CvArr* arr, int idx0, int idx1 );
doublecvGetReal3D( const CvArr* arr, int idx0, int idx1, int idx2 );
doublecvGetRealND( const CvArr* arr, int* idx );
CvScalarcvGet1D( const CvArr* arr, int idx0 );
CvScalarcvGet2D( const CvArr* arr, int idx0, int idx1 );
CvScalarcvGet3D( const CvArr* arr, int idx0, int idx1, int idx2 );
CvScalarcvGetND( const CvArr* 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, int idx0, double value );
voidcvSetReal2D( CvArr* arr, int idx0, int idx1, double value );
voidcvSetReal3D(
CvArr* arr,
int idx0,
int idx1,
int idx2,
double value
);
voidcvSetRealND( CvArr* arr, int* idx, double value );
voidcvSet1D( CvArr* arr, int idx0, CvScalar value );
voidcvSet2D( CvArr* arr, int idx0, int idx1, CvScalar value );
voidcvSet3D(
CvArr* arr,
int idx0,
int idx1,
int idx2,
CvScalar value
);
voidcvSetND( CvArr* arr, int* idx, CvScalar value );
为了方便,我们也可以使用cvmSet()和cvmGet(),这两个函数用于处理浮点型单通道矩阵,非常简单。
double cvmGet( const CvMat* mat, introw, int col )
void cvmSet( CvMat* mat, int row, intcol, double value )
以下函数调用cvmSet():
cvmSet( mat, 2, 2, 0.5000 );
等同于cvSetReal2D函数调用:
cvSetReal2D( mat, 2, 2, 0.5000 );
恰当的方法
从以上所有那些访问函数来看,你可能会想,没有必要再介绍了。实际上,这些set和get函数很少派上用场。大多数时侯,计算机视觉是一种运算密集型的任务,因而你想尽量利用最有效的方法做事。毋庸置疑,通过这些函数接口是不可能做到十分高效的。相反地,应该定义自己的指针计算并且在矩阵中利用自己的方法。如果打算对数组中的每一个元素执行一些操作,使用自己的指针是尤为重要的(假设没有可以为你执行任务的OpenCV函数)。
要想直接访问矩阵,其实只需要知道一点,即数据是按光栅扫描顺序存储的,列(“x”)是变化最快的变量。通道是互相交错的,这意味着,对于一个多通道矩阵来说,它们变化的速度仍然比较快。例3-9显示了这一过程。 【39~40】
例3-9:累加一个三通道矩阵中的所有元素
floatsum( const CvMat* mat ) {
float s = 0.0f;
for(int row=0; row<mat->rows; row++ ) {
const float* ptr=(const float*)(mat->data.ptr + row * mat->step);
for( col=0; col<mat->cols; col++ ) {
s += *ptr++;
}
}
return( s );
}
计算指向矩阵的指针时,记住一点:矩阵的元素data是一个联合体。所以,对这个指针解引用的时候,必须指明结构体中的正确的元素以便得到正确的指针类型。然后,为了使指针产生正确的偏移,必须用矩阵的行数据长度(step)元素。我们以前曾提过,行数据元素的是用字节来计算的。为了安全,指针最好用字节计算,然后分配恰当的类型,如浮点型。CvMat结构中为了兼容IplImage结构,有宽度和高度的概念,这个概念已经被最新的行和列取代。最后要注意,我们为每行都重新计算了ptr,而不是简单地从开头开始,尔后每次读的时候累加指针。这看起来好像很繁琐,但是因为CvMat数据指针可以指向一个大型数组中的ROI,所以无法保证数据会逐行连续存取。
点的数组
有一个经常提到但又必须理解的问题是,包含多维对象的多维数组(或矩阵)和包含一维对象的高维数组之间的不同。例如,假设有n个三维的点,你想将这些点传递到参数类型为CvMat*的一些OpenCV函数中。对此,有四种显而易见的方式,记住,这些方法不一定等价。一是用一个二维数组,数组的类型是CV32FC1,有n行,3列(n×3)。类似地,也可以用一个3行n列(3×n)的二维数组。也可以用一个n行1列(n×1)的数组或者1行n列(1×n)的数组,数组的类型是CV32FC3。这些例子中,有些可以*转换(这意味着只需传递一个,另一个便可可以计算得到),有的则不能。要想理解原因,可以参考图3-2中的内存布局 情况。
从图中可以看出,在前三种方式中,点集以同样的方式被映射到内存。但最后一种方式则不同。对N维数组的c维点,情况变得更为复杂。需要记住的最关键的一点是,给定点的位置可以由以下公式计算出来。
|
图3-2:有10个点,每个点由3个浮点数表示,这10个点被放在4个结构稍有不同的数组中。前3种情况下,内存布局情况是相同的,但最后一种情况下,内存布局不同
其中,Ncols 和Nchannels分别表示列数和通道数。总的来说,从这个公式中可以看出一点,一个c维对象的N维数组和一个一维对象的(N+c)维数组不同。至于N=1(即把向量描绘成n×1或者1×n数组),有一个特殊之处(即图3-2显示的等值)值得注意,如果考虑到性能,可以在有些情况下用到它。
关于OpenCV的数据类型,如CvPoint2D和CvPoint2D32f,我们要说明的最后一点是:这些数据类型被定义为C结构,因此有严格定义的内存布局。具体说来,由整型或者浮点型组成的结构是顺序型的通道。那么,对于一维的C语言的对象数组来说,其数组元素都具有相同的内存布局,形如CV32FC2的n×1或者1×n数组。这和申请CvPoint3D32f类型的数组结构也是相同的。
原文地址:
http://blog.csdn.net/gubenpeiyuan/article/details/8776542
扫码关注我们:跟着数理化走天下
获得更多的信息哦,一起交流,一起成长哦:微信号:跟着数理化走天下,纯属个人的交流,无盈利目的