您的位置:首页 > 运维架构

OpenCV3 - Mat 数据类型初探

2015-09-24 22:42 417 查看
开始学习OpenCV2了,对于刚开始接触OpenCV2的人来说,了解Mat这个在OpenCV里面用得最多的数据类型是开始学习的第一步。下面我说一下在我开始学习的时候是怎样一步一步来揭开Mat的真面目的。

首先来看看Mat类的定义中包含的数据变量。这里有必要提一下,定义一个Mat型变量后,会有两个数据部分,一个是数据头部分,包含了该变量的一些必要的信息;另一个是真正的矩阵数据。通常这两个数据块的地址并不是在一起,而是在两个内存块中,下面将通过截图详细说明。

Mat类中数据变量的定义:

/*! includes several bit-fields:
- the magic signature
- continuity flag
- depth
- number of channels
*/
int flags;//按照上面的注释,应该是一些标志位,具体还有待深入研究
//! the matrix dimensionality, >= 2
int dims;
//! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
int rows, cols;
//! pointer to the data
uchar* data;//Mat数据的存放地址

//! pointer to the reference counter;
// when matrix points to user-allocated data, the pointer is NULL
int* refcount;

//! helper fields used in locateROI and adjustROI
uchar* datastart;
uchar* dataend;
uchar* datalimit;

//! custom allocator
MatAllocator* allocator;

struct CV_EXPORTS MSize
{
MSize(int* _p);
Size operator()() const;
const int& operator[](int i) const;
int& operator[](int i);
operator const int*() const;
bool operator == (const MSize& sz) const;
bool operator != (const MSize& sz) const;

int* p;
};

struct CV_EXPORTS MStep
{
MStep();
MStep(size_t s);
const size_t& operator[](int i) const;
size_t& operator[](int i);
operator size_t() const;
MStep& operator = (size_t s);

size_t* p;
size_t buf[2];
protected:
MStep& operator = (const MStep&);
};

MSize size; //size 中包含一个指针变量p,指向类的 rows 的存储地址
MStep step; //step 包含一个 size_t 型的指针,指向 szie_t 的buf[0],buf[0]表示步长step的大小,buf[1]表示矩阵每个数据的字节大小。
//另外,步长 step 表示矩阵一行有多少个字节大小,step = buf[1] * cols


测试代码1:

#include "stdio.h"
#include <iostream>
#include <opencv2\opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
Mat A(10, 10, CV_16U);
uchar i = 0, j = 0;
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
A.ptr<unsigned short>(i)[j] = 0x1234;
}
int * p = (int *)&A;//指针p指向A的头文件的存储地址
return 0;
}


Mat A的结构,与定义是一致的。(这不废话嘛,不一致才怪了~)



图1. A的变量结构

测试代码中还定义了一个指针变量p,用来指向矩阵A的头文件的起始地址,这里p = 0x0030F8F4,查看该处的内存:



图2.矩阵A头文件在内存中的存储

通过对比观察,前四个字节分别是 02 40 ff 42,即flags = 0x42ff4002,化为十进制就是1124024322,也与图1相符。依次对比,发现确实是一一对应的。在这里,Mat A的头文件大小是56个字节,之所以强调在这里,是因为这里定义的矩阵数据类型是16U,16位无符号整型,待会儿会看看当定义其他数据类型的时候头文件的大小是不是会变化。

头文件看完了,下面我们来看看矩阵真正的数据存储情况。通过查看图1,可以看到数据的起始地址datastart = 0x00539a20,结束地址dataend = 0x00539ae8,dataend - datastart = 0xc8,十进制为200,测试代码中我们定义的10x10的16位无符号整数型的矩阵,数据大小正好为200。如下图所示:



图3.矩阵数据的内存存储
通过观察发现,在矩阵数据的最后,还有四个字节为 01 00 00 00,这四个字节不是矩阵的数据,其地址已经超过了dataend。这时,观察图1矩阵头文件中的refcount,发现其大小为1,地址也恰好就是dataend的大小。说明了在矩阵数据的最后,储存了refcount的大小,是一个int型变量,占用四个四节。
refcount是一个很有用的东西,它记录了这个矩阵的数据被其他变量引用了多少次。在c++中矩阵的一些赋值操作往往只给新的变量赋予一个新的头文件,而数据部分只把数据指针指过去,而不重新分为内存,需要程序员来管理内存。这样的话就会有隐患,因为如果原始的变量的数据内存释放了,新的变量却还在,指向了已经被释放的内存区域,如果对新变量进行操作,很可能出现意想不到的问题。不过不用担心,OpenCV的开发者早就想到了这个问题,并为我们想到了一个解决方法,那就是靠这个refcount来记录该数据被多少变量共用,直到最后一个变量被释放时,才释放掉这个存储数据内存块。这也是为什么类定义中refcount为指针,让实际记录次数的变量跟在矩阵数据最后面。我们就可以放心的用啦,下面将会给出例子继续详细说明这个问题~

