早期的 OpenCV 中,使用 IplImage 和 CvMat 数据结构来表示图像。IplImage和 CvMat 都是 C 语言的结构。使用这两个结构的问题是内存需要手动管理,开发者必须清楚的知道何时需要申请内存,何时需要释放内存。这个开发者带来了一定的负担,开发者应该将更多精力用于算法设计,因此在新版本的 OpenCV 中引入了 Mat 类。
新加入的 Mat 类能够自动管理内存。使用 Mat 类,你不再需要花费大量精力在内存管理上。而且你的代码会变得很简洁,代码行数会变少。但 C++接口唯一的不足是当前一些嵌入式开发系统可能只支持 C 语言,如果你的开发平台支持C++,完全没有必要再用 IplImage 和 CvMat。在新版本的 OpenCV 中,开发者依然可以使用 IplImage 和 CvMat,但是一些新增加的函数只 供了 Mat 接口。
Mat 类的定义如下所示,关键的属性如下方代码所示:
class CV_EXPORTS Mat
{
public:
//一系列函数
...
/* flag 参数中包含许多关于矩阵的信息,如:
-Mat 的标识
-数据是否连续
-深度
-通道数目
*/
int flags;
//矩阵的维数,取值应该大于或等于 2
int dims;
//矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
int rows, cols;
//指向数据的指针
uchar* data;
//指向引用计数的指针
//如果数据是由用户分配的,则为 NULL
int* refcount;
//其他成员变量和成员函数
...
};
补充:
step1(i):每一维元素的通道数
step[i]:每一维元素的大小,单位字节
size[i]:每一维元素的个数
elemSize():每个元素大小,单位字节
elemSize1():每个通道大小,单位字节
每一维的元素表示什么意思呢?
这里我们以空间几何的角度来解释,能够更加容易理解一点。
三维矩阵,一共有三维,我们分别类比为
面:每个二维矩阵,表示第1维的元素
线:矩阵的每一行,表示第2维的元素
点:矩阵中每行的每个元素,表示第3维的元素
那么这样子就可以解释清楚每一维元素的含义了。
以step[i]为例
step[0]:面的大小,第1维的元素的大小,也就是二维矩阵的大小,一个二维矩阵有8行,所以
step[0] = step[1] * 8 = 480
step[1]:线的大小,第2维的元素的大小,也就是二维矩阵每一行的大小,由于每个元素大小为6,每行有10个元素,所以
step[1] = 10 * 6 = 60
step[2]:点的大小,第3维的元素的大小,这里矩阵的每个元素类型为CV_16UC3,所以
step[2] = 2 * 3 = 6
这里注意:
1.step的大小是字节
2.注意下标与维数的对应关系:下标2对应点,1对应线,0对应面
3.矩阵有几维,step[]数组就有几个元素,如3维,则有3个元素,step[0],step[1],step[2].分别对应面,线,点
只要记住,最后一个总是表示点,然后依次向前为线,面...
4.第2,3 点 ,对于size和step1()也一样。
step1(i)和size[]与step[i]原理相同。
常用的构造函数有:
这些构造函数中,很多都涉及到类型 type。type 可以是 CV_8UC1,CV_16SC1,...,CV_64FC4 等。里面的 8U 表示 8 位无符号整数,16S 表示 16 位有符号整数,64F表示 64 位浮点数(即 double 类型);C 后面的数表示通道数,例如 C1 表示一个通道的图像,C4 表示 4 个通道的图像,以此类推。
如果你需要更多的通道数,需要用宏 CV_8UC(n),例如:
Mat M(3,2, CV_8UC(5));//创建行数为3,列数为2,通道数为5的图像
有些 type 参数如 CV_32F未注明通道数目,这种情况下它表示单通道。
常用方法
void copyTo(); //拷贝
Mat clone(); //拷贝
int channels(); //通道,矩阵中的每一个矩阵元素拥有的值的个数
int depth(); //深度,即每一个像素的位数(bits)
bool empty() const; //判断是否为空
uchar* ptr(int i0=0); //指针取第0行数据
void convertTo(oclMat& m, int rtype, double alpha=1, double beta=0);
//m:转为目标数据类型的矩阵;
//rtype: 指定目标数据类型,或者是depth(通道数),如果rtype:是负值,那么目标矩阵的数据类型和源矩形的数据类型是一致的;
//alpha:基于尺度的变化值;
//beta:在尺度上的加和;
Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。复制矩阵数据往往花费较多时间,因此除非有必要,不要复制大的矩阵。
为了解决矩阵数据的传递,OpenCV 使用了引用计数机制。其思路是让每个Mat 对象有自己的矩阵头信息,但多个 Mat 对象可以共享同一个矩阵数据。让矩阵指针指向同一地址而实现这一目的。很多函数以及很多操作(如函数参数传值)只复制矩阵头信息,而不复制矩阵数据。
如果 Mat 类自己申请数据空间,那么该类会多申请 4 个字节,多出的 4 个字节存储数据被引用的次数。引用次数存储于数据空间的后面,refcount 指向这个位置,如图所示。当计数等于 0 时,则释放该空间。
Mat类.jpeg
关于多个矩阵对象共享同一矩阵数据,我们可以看这个例子:
Mat A(100,100, CV_8UC1);
Mat B = A;
Mat C = A(Rect(50,50,30,30));
上面代码中有三个Mat对象,分别是A,B和C。这三者共有同一矩阵数据,其示意图如图:
三个矩阵头共用共用同一矩阵数据.jpeg
部分复制:一般情况下只会复制Mat对象的头和指针部分,不会复制数据部分。如:
Mat A = imread(filePath);
Mat B = A;
完全复制:如果想把Mat对象的头部和数据部分一起复制,如下:
Mat F = A.clone();
Mat G;
A.copyTo(G);
四个要点:
除了在构造函数中可以创建图像,也可以使用 Mat 类的 create()函数创建图像。如果 create()函数指定的参数与图像之前的参数相同,则不进行实质的内存申请操作;如果参数不同,则减少原始数据内存的索引,并重新申请内存。使用方法如下面例程所示:
Mat M(2,2, CV_8UC3);//构造函数创建图像
M.create(3,2, CV_8UC2);//释放内存重新创建图像
需要注意的时,使用 create()函数无法设置图像像素的初始值。
OpenCV 2 中 供了 Matlab 风格的函数,如 zeros(),ones()和 eyes()。这种方法使得代码非常简洁,使用起来也非常方便。使用这些函数需要指定图像的大小和类型,使用方法如下:
Mat Z = Mat::zeros(3, 3, CV_8UC1);
cout << "Z = " << endl << " " << Z << endl;
Mat O = Mat::ones(3, 3, CV_32F);
cout << "O = " << endl << " " << O << endl;
Mat E = Mat::eye(3, 3, CV_64F);
cout << "E = " << endl << " " << E << endl;
show.jpeg
1.Mat 转为 IplImage 和 CvMat 格式
假如你有一个以前写的函数,函数的定义为:
void mycvOldFunc(IplImage * p, ...);
函数的参数需要 IplImage 类型的指针。Mat 转为 IplImage,可以用简单的等号赋值操作来进行类型转换,这样实现:
Mat img(Size(320, 240), CV_8UC3);
...
IplImage iplimg = img; //转为IplImage结构
mycvOldFunc( & iplimg, ...);//对 iplimg 取地址
如果要转为 CvMat 类型,操作类似:
CvMat cvimg = img; //转为CvMat结构
需要特别注意的是,类型转换后,IplImage 和 CvMat 与 Mat 共用同一矩阵数据,而 IplImage 和 CvMat 没有引用计数功能,如果上例中的 img 中数据被释放,iplimg 和 cvimg 也就失去了数据。因此要牢记不可将 Mat 对象 前释放。
2.IplImage 和 CvMat 格式转为 Mat
Mat 类有两个构造函数,可以实现 IplImage 和 CvMat 到 Mat 的转换。这两个函数都有一个参数 copyData。如果 copyData 的值是 false,那么 Mat 将与 IplImage或 CvMat 共用同一矩阵数据;如果值是 true,Mat 会新申请内存,然后将 IplImage或 CvMat 的数据复制到 Mat 的数据区。
如果共用数据,Mat 也将不会使用引用计数来管理内存,需要开发者自己来管理。
Mat::Mat(const CvMat* m, bool copyData=false)
Mat::Mat(const IplImage* img, bool copyData=false)
例子代码如下:
IplImage * iplimg = cvLoadImage("lena.jpg");
Mat im(iplimg, true);