OpenCV学习笔记(1)——IplImage和Mat结构

OpenCV自从2001年出现到现在已经13年了,除了包含的内容更加广泛之外,其API也发生了变化。OpenCV 2.0之前的API都是基于C的,之后的版本都是基于C++的。相比较来说,C++接口会比较高级一些,最大的特点就是它可以自动管理内存,而C版本的接口需要开发人员自己管理内存。这在小的程序里面优势不明显,因为在小的程序里面我们往往可以不用去做很多内存管理,当程序运行结束后,操作系统会帮我们释放掉所有的资源(当然这是一个不好的习惯)。但如果程序比较大,内存管理将会是一个很大的负担。所以,现在OpenCV官方推荐使用C++ API。但是这并不是说C API就没有优势。在一些嵌入式平台上,往往只支持C,并不支持C++,这时候,就只能使用C API了。另外,C接口实现的程序更具有移植性。这两种接口我都有使用,感觉C接口更适合研究算法,C++接口更适合开发应用程序,特别是比较大型的应用程序。其实,两种接口的大多数的函数使用基本一样,所以掌握一个,也很容易掌握另外一个。有些时候,也可以两个混合使用。这里,我总结一下这两种接口里面最常用的两个数据结构:IplImage结构和Mat结构。

C接口的图像编码基本结构——IplImage

