废话内容,写在前面:
Mat是个什么玩意?
2015年下半年我在深圳的时候,一边看C++的入门,一边敲一点点和工作有关的代码。有一天飞哥和勇哥终于按耐不住觉得我总是测试光学打光做电子元器件可能就真的要废了,便让我用Visual Studio把一张图片用窗口显示出来,然后对图片进行二值化处理,提取特征值。
我印象特别深刻,方法函数里面涉及到了好多Mat,如今开始动OpenCV第一个碰到的东西就是Mat,在这里把研读的关于Mat类,记录一下。
OpenCV官方文档是这样界定Mat的:基本的图像容器。
处理一张图片为何要用到图像的容器这种定义的概念?没有《图像处理技术》基础知识的人可能不太能懂图像为何需要一个基本的图像容器去处理。我本科学的《图像处理技术》的基础内容现在也不太记得,脑海中只有一些隐约的印象:图像是很多像素点构成,每个像素点有具体的像素值(0~255),一张二维平面上的图片,它上面所有的点平面坐标位置,有点像《线性代数》里的矩阵,处理一张图片就是处理图片上所包含的像素值的信息,所以图片的处理,就可以转换成矩阵运算。
正如OpenCV官方文档上说到的:如何获取并存储这些像素值由我们的需求而定,最终在计算机世界里所有图像都可以简化为数值矩以及矩阵信息。作为一个计算机视觉库, OpenCV 其主要目的就是通过处理和操作这些信息,来获取更高级的信息。因此,OpenCV如何存储并操作图像是你首先要学习的。
Mat的发展情况:
在2001年刚刚出现的时候,OpenCV基于C语言接口而建。为了在内存(memory)中存放图像,当时采用名为IplImage的C语言结构体,时至今日这仍出现在大多数的旧版教程和教学材料。但这种方法必须接受C语言所有的不足,这其中最大的不足要数手动内存管理,其依据是用户要为开辟和销毁内存负责。虽然对于小型的程序来说手动管理内存不是问题,但一旦代码开始变得越来越庞大,你需要越来越多地纠缠于这个问题,而不是着力解决你的开发目标。
幸运的是,C++出现了,并且带来类的概念,这给用户带来另外一个选择:自动的内存管理(不严谨地说)。这是一个好消息,如果C++完全兼容C的话,这个变化不会带来兼容性问题。为此,OpenCV在2.0版本中引入了一个新的C++接口,利用自动内存管理给出了解决问题的新方法。使用这个方法,你不需要纠结在管理内存上,而且你的代码会变得简洁(少写多得)。但C++接口唯一的不足是当前许多嵌入式开发系统只支持C语言。所以,当目标不是这种开发平台时,没有必要使用旧方法(除非你是自找麻烦的受虐狂码农)。
关于 Mat ,首先要知道的是你不必再手动地(1)为其开辟空间(2)在不需要时立即将空间释放。但手动地做还是可以的:大多数OpenCV函数仍会手动地为输出数据开辟空间。当传递一个已经存在的 Mat 对象时,开辟好的矩阵空间会被重用。也就是说,我们每次都使用大小正好的内存来完成任务。
基本上讲 Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。因此,当在程序中传递图像并创建拷贝时,大的开销是由矩阵造成的,而不是信息头。OpenCV是一个图像处理库,囊括了大量的图像处理函数,为了解决问题通常要使用库中的多个函数,因此在函数中传递图像是家常便饭。同时不要忘了我们正在讨论的是计算量很大的图像处理算法,因此,除非万不得已,我们不应该拷贝 大 的图像,因为这会降低程序速度。
为了搞定这个问题,OpenCV使用引用计数机制。其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。而拷贝构造函数则 只拷贝信息头和矩阵指针 ,而不拷贝矩阵。
关于 Mat 内存管理的问题:
如果矩阵属于多个 Mat 对象,那么当不再需要它时谁来负责清理?简单的回答是:最后一个使用它的对象。通过引用计数机制来实现。无论什么时候有人拷贝了一个 Mat 对象的信息头,都会增加矩阵的引用次数;反之当一个头被释放之后,这个计数被减一;当计数值为零,矩阵会被清理。但某些时候你仍会想拷贝矩阵本身(不只是信息头和矩阵指针),这时可以使用函数 clone() 或者 copyTo() 。
1.OpenCV函数中输出图像的内存分配是自动完成的(如果不特别指定的话)。
2.使用OpenCV的C++接口时不需要考虑内存释放问题。
3.赋值运算符和拷贝构造函数( ctor )只拷贝信息头。
4.使用函数 clone() 或者 copyTo() 来拷贝一副图像的矩阵。
以上是对图像以及 Mat类发展,以及OpenCV在处理图像时的内存管理的一个简单的了解。
接下来要了解一下OpenCV是如何存储图像的:
这里讲述如何存储像素值。需要指定颜色空间和数据类型。颜色空间是指对一个给定的颜色,如何组合颜色元素以对其编码。最简单的颜色空间要属灰度级空间,只处理黑色和白色,对它们进行组合可以产生不同程度的灰色。
对于 彩色 方式则有更多种类的颜色空间,但不论哪种方式都是把颜色分成三个或者四个基元素,通过组合基元素可以产生所有的颜色。RGB颜色空间是最常用的一种颜色空间,这归功于它也是人眼内部构成颜色的方式。它的基色是红色、绿色和蓝色,有时为了表示透明颜色也会加入第四个元素 alpha (A)。
有很多的颜色系统,各有自身优势:
1.RGB是最常见的,这是因为人眼采用相似的工作机制,它也被显示设备所采用。
2.HSV和HLS把颜色分解成色调、饱和度和亮度/明度。这是描述颜色更自然的方式,比如可以通过抛弃最后一个元素,使算法对输入图像的光照条件不敏感。
3.YCrCb在JPEG图像格式中广泛使用。
4.CIE L a b *是一种在感知上均匀的颜色空间,它适合用来度量两个颜色之间的距离。
每个组成元素都有其自己的定义域,取决于其数据类型。如何存储一个元素决定了我们在其定义域上能够控制的精度。最小的数据类型是 char ,占一个字节或者8位,可以是有符号型(0到255之间)或无符号型(-127到+127之间)。尽管使用三个 char 型元素已经可以表示1600万种可能的颜色(使用RGB颜色空间),但若使用float(4字节,32位)或double(8字节,64位)则能给出更加精细的颜色分辨能力。但同时也要切记增加元素的尺寸也会增加了图像所占的内存空间。
关于Mat类的创建,具体的代码和内容可以查阅OpenCV中文网里的相关入门教学,当然还要具备一点点基础的C++入门知识。
待续以后会更新相关内容,等我把C++入门摸一下。