OpenCV学习笔记(一)—OpenCV的基本数据类型和矩阵

基本数据类型

可在"....../OpenCV/cxcore/include"目录下.h文件中查看其详细定义.

points,size,rectangles和Scalar三元组的结构
结构 成员 意义
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的值

备注:CvSize如果希望使用浮点类型 可使用CvSize的变体类型 CvSize2D32f;

CvScalar有三个构造函数:

cvScalar() 需要一个,俩个,三个或者四个参数并将这些数传递给数组val[]中的元素

cvRealScalar()只需要一个参数,它被传给val[0],其他被置0

cvScalarAll()他需要一个参数,val[]中所有的元素都会被设置为这个参数


矩阵和图像类型

虽然OpenCV是由C语言实现的,但它使用的结构体也是遵循面向对象的思想设计的.实际上,IplImage由CvMat派生,而CvMat由CvArr派生

CvMat矩阵结构:

首先要了解:

第一,在OpenCV中没有向量(vector)结构.任何时候需要向量,都只需要一个列矩阵(如果需要一个转置或者共轭向量,则需要一个行矩阵)

第二,OpenCV中的矩阵概念,与我们在代数课上学习的更抽象,尤其是矩阵的元素,并非只能取简单的数值类型

例如:一个用于新建一个二维矩阵的例程有以下原型:

cvMat* cvCreatMat(int rows , int cols , int  type);

type可以是任何类型,于是矩阵的类型可以是32位浮点类型(CV_32FC1),或者是无符号的8位三元组的整型数据(CV_8UC3),或者是其他类型的元素.

一个CvMat的元素不一定就是个单一的数字,在矩阵中可以通过单一的输入表示多值,这样我们可以在一个三原色通道中描绘多重色彩通道.

对于一个包含RGB通道的简单图像,大多数的图像操作将分别应用于每一个通道(除非另有说明).

实质上,CvMat的结构相当简单.矩阵由宽度(width),高度(height),类型(type),行数据长度(step,用字节表示)和一个指向数据的指针构成.

可以通过一个指向CvMat的指针访问这些成员,或者对于一些普通元素,使用现成的访问方法.

例如:为获得矩阵的大小,可通过调用函数vGetSize(CvMat *),返回一个CvSize结构,便可以获得任何所需信息,

或者独立访问高度和宽度结构为 matrix->height 和matrix->weight.

typedef struct CvMat
{
    int type;
    int step;

    /* for internal use only */
    int* refcount;
    int hdr_refcount;

    union
    {
        uchar* ptr;
        short* s;
        int* i;
        float* fl;
        double* db;
    } data;

#ifdef __cplusplus
    union
    {
        int rows;
        int height;
    };

    union
    {
        int cols;
        int width;
    };
#else
    int rows;
    int cols;
#endif

}
CvMat;

此类信息常被称作矩阵头,很多程序是区分矩阵头和数据体的,后者是各个data成员所指像的内存位置.

矩阵有很多种创建方法.最常见的是cvCreateMat(),它由多个原函数组成,如cvCreateMatHeader()和cvCreateData().

cvCreateMatHeader()函数创建CvMat结构,不为数据分配内存,而cvCreateData()函数只负责数据内存的分配;

有时只需要函数cvCreateMatHeader(),因为以因为其他原因分配了存储空间,或因为还不准备分配存储空间;

第三种方法是用函数cvCloneMat(CvMat*),它依据现有的一个矩阵创建一个新的矩阵.当这个矩阵不在需要时,可以调用函数cvReleaseMat(cvMat**)释放它.

	//创建一个type类型 ,rows行,cols列的矩阵
	CvMat* cvCreateMat( int rows, int cols, int type );
	//只创建矩阵头,不创建数据
	CvMat* cvCreateMatHeader( int rows, int cols, int type );
	//初始化当前矩阵
	CvMat* cvInitMatHeader( 
		CvMat* mat, 
		int rows,
		int cols,
		int type,
		void* data CV_DEFAULT(NULL),
		int step CV_DEFAULT(CV_AUTOSTEP) 
		);
	//与cvInitMatHeader类似同时创建矩阵
	CvMat cvMat(
		int rows, 
		int cols, 
		int type, 
		void* data CV_DEFAULT(NULL)
		);
	//创建一个和mat相同的矩阵
	CvMat* cvCloneMat( const CvMat* mat );
	//释放矩阵mat的空间 包括头和数据
	void cvReleaseMat( CvMat** mat );


	/*例:用固定数据创建一个OpenCV矩阵*/
	//创建一个矩阵,包含一些散乱的数据
	float vals[] = { 0.866025, -0.50000,0.500000, 0.866025};
	CvMat rotmat;
	cvInitMatHeader(
		&rotmat,
		2,
		2,
		CV_32FC1,
		vals
		);