IplImage结构是C接口里面进行图像编码的基本结构。其实,在C API里面,最顶层的是CvArr,接下来是CvMat,而IplImage由CvMat结构“派生”,即CvArr —> CvMat —> IplImage。虽然是C实现的,但是其关系却具有面向对象里面的继承关系:CvArr是一个抽象基类,CvMat由它派生,IplImage由CvMat派生。在许多函数原型中,我们都可以看到CvArr*,这时我们可以将CvMat*或IplImage*传递到程序。

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

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( ),它由多个原函数组成,如只创建CvMat结构而不为数据分配内存的cvCreateMatHeader( )和只负责数据内存分配的cvCreateMatHeader( )函数。第三种方法是用函数cvCloneMat(CvMat * ),它依据一个现有的矩阵创建一个新的矩阵(注:函数cvCloneMat( )和其他的OpenCV包含单词“clone”的函数,不仅创建一个和输入头同样的头,也分配各自的数据区并将源数据复制到新对象。当这个矩阵不再需要时,可以调用函数cvReleaseMat(CvMat * )释放它。关于CvMat结构的存储以及一些操作(比如存取等)这里就不介绍了。因为用的比较多的还是它的派生类IplImage结构。

IplImage结构本质上是一个CvMat对象,但它还有其他一些成员变量将矩阵解释为图像。IplImage结构的准确定义如下:

typedef struct _IplImage
{
    int  nSize;             /* sizeof(IplImage) */
    int  ID;                /* version (=0)*/
    int  nChannels;         /* Most of OpenCV functions
	    		support 1,2,3 or 4 channels */
    int  alphaChannel;      /* Ignored by OpenCV */
    int  depth;             /* Pixel depth in bits: IPL_DEPTH_8U,
			IPL_DEPTH_8S, IPL_DEPTH_16S,
                       	IPL_DEPTH_32S, IPL_DEPTH_32F and
			IPL_DEPTH_64F are supported.  */
    char colorModel[4];     /* Ignored by OpenCV */
    char channelSeq[4];     /* ditto */
    int  dataOrder;         /* 0 - interleaved color channels,
			1 - separate color channels.
                       	cvCreateImage can only create
			interleaved images */
    int  origin;            /* 0 - top-left origin,
                               1 - bottom-left origin
   	      		  (Windows bitmaps style).  */
    int  align;             /* Alignment of image rows (4 or 8).
                               OpenCV ignores it and uses
			   widthStep instead.    */
    int  width;             /* Image width in pixels.           */
    int  height;            /* Image height in pixels.          */
    struct _IplROI *roi;    /* Image ROI. If NULL, the whole image is selected. */
    struct _IplImage *maskROI;      /* Must be NULL. */
    void  *imageId;                 /* "           " */
    struct _IplTileInfo *tileInfo;  /* "           " */
    int  imageSize;         /* Image data size in bytes
                               (==image->height*image->widthStep
                               in case of interleaved data)*/
    char *imageData;        /* Pointer to aligned image data.      */
    int  widthStep;         /* Size of aligned image row in bytes. */
    int  BorderMode[4];     /* Ignored by OpenCV.                  */
    int  BorderConst[4];    /* Ditto.                              */
    char *imageDataOrigin;  /* Pointer to very origin of image data
                               (not necessarily aligned) -
                               needed for correct deallocation */
}
IplImage;

该结构里面的每个成员都有注释说明。想要深入了解,可以查看OpenCV官方文档。

C++接口的Mat类

前面已经说过,C++结构的一个特别大的好处就是可以一定程度上实现内存自动管理。Mat类便是如此。当你使用Mat类的时候,你不必再像使用CvMat或IplImage结构时还需要主动管理内存。当你使用Mat时,它会自动分配内存,一旦你不需要时,它便会自动释放。大多数C++接口的函数都可以自己管理内存,但偶尔也会有需要开发人员手动管理的。和CvMat相似,Mat结构也由两部分组成:矩阵头和存储像素点值的数据域组成。矩阵头的大小是恒定的,主要包含一些矩阵基本的信息和数据域的地址信息,但是数据域的大小却会随着图像大小变化。因为Mat类特别大,所以这里就不贴代码了。

OpenCV是一个图像处理库,它包含了大量的图像处理函数。很多情况下,我们通过一系列函数调用就可以解决一个实际问题。这样便不可避免的要进行数据的传递,但是图像数据往往是比较大的,如果经常拷贝,会严重影响性能(算法本身往往就很吃资源)。如同大多数高级语言那样,OpenCV使用了一种引用计数机制——每个Mat对象都有自己的头,但是不同的Mat对象却可能共享同样的数据域(头里面指向数据域的指针可能指向同一个地址)。而且很多时候,复制操作往往只是复制头信息,并不复制数据域。比如下面的代码:

Mat A, C;  // 只创建了头信息
A = imread(argv[1], 1); //使用imread()函数会给A分配内存(数据域)

Mat B(A);  // 使用复制构造函数
C = A;	   // 复制操作
Mat D(A, Rect(10, 10, 100, 100));
Mat E = A(Range:all(), Range(1, 3));

Mat F = A.clone();
Mat G;
A.copyTo(G);

只有A分配了数据域,B和C都有自己的头信息,但是数据域是共享A的。A、B、C三个结构一样大。

D和E虽然只是使用A的一部分数据,但它们也只有头信息,数据域也是共享A的,只是共享了一部分而已。对于A~E,因为共享的是同一个数据域,所以只要有一个改变了数据域,就会影响到其他四个Mat对象。在矩阵内部维护一个计数器,用于统计有多少个对象共享这个数据域。只有当计数器为0的时候,才会真正释放数据域的内存,否则,只是释放头信息。

但通过使用clone( )和copyTo( )函数却可以连同头信息和数据域一起复制。所以上面的F、G与A是不同的,更改任何一个的数据域并不影响其他两个。总结一下:

  1. 用于OpenCV函数输出的图像的内存分配是自动分配的。
  2. 使用OpenCV的C++接口编程时你不用考虑内存管理。
  3. 赋值操作和复制构造函数只复制头信息。
  4. 如果想连通数据域一起复制,那么可以使用clone( )和copyTo( )函数。

以上便是OpenCV中用来存储图像数据的两种结构,理解这两种结构才能更好的使用OpenCV进行图像处理。


添加新评论

选择表情 captcha

友情提醒:不填或错填验证码会引起页面刷新,导致已填的评论内容丢失。

|