测试代码2:

#include "stdio.h"
#include <iostream>
#include <opencv2\opencv.hpp>

//#include <string>

using namespace cv;
using namespace std;

int main()
{
// create a big 8Mb matrix
Mat A(10, 10, CV_16U);
cout << "运行Mat A(10, 10, CV_16U)\n";
cout << "A的数据地址:" << (unsigned int)A.datastart << ",数据起始地址:" << (unsigned int)A.data << ",矩阵数据引用次数:" << *A.refcount << '\n' << endl;
uchar i = 0, j = 0;
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
A.ptr<unsigned short>(i)[j] = (i<<8) + j;
}
int * p = (int *)&A;

// create another header for the same matrix;
// this is an instant operation, regardless of the matrix size.
Mat B = A;
cout << "运行Mat B = A\n";
cout << "A的数据地址:" << (unsigned int)A.datastart << ",数据起始地址:" << (unsigned int)A.data << ",矩阵数据引用次数:" << *A.refcount << '\n';
cout << "B的数据地址:" << (unsigned int)B.datastart << ",数据起始地址:" << (unsigned int)B.data << ",矩阵数据引用次数:" << *B.refcount << '\n' << endl;

// create another header for the 3-rd row of A; no data is copied either
Mat C = B.row(3);
cout << "运行Mat C = B.row(3)\n";
cout << "A的数据地址:" << (unsigned int)A.datastart << ",数据起始地址:" << (unsigned int)A.data << ",矩阵数据引用次数:" << *A.refcount << '\n';
cout << "B的数据地址:" << (unsigned int)B.datastart << ",数据起始地址:" << (unsigned int)B.data << ",矩阵数据引用次数:" << *B.refcount << '\n';
cout << "C的数据地址:" << (unsigned int)C.datastart << ",数据起始地址:" << (unsigned int)C.data << ",矩阵数据引用次数:" << *C.refcount << '\n' << endl;

// now create a separate copy of the matrix
Mat D = B.clone();
cout << "运行Mat D = B.clone()\n";
cout << "A的数据地址:" << (unsigned int)A.datastart << ",数据起始地址:" << (unsigned int)A.data << ",矩阵数据引用次数:" << *A.refcount << '\n';
cout << "B的数据地址:" << (unsigned int)B.datastart << ",数据起始地址:" << (unsigned int)B.data << ",矩阵数据引用次数:" << *B.refcount << '\n';
cout << "C的数据地址:" << (unsigned int)C.datastart << ",数据起始地址:" << (unsigned int)C.data << ",矩阵数据引用次数:" << *C.refcount << '\n';
cout << "D的数据地址:" << (unsigned int)D.datastart << ",数据起始地址:" << (unsigned int)D.data << ",矩阵数据引用次数:" << *D.refcount << '\n' << endl;

// copy the 5-th row of B to C, that is, copy the 5-th row of A
// to the 3-rd row of A.
B.row(5).copyTo(C);
// now let A and D share the data; after that the modified version
// of A is still referenced by B and C.
A = D;
cout << "运行A = D\n";
cout << "A的数据地址:" << (unsigned int)A.datastart << ",数据起始地址:" << (unsigned int)A.data << ",矩阵数据引用次数:" << *A.refcount << '\n';
cout << "B的数据地址:" << (unsigned int)B.datastart << ",数据起始地址:" << (unsigned int)B.data << ",矩阵数据引用次数:" << *B.refcount << '\n';
cout << "C的数据地址:" << (unsigned int)C.datastart << ",数据起始地址:" << (unsigned int)C.data << ",矩阵数据引用次数:" << *C.refcount << '\n';
cout << "D的数据地址:" << (unsigned int)D.datastart << ",数据起始地址:" << (unsigned int)D.data << ",矩阵数据引用次数:" << *D.refcount << '\n' << endl;