一旦我们创建了一个矩阵,我们可以用来做很多事情.最简单的就是查询数组定义和数据访问等.

为查询矩阵,我们可以使用函数cvGetElemType(const CvArr* arr),  返回一个整型常数,表示存在数组里的元素类型

cvGetDims(const CvArr* arr , int * sizes =NULL);,返回维数

cvGetDimsSize(const CvArr* arr, int index),简单返回矩阵在那个维数上的矩阵大小,注:这里讨论的是2维数组,所以第0维,通常表示宽度,第一维通常表示高度.

矩阵数据的存取

(1)简单的方法

从矩阵中得到一个元素的最简单方法是利用宏CV_MAT_ELEM().

这个宏传入矩阵,待提取的元素类型,行和列数 4个参数,返回取出元素的值

//利用CV_MAT_ELEM()宏存取矩阵
	CvMat* mat = cvCreateMat(5,5,CV_32FC1);
	float element_3_2 = CV_MAT_ELEM(*mat,float,3,2);
还有一个于此类似的宏CV_MAT_ELEM_PTR(),

这个宏传入矩阵,待返回元素的行号和列号这三个参数,返回指向这个元素的指针

改宏和CV_MAT_ELEM()宏的最重要区别是后者在指针解引用之前将其转化成指定的类型.

如何需要同时读取数据和设置数据,可以直接调用CV_MAT_ELEM_PTR().但是在这种情况下,必须自己将指针转化成适当的类型

//利用CV_MAT_ELEM_PTR为矩阵设置一个数值
	CvMat* mat = cvCreateMat(5,5,CV_32FC1);
	float element_3_2 = 7.7;
	*((float*)CV_MAT_ELEM_PTR(*mat,3,2)) = element_3_2;
遗憾的是这些宏每次调用的时候都重新计算指针.这意味着要查找指向矩阵基本元素的数据区的指针,计算目标数据在矩阵中的相对地址,然后将相对位置与基本位置相加.所以即使这些宏容易使用也不是存取矩阵的最佳方法.在计划顺序访问矩阵中的所有元素时,这种方法的缺点尤为突出.

(2)麻烦的方法

在简单的方法中讨论的两个宏仅仅适合访问一维或者是二维数组,事实上OpenCV可以支持普通的N维数组,这个N值可以取数值为任意大的数
为了访问普通矩阵中的数据可以使用在例3.6和例3.7中的列举的函数

cvPtr*D家族都可以接受Cvarr* 类型的矩阵指针参数,紧随其后的参数是表示索引的整数值,最后一个是可选参数,他表示输出值的类型.函数返回一个指向所需元素的指针,

对于cvPtrND来说,第二个参数是一个指向一个整型数组的指针,这个数组中包含索引的合适数字.

//例3.6 指针访问矩阵结构
	uchar* cvPtr1D(
		const CvArr* arr,
		int idx0,
		int* type CV_DEFAULT(NULL)
		);
	uchar* cvPtr2D(
		const CvArr* arr, 
		int idx0, 
		int idx1, 
		int* type CV_DEFAULT(NULL) );
	uchar* cvPtr3D( 
		const CvArr* arr,
		int idx0, 
		int idx1, 
		int idx2,
		int* type CV_DEFAULT(NULL));
	uchar* cvPtrND( 
		const CvArr* arr, 
		const int* idx,
		int* type CV_DEFAULT(NULL),
		int create_node CV_DEFAULT(1),
		unsigned* precalc_hashval CV_DEFAULT(NULL));
