Mat - 基本图像容器
世间的图像是各种各样的,但是到了计算机的世界里所有的图像都简化为了数值矩以及矩阵信息。作为一个计算视觉库,OpenCV的主要目的就是处理和操作这些信息,来获取更高级的信息,也就是潜在的价值。因此,我们需要首先学习OpenCV是如何存储并操作图像的。
Mat
2001年OpenCV刚刚出现的时候是基于C语言接口而建。 为了在内存(Memory)中存放图像, 当时采用的是名为IplImage(Intel Image Processing Library IPL)的C语言结构体,最大的问题就是需要手动的进行内存管理。在OpenCV2.0版本中出现了C++接口, 利用自动内存管理(说法不严谨)给出了解决问题的新方法。
Mat是一个类,包含两部分数据部分:
-
(1)矩阵头(包含矩阵尺寸、存储方法、存储地址等信息)
其中矩阵头的尺寸是常数值,矩阵本身的尺寸根据图像的大小而定。
当在程序中传递图像并创建拷贝时,大的开销是由矩阵造成的,而不是信息头,但是在OpenCV函数中矩阵的传递又是非常常见的。
(2)指针(指向存储所有像素值的矩阵)
为了解决矩阵开销问题,OpenCV采用了引用计数机制。其思路就是每个Mat对象都有自己的头信息, 但共享同一个矩阵,通过矩阵指针指向同一个地址而实现。而拷贝构造函数而只拷贝信息头和矩阵指针,不拷贝矩阵。
Mat A, C; //只创建信息头部分
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); //这里为矩阵开辟内存
Mat B(A); //使用拷贝构造函数
C = A; //赋值运算符
说明: A, B, C三个Mat对象都指向了同一个也是唯一一个数据矩阵。虽然他们的信息头不同, 但通过任何一个对象所做的改变都会影响其他对象。实际上,不同的对象只是访问相同数据矩阵的不同途径而已。
还可以创建只引用部分数据的信息头,比如创建一个感兴趣区域(ROI),你只需要创建包含边界信息的信息头:
Mat D(A, Rect(10, 10, 100, 100)); //使用一个矩阵
Mat E = A(Range:all(), Range(1, 3)); //使用行和列表示边界
如果矩阵属于多个Mat对象,那么当不再需要它时谁来负责清理?简单的回答是:最后一个使用它的对象。通过引用计数机制来实现。无论什么时候有人考虑了一个Mat对象的信息头,都会增加矩阵的引用次数;反之当一个头被释放之后,这个计数被减一;当计数为零的时候,矩阵会被清理。但某些时候我们仍然会想拷贝矩阵本身(不只是信息头和矩阵指针),这个时候可以使用clone()或者copyTo()函数。
Mat F = A.clone();
Mat G;
A.copyTo(G);
现在改变F或者G就不会影响Mat信息头所指向的矩阵。总结一下,你需要记住的是:
OpenCV函数中输出图像的内存分配是自动完成的(如果不特别指定的话)。
使用OpenCV的C++接口时不需要考虑内存释放问题。
赋值运算符和拷贝构造函数(constructor--ctor)只拷贝信息头。
使用函数clone()或者copyTo()来拷贝一副图像的矩阵。
显示地创建一个Mat对象
- Mat()构造函数
Mat M(2, 2, CV_8UC3, Scalar(0, 0, 255));
cout << "M = " << endl << " " << M << endl;
对于二维多通道图像,首先要定义其尺寸,即行数和列数。
然后需要指定元素的数据类型以及每个矩阵点的通道数。
CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
比如CV_8UC3表示使用8位的unsigned char型,每个像素由三个元素组成的三通道。预先定义的通道数可以多达四个。 Scalar是short型vector。指定这个可以使用指定的定制化值来初始化矩阵。当然,如果你需要更多维数,你可以使用宏并把通道数放在小括号中,如下所示
在C\C++中通过构造函数进行初始化
int sz[3] = {2, 2, 2};
Mat L(3, sz, CV_8UC1, Scalar::all(0));
上面的例子演示了如何创建一个超过两维的矩阵:指定维数,然后传递一个指向一个数组的指针,这个数组包含每个维度的尺寸;其余的相同
为已存在IplImage指针创建信息头:
IplImage* img = cvLoadImage("greatwave.jpg", 1);
Mat mtx(img); //convert IplImage* -> Mat
- Create() function: 函数
M.create(4, 4, CV_8UC(2));
cout << "M = "<< endl << " " << M << endl << endl;
这个创建方法不能为矩阵设初值,它只是在改变尺寸时重新为矩阵数据开辟内存。
- MATLAB形式的初始化: zeros(),eyes(),ones();
Mat E = Mat::eye(4, 4, CV_64F);
cout << "E = " << endl << " " << E << endl << endl;
Mat O = Mat::ones(2, 2, CV_32F);
cout << "O = " << endl << " " << O << endl << endl;
Mat Z = Mat::zeros(3,3, CV_8UC1);
cout << "Z = " << endl << " " << Z << endl << endl;
- 对于小矩阵可以使用逗号分隔的方式初始化
Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cout << "C = " << endl << " " << C << endl << endl;
- 使用clone()或copyTo()为一个存在的Mat对象创建一个新的信息头
Mat RowClone = C.row(1).clone();
cout << "RowClone = " << endl << " " << RowClone << endl << endl;
格式化打印
- 调用函数randu()来对一个矩阵使用随机数填充,需要指定随机数的上界和下界:
Mat R = Mat(3, 2, CV_8UC3);
randu(R, Scalar::all(0), Scalar::all(255));
- 默认打印方式
cout << "R (default) = " << endl << R << endl << endl;