OpenCV学习之3
扫描二维码
随时随地手机看文章
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++,所以这些构造函数只是内联函数,它们首先提取参数列表,然后返回被赋予相关值的结构。
各数据类型的内联构造函数被列在下表中:cvPoint(),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个元素都会设置为这个参数。
矩阵和图像类型
下图为我们展示了三种图像的类或结构层次结构。使用OpenCV时,会频繁遇到IplImage数据类型,IplImage是我们用来为通常所说的“图像”进行编码的基本结构。这些图像可能是灰度,彩色,4通道的(RGB+alpha),其中每个通道可以包含任意的整数或浮点数。因此,该类型比常见的、易于理解的3通道8位RGB图像更通用。
OpenCV提供了大量实用的图像操作符,包括缩放图像,单通道提取,找出特定通道最大最小值,两个图像求和,对图像进行阈值操作,等等。
图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, int cols, int type );
这里type可以是任何预定义类型,预定义类型的结构如下:CV_ (S|U|F)C。于是,矩阵的元素可以是32位浮点型数据(CV_32FC1),或者是无符号的8位三元组的整型数据(CV_8UC3),或者是无数的其他类型的元素。一个CvMat的元素不一定就是个单一的数字。在矩阵中可以通过单一(简单)的输入来表示多值,这样我们可以在一个三原色图像上描绘多重色彩通道。对于一个包含RGB通道的简单图像,大多数的图像操作将分别应用于每一个通道(除非另有说明)。
IplImage数据结构
从本质上讲,它是一个CvMat对象,但它还有其他一些成员变量将矩阵解释为图像。这个结构最初被定义为Intel图像处理库(IPL)的一部分。IplImage结构的准确定义如例3-10所示。
例3-10:IplImage结构
typedef struct _IplImage {
int nSize;
int ID;
int nChannels;
int alphaChannel;
int depth;
char colorModel[4];
char channelSeq[4];
int dataOrder;
int origin;
int align;
int width;
int height;
struct _IplROI* roi;
struct _IplImage* maskROI;
void* imageId;
struct _IplTileInfo* tileInfo;
int imageSize;
char* imageData;
int widthStep;
int BorderMode[4];
int BorderConst[4];
char* imageDataOrigin;
} IplImage;
我们试图讨论这些变量的某些功能。有些变量不是很重要,但是有些变量非常重要,有助于我们理解OpenCV解释和处理图像的方式。
width和height这两个变量很重要,其次是depth和nchannals。depth变量的值取自ipl.h中定义的一组数据,但与在矩阵中看到的对应变量不同。因为在图像中,我们往往将深度和通道数分开处理,而在矩阵中,我们往往同时表示它们。可用的深度值如表3-2所示。 表3-2:OpenCV图像类型
宏
图像像素类型
IPL_DEPTH_8U
无符号8位整数 (8u)
IPL_DEPTH_8S
有符号 8位整数(8s)
IPL_DEPTH_16S
有符号16位整数(16s)
IPL_DEPTH_32S
有符号32位整数(32s)
IPL_DEPTH_32F
32位浮点数单精度(32f)
IPL_DEPTH_64F
64位浮点数双精度(64f)
通道数nChannels可取的值是1,2,3或4。
随后两个重要成员是origin和dataOrder。origin变量可以有两种取值:IPL_ORIGIN_TL 或者 IPL_ORIGIN_BL,分别设置坐标原点的位置于图像的左上角或者左下角。
在计算机视觉领域,一个重要的错误来源就是原点位置的定义不统一。具体而言,图像的来源、操作系统、编解码器和存储格式等因素都可以影响图像坐标原点的选取。举例来说,你或许认为自己正在从图像上面的脸部附近取样,但实际上却在图像下方的裙子附近取样。避免此类现象发生的最好办法是在最开始的时候检查一下系统,在所操作的图像块的地方画点东西试试。
dataOrder的取值可以是IPL_DATA_ORDER_PIXEL或IPL_DATA_ORDER_PLANE,前者指明数据是将像素点不同通道的值交错排在一起(这是常用的交错排列方式),后者是把所有像素同通道值排在一起,形成通道平面,再把平面排列起来。
参数widthStep与前面讨论过的CvMat中的step参数类似,包括相邻行的同列点之间的字节数。仅凭变量width是不能计算这个值的,因为为了处理过程更高效每行都会用固定的字节数来对齐;因此在第i行末和第i+1行开始处可能会有些冗于字节。参数imageData包含一个指向第一行图像数据的指针。如果图像中有些独立的平面(如当dataOrder = IPL_DATA_ORDER_PLANE)那么把它们作为单独的图像连续摆放,总行数为height和nChannels的乘积。但通常情况下,它们是交错的,使得行数等于高度,而且每一行都有序地包含交错的通道。
最后还有一个实用的重要参数—— 感兴趣的区域(ROI),实际上它是另一个IPL/IPP 结构IplROI的实例。IplROI包含xOffset,yOffset,height,width和coi成员变量,其中COI代表channel of interest(感兴趣的通道)。ROI的思想是:一旦设定ROI,通常作用于整幅图像的函数便会只对ROI所表示的子图像进行操作。如果IplImage变量中设置了ROI,则所有的OpenCV函数就会使用该ROI变量。如果COI被设置成非0值,则对该图像的操作就只作用于被指定的通道上了。不幸的是,许多OpenCV函数都忽略参数COI。
OpenCV提供了多种基本数据类型。虽然这些数据类型在C语言中不是基本类型,但结构都很简单,可将它们作为原子类型。可以在“…/OpenCV/cxcore/include”