如何仅仅是读取数字,可用另一种函数族cvGet*D.与3.6类似,但是返回矩阵元素的实际值

//例3.7CvMat和IPlImage元素函数
	double cvGetReal1D( const CvArr* arr, int idx0 );
	double cvGetReal2D( const CvArr* arr, int idx0, int idx1 );
	double cvGetReal3D( const CvArr* arr, int idx0, int idx1, int idx2 );
	double cvGetRealND( const CvArr* arr, const int* idx );

	CvScalar cvGet1D( const CvArr* arr, int idx0 );
	CvScalar cvGet2D( const CvArr* arr, int idx0, int idx1 );
	CvScalar cvGet3D( const CvArr* arr, int idx0, int idx1, int idx2 );
	CvScalar cvGetND( const CvArr* arr, const int* idx );

cvGet *()D中有四个函数返回值是double类型的,另外四个的返回值是CvScalar类型的.

这意味着,在使用这些函数的时候,会有很大的空间浪费.所以,只是在你认为用这些函数比较方便和高效的时候才用他们,否则最好使用cvPtr*D();


用cvPtr*D()函数族还有另外一个原因,即可以用这些指针函数访问矩阵中的特定的点,然后由这个点出发,用指针的算术运算得到指向矩阵中的其他数据的指针,

在多通道的矩阵中,务必记住一点:通道是连续的,例如,在一个3通道的2维的表示红,绿,蓝(RGB矩阵中).要将指向该数据类型的指针指向下一通道,我们只需要将其增加1.如果想访问下一个"像素"或者元素集,我们只需要增加一定的偏移量,使其与通道数相等.


另一个需要知道的技巧是矩阵数组的step元素,step是矩阵中行的长度,单位为字节.在那些结构中,仅依靠cols或width是无法在矩阵的不同行之间移动指针的,处于效率的考虑,矩阵和图像的内存分配都是4字节的整数倍.所以三个字节宽度的矩阵将被分配4个字节,最后一个字节被忽略.因此如果我们得到一个字节指针,该指针指向数据元素,那我们可以和step相加,使指针正好在我们点的下一行.如果我们有一个整型或者浮点型的矩阵,对应的有整型和浮点型的指针指向数组区域,我们让step/4与指针相加来移动到下一行,对于双精度的我们让step/8与指针相加.

//例3.8 为CvMat或者IplImage元素设定值的函数
	void cvSetReal1D( CvArr* arr, int idx0, double value );
	void cvSetReal2D( CvArr* arr, int idx0, int idx1, double value );
	void cvSetReal3D(CvArr* arr,int idx0,int idx1, int idx2, double value );
	void cvSetRealND( CvArr* arr, const int* idx, double value );

	void cvSet1D( CvArr* arr, int idx0, CvScalar value );
	void cvSet2D( CvArr* arr, int idx0, int idx1, CvScalar value );
	void cvSet3D( CvArr* arr, int idx0, int idx1, int idx2, CvScalar value );
	void cvSetND( CvArr* arr, const int* idx, CvScalar value );
	//为了方便,我们也可以使用cvmSet()和cvmGet(),这俩个函数用于处理浮点型的单通道矩阵,非常简单
	 void  cvmSet( CvMat* mat, int row, int col, double value );
	 double  cvmGet( const CvMat* mat, int row, int col );
	 //以下函数调用cvmSet()
	 cvmSet(mat,2,2,0.5000);
	 //等同于
	 cvSetReal2D(mat,2,2,0.5000);

(3)恰当的方法

应该定义自己的指针计算并且在矩阵中利用自己的方法,才能高效.

要想直接访问矩阵,其实只需要知道一点,即数据是按光栅扫描顺序存储的,列("x")是变换最快的变量.通道是相互交错的,这意味着,对于一个多通道矩形来说,它门的变化速度仍然比较快,例3.9显示了这一过程.

//例3.9 累加一个三通道矩阵中的所有元素
	 float sum( 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 (int col = 0 ;col < mat->cols ; col++)
			 {
				 s += *ptr++;
			 }
		 }
		 return s;
	 }

