2修正
我们有多种方法从现实世界中获取数字图像,如数码相机、扫描仪、计算机断层扫描和核磁共振成像等。在任何情况下,我们(人类)看到的都是图像。而当我们将其转换存入数字设备时,记录的则是图像的每个点的数值。
例如,在上面的图片中,你可以看到,汽车车镜只不过是一个包含了像素点的所有强度值的矩阵。我们如何获取和存储像素值可能根据我们的需要而变化,但最终,计算机世界中的所有图像都可能被简化为数字矩阵和描述矩阵本身的其他信息。OpenCV是一个计算机视觉库,它的主要焦点是处理和操作这些信息。因此,您需要熟悉的第一件事是OpenCV如何存储和处理图像。
Mat
OpenCV出现于2001年。当时这个库是围绕一个C接口构建的,并将图像存储在内存中,他们使用了一个名为IplImage的C结构。这在大多数旧教程和教材中都能看到。它麻烦在把C语言的所有缺点都推上台面。最大的问题在手动内存管理。它建立在用户负责处理内存分配和回收的假设之上。尽管这不是小程序的问题,但是,一旦您的代码库增长了,那么处理这些问题的难度将超过您的开发目标。
所幸,C++出现了,并引入了类的概念,通过自动内存管理(或多或少)使用户更容易。好消息是,C++与C完全兼容,因此不可能产生兼容性问题。因此,opencv2.0引入了一个新的C++接口,它提供了一种新的做事方式,这意味着您不需要修改内存管理,使您的代码简洁(更少写,以获得更多)。C++接口的主要缺点是,目前许多嵌入式开发系统只支持C.因此,除非您的目标是嵌入式平台,否则使用旧的方法是没有意义的(除非您是一个受虐待的程序员,并且您正在自找麻烦)
关于Mat,您需要知道的第一件事是,您不再需要手动分配它的内存,并在您不需要它的时候立即释放它。虽然这仍然是一种可能,但大多数OpenCV函数都会自动分配它的输出数据。如果你传递一个已经存在的Mat对象,它已经为矩阵分配了所需的空间,那么这将被重用。换句话说,我们在任何时候都只使用我们执行任务所需的内存。
Mat基本上是一个类有两个数据部分:矩阵头(包含信息矩阵的大小等,用于存储的方法,在解决矩阵存储,等等)和一个指向包含像素值的矩阵(采取任何维数取决于选择的方法来存储)。矩阵头的大小是恒定的,但是矩阵本身的大小可能因图像而异,而且通常是按数量级放大的。
OpenCV是一个图像处理库。它包含大量的图像处理功能。要解决计算难题,大多数情况下,您最终会使用库的多个功能。正因为如此,将图像传递给函数是一种常见的做法。我们不应该忘记,我们讨论的是图像处理算法,它的计算量非常大。我们最不想做的事情就是通过不必要地复制潜在的大图片来进一步降低程序的速度。
为了解决这个问题,OpenCV使用一个引用计数系统。其思想是,每个Mat对象都有自己的头,但是矩阵可以通过它们的矩阵指针指向同一个地址来共享它们之间的两个实例。而且,复制操作符只会复制标题和指向大矩阵的指针,而不是数据本身。
Mat A, C;// creates just the header parts
A =imread(argv[1],IMREAD_COLOR);// here we'll know the method used (allocate matrix)
Mat B(A);// Use the copy constructor
C = A;// Assignment operator
所有上面的对象,最后都指向同一个单一的数据矩阵。但是,它们的标题是不同的,并且使用它们中的任何一个进行修改也会影响到所有其他的。在实践中,不同的对象只是为相同的底层数据提供不同的访问方法。然而,它们的头部分是不同的。真正有趣的部分是,您可以创建只引用完整数据的子部分的头部。例如,在一个图像中创建一个感兴趣的区域(ROI),您只需创建一个带有新边界的新标头
Mat D (A,Rect(10, 10, 100, 100) );// using a rectangle
Mat E = A(Range::all(), Range(1,3));// using row and column boundaries
现在你可能会问,这个矩阵本身是否属于多个垫子对象,当它不再需要时,它负责清理它。简短的回答是:最后一个使用它的对象。这是通过使用引用计数机制来处理的。每当有人复制一个垫子对象的头,就会为矩阵增加一个计数器。当一个头被清理时,这个计数器就会减少。当计数器达到零时,矩阵也被释放了。有时您也想要复制矩阵本身,所以OpenCV提供了cv:::克隆()和cv::copyTo()函数。
Mat F = A.clone();
Mat G;
A.copyTo(G);
现在修改F或G不会影响到由垫子头指向的矩阵。您需要记住的是:OpenCV函数的输出图像分配是自动的(除非另有说明)。您不需要使用OpenCVs C++接口来考虑内存管理。赋值运算符和复制构造函数只复制标题。图像的底层矩阵可以使用cv:::克隆()和cv::copyTo()函数来复制。
存储方法是关于如何储存像素值的。您可以选择使用的颜色空间和数据类型。颜色空间指的是我们如何将颜色组件组合成一种特定的颜色。最简单的是灰色的比例,我们可以使用的颜色是黑白的。这些组合可以让我们创造出许多灰色的阴影。
对于丰富多彩的方式,我们有更多的方法可供选择。每一个都将其分解为3或4个基本组成部分,我们可以使用这些组合来创建其他的组件。最受欢迎的是RGB,主要是因为这也是我们的眼睛是如何构建颜色的。它的底色是红色、绿色和蓝色。要对颜色的透明度进行编码,有时会有第四个元素:alpha(A)被添加。然而,还有很多其他的颜色系统都有各自的优势.
RGB是最常见的,因为我们的眼睛使用的是类似的东西,但是请记住,OpenCV标准显示系统使用BGR颜色空间(红色和蓝色通道的开关)来组合颜色。
YCrCb被流行的JPEG图像格式使用。
如果你需要测量一种颜色到另一种颜色的距离,那么它就会很方便。
每个建筑组件都有自己的有效域。这将导致使用的数据类型。我们如何存储一个组件定义了我们对其领域的控制。最小的数据类型可能是char,这意味着一个字节或8位。这可能是无符号的(也可以将值从0到255)或签名(从-127到+127的值)。尽管在三个组件的情况下,这已经提供了1600万种可能的颜色(如RGB),但是我们可以通过使用浮点数(4字节=32位)或双(8字节=64位)数据类型来获得更精确的控制。然而,再保险
显式地创建Mat对象
在Load、修改和保存一个图像教程中,您已经学习了如何通过使用cv::imwrite()函数来将矩阵写入到图像文件中。但是,为了便于调试,可以更方便地看到实际的值。你可以用Mat的运算符来做这个,要知道这只适用于二维矩阵。
虽然Mat作为一个图像容器很好地工作,但它也是一个通用的矩阵类。因此,创建和操作多维矩阵是可能的。您可以以多种方式创建Mat对象
cv::Mat::Mat Constructor
Mat M(2,2,CV_8UC3,Scalar(0,0,255));
cout <<"M = "<< endl <<" "<< M << endl << endl;
image对于二维和多通道的图像,我们首先定义它们的大小:行和列计数。然后,我们需要指定用于存储元素的数据类型和每个矩阵点的通道数量。为了做到这一点,我们根据下面的约定构造了多个定义
CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
例如,CV 8UC3意味着我们使用的是8位长的无符号字符类型,每个像素有三个组成三个通道。这是预先定义的最多4个通道号码。cv:标量是四个元素的短向量。指定这一点,您可以用定制值初始化所有矩阵点。如果您需要更多,您可以使用上面的宏创建类型,在括号中设置通道号,如下所示。