// now make B an empty matrix (which references no memory buffers),
// but the modified version of A will still be referenced by C,
// despite that C is just a single row of the original A
B.release();
cout << "运行B.release()\n";
cout << "A的数据地址:" << (unsigned int)A.datastart << ",数据起始地址:" << (unsigned int)A.data << ",矩阵数据引用次数:" << *A.refcount << '\n';
if (0x00 == (B.datastart))
{
cout << "B的数据地址:0x00\n";
}
cout << "C的数据地址:" << (unsigned int)C.datastart << ",数据起始地址:" << (unsigned int)C.data << ",矩阵数据引用次数:" << *C.refcount << '\n';
cout << "D的数据地址:" << (unsigned int)D.datastart << ",数据起始地址:" << (unsigned int)D.data << ",矩阵数据引用次数:" << *D.refcount << '\n' << endl;
// finally, make a full copy of C. As a result, the big modified
// matrix will be deallocated, since it is not referenced by anyone
C = C.clone();
cout << "运行C = C.clone()\n";
cout << "A的数据地址:" << (unsigned int)A.datastart << ",数据起始地址:" << (unsigned int)A.data << ",矩阵数据引用次数:" << *A.refcount << '\n';
cout << "C的数据地址:" << (unsigned int)C.datastart << ",数据起始地址:" << (unsigned int)C.data << ",矩阵数据引用次数:" << *C.refcount << '\n';
cout << "D的数据地址:" << (unsigned int)D.datastart << ",数据起始地址:" << (unsigned int)D.data << ",矩阵数据引用次数:" << *D.refcount << '\n' << endl;
return 0;
}


运行结果:



图3.测试代码2的运行结果
结果分析:前面对照着内存分析过,测试代码2就没照着内存分析了,把结果输出了,有了前面的了解这里通过输出分析一下。有兴趣的话可以自己找着前面的步骤分析,能够有一个更好的认识。
首先 Mat A(10, 10, CV_16U) 运行后,定义了一个Mat 型的矩阵A,同时调用了构造函数生成了矩阵(包含矩阵数据区)。接着对矩阵A的数据进行了一个初始化。运行Mat B = A后,定义一个矩阵B,但是在这里并不对B进行数据内存的分配,B只有一个头文件,和A共用数据存储区。这从输出的数据地址可以看出来,A和B指向的是同一块地址,同时之前提到的refcount指向的数值变为2。运行Mat C
= B.row(3),这里C相当于是一个10x1的行矩阵,其值为矩阵B(也是A)的第三行,同样这里也并不对C进行数据内出的分配,只是把B(A)的第三行数据的地址赋给C的数据起始地址。通过这个语句的执行,可以知道data指针和datastart指针的区别了。data就是指整个被分配的数据块的起始地址,而datastart就是当前矩阵的数据的起始地址。对于A(B)来说,两者是一样的,但是因为C只有一行,所以这两者并不一样了。同时,refcount指向的的值为3。
那么,如果我们想定义个新的矩阵,并且希望给新的矩阵重新分配数据内存,那么该怎么办呢?看下面!Mat D = B.clone(),这时发现有不一样的了,矩阵D的数据地址和A(B)就不一样了,说明给D分配了新的内存块。事实上,clone函数就是专门用来给新矩阵也分配独立内存的。注意,这时A(B)的refcount指向的数没有变为4,还是3,也印证了refcount的作用。运行A = D,改变了A,这时A的数据变为了D的数据了,A和D一样的了。B的refcount减1变为2,而D的refcount则变为2。运行B.release(),就把B给释放掉了,这时B的数据指针都是空指针了,但是最开始分配的第一块数据内存并没有被释放,因为还有C在用这块内存,虽然C只用了一部分,但是还是可以看到C的refcount指向的数变为1,说明这时候就只有C在用这块数据内存了。
最后,再做个小测试。运行C = C.clone(),可能第一眼看不明白这是在干什么。其实这是在重新给C分配内存。这时候最开始的第一条语句 Mat A(10, 10, CV_16U)运行时分配的第一块内存终于释放了!同时注意,给C新分配内存的时候,数据内存的大小只有一行矩阵数据的大小(另外加上四个字节的refcount),这时候C的data和datastart是一样的了!
好了!到目前为止,对于OpenCV2中的 Mat 数据类型已经有了一个比较具体的认识了。不过现在还只是有了一个基本的认识,后面将进一步通过例子,来学习 Mat 的相关操作。这才是最关键的部分!

参考:
OpenCV document
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: