基本数据类型
可在"....../OpenCV/cxcore/include"目录下.h文件中查看其详细定义.
结构 | 成员 | 意义 |
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 );
这意味着,在使用这些函数的时候,会有很大的空间浪费.所以,只是在你认为用这些函数比较方便和高效的时候才用他们,否则最好使用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);
应该定义自己的指针计算并且在矩阵中利用自己的方法,才能高效.
要想直接访问矩阵,其实只需要知道一点,即数据是按光栅扫描顺序存储的,列("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;
}
点的数组
包含多维对象的多为数组(或矩阵)和包含一维对象的高维数组的不同.例如有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类型数组结构也是相同的.
/*
*
*本程序由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;
}