[TOC]

概述

文章参考:https://blog.csdn.net/CV_Jason/article/details/54928920

文章参考:https://zhuanlan.zhihu.com/p/265085433

概述

OpenCV 作为强大的计算机视觉开源库,很大程度上参考了MatLab的实现细节和语法风格,比如说,在OpenCV2.x版本以后,越来越多的函数实现了MatLab所具有的功能,甚至干脆连函数名都一模一样(如 imread, imshow,imwriter等)。这一做法,不仅拉近了产品开发与学术研究的距离,并极大程度的提高了开发人员的研发效率,不得不说,Intel公司真的是一个伟大的公司。

在计算机内存中,数字图像以矩阵的形式存储和运算,比如,在MatLab中,图像读取之后对应一个矩阵,在OpenCV中,同样也是如此。

在早期的OpenCV1.x版本中,图像的处理是通过IplImage(该名称源于Intel的另一个开源库Intel Image Processing Library ,缩写成IplImage)结构来实现的。早期的OpenCV是用C语言编写,因此提供的借口也是C语言接口,其源代码完全是C的编程风格。IplImage结构是OpenCV矩阵运算的基本数据结构。

到OpenCV2.x版本,OpenCV开源库引入了面向对象编程思想,大量源代码用C++重写,Mat类 (Matrix的缩写) 是OpenCV用于处理图像而引入的一个封装类。从功能上讲,Mat类在IplImage结构的基础上进一步增强,并且,由于引入C++高级编程特性,Mat类的扩展性大大提高,Mat类的内容在后期的版本中不断丰富,如果你查看Mat类的定义的话(OpenCV3.1\sources\modules\core\include\opencv2\core\mat.hpp),会发现其设计实现十分全面而具体,基本覆盖计算机视觉对于图像处理的基本要求。

img

因此,在当前的OpenCV开发中,Mat可以说是最最最常见的数据单元,深入了解Mat类对于OpenCV深入开发有着重大意义。

Mat类常用成员函数和成员变量

Mat类十分庞大,其所涉及的成员函数和变量难以一一细数,在这里,仅学习记录其最最最常见的部分,以便日常使用。

构造函数

默认构造函数

1
cv::Mat::Mat()

默认构造函数,生成一个矩阵并由OpenCV提供的函数(一般是Mat::create() 和 cv::imread() )来分配储存空间。

Mat类可以分为两个部分:矩阵头指向像素数据的矩阵指针

矩阵头 包括数字图像的矩阵尺寸、存储方法、存储地址和引用次数等,矩阵头的大小是一个常数,不会随着图像的大小而改变,但是保存图像像素数据的矩阵则会随着图像的大小而改变,通常数据量会很大,比矩阵头大几个数量级。这样,在图像复制和传递过程中,主要的开销是由存放图像像素的矩阵而引起的。因此,OpenCV使用了引用次数,当进行图像复制和传递时,不再复制整个Mat数据,而只是复制矩阵头和指向像素矩阵的指针,例如:

1
2
3
cv::Mat a ; //默认构造函数,创建矩阵头
a = cv::imread("test.jpg");//读入图像,矩阵指针指向该像素数据
cv::Mat b = a ;//复制 

上面的a,b有各自的矩阵头,但是其矩阵指针指向同一个矩阵,也就是其中任何一个改变了矩阵数据都会影响另外一个。
那么,多个Mat共用一个矩阵数据,最后谁来释放矩阵数据呢?
这就是引用计数的作用,当Mat对象每被复制一次时,就会将引用计数加1,而每销毁一个Mat对象(共用同一个矩阵数据)时引用计数会被减1,当引用计数为0时,矩阵数据会被清理。

type()函数

cv::Mat 类的对象有一个成员函数 type() 用来返回矩阵元素的数据类型,返回值是 int 类型,不同的返回值代表不同的类型。OpenCV Reference Manual 中对 type() 的解释如下所示:

1
2
3
Mat::type
C++: int Mat::type() const
The method returns a matrix element type. This is an identifier compatible with the CvMat type system, like CV_16SC3 or 16-bit signed 3-channel array, and so on.

实际的代码如下所示:

1
2
3
cv::Mat haha = cv::Mat::zeros(3,3,CV_64F);
int hahaType = haha.type();
std::cout<<"hahaType = "<<hahaType<<std::endl;

至此,知道了 type() 函数,下一步更关键的就是返回值和具体类型之间的对应关系了。文章《[LIST OF MAT TYPE IN OPENCV][LIST OF MAT TYPE IN OPENCV]》对此整理得非常清楚,具体如下表所示:

C1 C2 C3 C4
CV_8U 0 8 16 24
CV_8S 1 9 17 25
CV_16U 2 10 18 26
CV_16S 3 11 19 27
CV_32S 4 12 20 28
CV_32F 5 13 21 29
CV_64F 6 14 22 30

表头的 C1, C2, C3, C4 指的是通道(Channel)数,比如灰度图像只有 1 个通道,是 C1;JPEG格式 的 RGB 彩色图像就是 3 个通道,是 C3;PNG 格式的彩色图像除了 RGB 3个通道外,还有一个透明度通道,所以是 C4。大家还会发现 7 怎么没有被定义类型,这个可以看 OpenCV 源码,有如下所示的一行,说明 7 是用来给用户自定义的

1
#define CV_USRTYPE1 7

如果仅仅是为了在数值计算前明确数据类型,那么看到这里就可以了;如果是要使用 at 方法访问数据元素,那么还需要下面一步。因为以单通道为例,at 方法接受的是 uchar 这样的数据类型,而非 CV_8U。在已知通道数和每个通道数据类型的情况下,指定给 at 方法的数据类型如下表所示:

只要认识到,最关键的数据类型其实只有0~6,其他的,都是从0~6扩展通道而已,至于扩展几维,按照你的实际情况来就好。这里再解释一下,0~6的数据类型对应的数据范围:

无符号8bit整型(最常用):Unsigned 8bits uchar 0~255
IplImage: IPL_DEPTH_8U
Mat: CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4

有符号8bit整型:Signed 8bits char -128~127
IplImage: IPL_DEPTH_8S
Mat: CV_8SC1,CV_8SC2,CV_8SC3,CV_8SC4

无符号16bit整型:Unsigned 16bits ushort 0~65535
IplImage: IPL_DEPTH_16U
Mat: CV_16UC1,CV_16UC2,CV_16UC3,CV_16UC4

有符号16bit整型:Signed 16bits short -32768~32767
IplImage: IPL_DEPTH_16S
Mat: CV_16SC1,CV_16SC2,CV_16SC3,CV_16SC4

有符号32bit整型:Signed 32bits int -2147483648~2147483647
IplImage: IPL_DEPTH_32S
Mat: CV_32SC1,CV_32SC2,CV_32SC3,CV_32SC4

32bit浮点数:Float 32bits float -1.18*10-38~3.40*10-38
IplImage: IPL_DEPTH_32F
Mat: CV_32FC1,CV_32FC2,CV_32FC3,CV_32FC4

64bit浮点数:Double 64bits double
Mat: CV_64FC1,CV_64FC2,CV_64FC3,CV_64FC4