计算指向矩阵的指针时,记住一点:矩阵的元素data是一个联合体.所以,对这个指针解引用的时候,必须指明结构体中的正确元素以便得到正确的指针类型.然后,为了使指针产生正确的偏移,必须使用矩阵行数据长度单位(step)元素,行数据元素是以字节来计算的.为了安全指针最好用字节计算,然后分配恰当的类型,如浮点型.CvMat结构中为了兼容IplImage结构,有宽度和高度的概念,这个概念已被最新的行和列取代.最后要注意,我们为每行都重新计算了ptr,而不是简单的从头开始,而后每次读的时候都累加指针.这看起来很繁琐,但是因为CvMat数据指针可以指向一个大型数组中的ROI,所以无法保证数据会逐行连续存取.

点的数组

包含多维对象的多为数组(或矩阵)和包含一维对象的高维数组的不同.例如有n个三维的点,你想将这些点传递到参数类型为CvMat* 的一些函数中.对此有四种显而易见的方式.记住这些方法不一定等价.一个是用一个二维数组数组的类型是(CV32FC1);类似的也可以用一个3行n列(3×n)的二维数组;也可以用一个n行一列(n×1)的数组或者1行n列(1×n)的数组,数组的类型是CV32FC3.这些例子中,有些可以自由转换(这意味着只需要传递一个,另一个便可以计算得到),有的则不能.


关于OpenCV的数据类型,如CvPoint2D和CvPoint2D31f,我们要说明的最后一点是:这些数据类型被定义为C结构,因此有严格定义的内存布局.具体来说,由整型或者浮点型组成的结构是顺序型的通道.那么,对于一维C语言对象数组来说,起数组元素都具有相同的内存布局,形如CV32FC2的n×1或者1×n的数组,这和申请CvPoint3D32f类型数组结构也是相同的.

创建矩阵的四种方式:

(1)
cvCreateMat(int rows,int cols,int type)
Type可以使任何预定义类型。Type的写法规则:CV_(S|U|F)C

(2)
cvCreateMatHeader()函数创建CvMat结构体,不分配数据内存
cvCreateData()函数分配数据需要的内存

(3)
从数组创建矩阵
cvInitMatHeader()函数,在已有的CvMat结构体上初始化矩阵

(4)
cvCloneMat(),该函数依据现有矩阵克隆一个矩阵,分配了独立的空间,需要使用cvReleaseMat()释放。

获取矩阵的相关属性

获取矩阵的数据类型:cvGetElemType
获取矩阵的通道数(维度):cvGetDims
获取矩阵某一个维度上的大小:cvGetDimSize
二维矩阵获取矩阵大小:cvGetSize

/*
*
*本程序由www.opencvchina.com完成
*如若转载,请注明出自 www.opencvchina.com
*
*
*/


#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
#include "cxcore.h"

void test(CvMat* t)
{
        //获取矩阵的数据类型
        int type = cvGetElemType(t);

        //获取矩阵的维度信息
        int size[10];
        int dims = cvGetDims(t,size);

        int x = cvGetDimSize(t,0);
        int y = cvGetDimSize(t,1);
}

int main(int argc, char* argv[])
{

        //创建矩阵 方式1  直接创建
        CvMat* pmat1;
        pmat1 = cvCreateMat(8,9,CV_32FC1);

        //创建矩阵方式2  先创建矩阵头部  再创建矩阵的数据块的内存空间
        CvMat* pmat2;
        pmat2 = cvCreateMatHeader(4,5,CV_8UC1);
        cvCreateData(pmat2);

        //创建矩阵方式3  通过数据创建矩阵
        float data[4] = {3,4,6,0};
        CvMat pmat3;
        cvInitMatHeader(&pmat3,2,2,CV_32FC1,data);

        //创建矩阵方式4 通过已有矩阵进行克隆
        CvMat* pmat4;
        pmat4 = cvCloneMat(pmat2);

        //访问矩阵的相关属性
        test(pmat2);
        

        //释放矩阵的内存空间
        cvReleaseMat(&pmat1);
        cvReleaseMat(&pmat2);
        cvReleaseMat(&pmat4);

        return 0;
}


你可能感兴趣的:(openCV,openCV,图像处理,学习)