今天继续学习,接着前一条博客介绍的,继续OpenCV英文教程的学习。
2.1 Mat - 基础图像容器
Mat
早期的OpenCV采用C语言实现,表示图像的C语言数据结构是IplImage。使用C语言接口的最大问题是手工内存管理,需要手动的分配和释放内存。OpenCV 2.0以后引进了C++接口,用户不用再纠结于内存管理,使代码更简洁明了(less to write, to achieve more)。但采用C++唯一的短板是许多嵌入式开发系统当前只支持C语言,如果是这样,就只能采用早期的C语言实现的OpenCV方案了。
Mat是用于存放图像的容器类,使用Mat最大的好处是不用手动分配和释放内存了(当然手动管理内存也是可以的)。Mat类由两部分组成:矩阵头(matrix header)和指向包含像素值的矩阵的指针。矩阵头包含了一些图像的格式信息:矩阵大小,存储方式,存放矩阵的地址等。矩阵头的大小是固定的,而具体存放图像像素值的矩阵大小随着存储图像的大小变化。
由于OpenCV是用于图像处理的库,包含了大量图像处理函数。因此经常需要把图像作为参数传递给函数。为了加快传递参数的速度,避免费时的实参->形参的复制,OpenCV采用引用计数机制。每个Mat对象拥有自己的矩阵头,而具体存放图像的矩阵是可以被Mat对象共享的。两个Mat对象的指针可以指向同一个矩阵地址。自然,可以理解,Mat类的拷贝构造函数和赋值操作只会复制矩阵头和指针,而不会复制图像数据。
Mat A, C;
A = imread(argv[1], CV_LOAD_IMAGE_COLOR);
Mat B(A);
C = A;
上面例子中,最后A,B,C三个Mat对象的指针全都指向同一个地址。因此,改变其中一个对象指针指向的内容,其他两个对象也会受到影响。实际上这三个不同的对象只是提供了对同一块数据区域的不同访问方法。
也可以提取一块数据区域中的子区域。例如创建一个图像的感兴趣区域(region of interest,ROI)可以采用如下方式。
Mat D (A, Rect(10, 10, 100, 100 ) ); // 提取一个矩形子区域,左上角起点坐标为(10,10),长宽为100,100
Mat E = A(Range::all(), Range(1,3)); // A对象对应的图形中,按行列所有行,第1到3列 的子区域
这样多个Mat对象共享一块存储图像矩阵的内存,那么如何分配和释放内存呢?——引用计数机制。每拷贝一次Mat对象的头,存储图像的数据矩阵对应的计数+1。当一个头被清除时,计数-1。如果计数减到0,数据矩阵占用的内存被释放。而如果要复制整个数据矩阵,而不是仅仅复制Mat对象的头,可以使用如下两个函数。
Mat F = A.clone();
Mat G;
A.copyTo(G);
存储方法
颜色空间:
RGB+Alpha(A),RGB是红绿蓝三原色,Alpha代表透明度。最常见。
HSV和HLS,颜色分量为色相,饱和度和亮度,是一种描述颜色更为自然的方法。在需要尽量降低光照影响的时候,可以去除最后的亮度分量。
YCrCb,流行于JPEG图像格式。
CIE L*a*b,是一个感知均匀颜色空间,可以很容易测量一个颜色到另一个颜色的距离。
每一种颜色分量都有自己独立的用处,从而决定所采用的数据类型以及相关操作。
显式创建Mat对象
Mat() 构造函数初始化二维Mat对象
Mat M(2,2, CV_8UC3, Scalar(0,0,255));
创建一个Mat对象,两行两列,采用8位无符号3通道矩阵,红色。
矩阵数据类型定义如下:
CV_[The number of bits per item] [S|U|F] [Type Prefix] C [The channel number] 。
如CV_8UC3表示 8位的无符号三通道矩阵。
使用C++数组初始化多维Mat对象
int sz[3] = {2,2,2};
Mat L(3,sz, CV_8UC(1), Scalar::all(0) );
如上面所示,先指定维数,然后传递包含每个维数大小的数组指针,后面同上。
用已存在的IplImage指针创建一个Mat头
IplImage* img = cvLoadImage("greatwave.png", 1);
Mat mtx(img); // IplImage* -> Mat
Create()函数: M.create(4,4, CV_8UC(2) );
不能使用此构造方式初始化矩阵值。它只在新矩阵大小和旧矩阵不同时重新分配它的矩阵数据内存。
Matlab风格的初始化: zeros(),ones(),eye()。指定大小和数据类型:
Mat E = Mat::eye(4, 4, CV_64F);
Mat O = Mat::ones(2, 2, CV_32F);
Mat Z = Mat::zeros(3, 3, CV_8UC1);
逗号分隔值的初始化
Mat C = (Mat(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
clone()或copyTo() 函数,前面已经提过。
可以使用randu()函数用随机数填充矩阵。需要指定随机数的上界和下界。
Mat R = Mat(3, 2, CV_8UC3);
randu(R, Scalar::all(0), Scalar::all(255));
输出格式
default,python,csv,numpy,c;
cout << format(R,"python") << endl;
<< 操作符还支持其他OpenCv数据结构的输出,如 2D Point,3DPoint,vector等。