阅读(2425) (9)

Mat-基本图像容器

2017-08-29 10:36:14 更新

目标

我们有多种方式从现实世界中获取数字图像:数码相机,扫描仪,计算机断层扫描和磁共振成像等等。在任何情况下,我们(人类)看到的都是图像。然而,当将其转换为数字设备时,我们记录的是图像中每个点的数值。

Mat-基本图像容器

例如在上述图像中,您可以看到汽车的镜像只不过是一个包含像素点所有强度值的矩阵。我们如何获取和存储像素值可能会根据我们的需要而有所不同,但最终,计算机世界内的所有图像可能会被减少到描述矩阵本身的数字矩阵和其他信息。OpenCV是一个计算机视觉库,其主要重点是处理和操纵这些信息。因此,您需要熟悉的第一件事是OpenCV如何存储和处理图像。

Mat

OpenCV自2001年以来一直存在。在那些日子里,库是围绕C接口构建的,并将图像存储在内存中,它们使用了一个称为IplImage的C结构。这是大部分老版本的教程和教材。这样做的问题是它带来了C语言的所有缺点。最大的问题是手动内存管理。它建立在用户负责处理内存分配和释放的假设的基础上。虽然这不是一个较小的程序的问题,但一旦你的代码基础的增长,处理这些代码就更难,而不是专注于解决你的发展目标。

幸运的是C ++来了,并介绍了类的概念,使用户更容易通过自动内存管理(或多或少)。好消息是,C ++与C完全兼容,所以在进行更改时不会出现任何兼容性问题。因此,OpenCV 2.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

现在您可以询问矩阵本身是否属于多个Mat对象,它们在不再需要时负责清理它。简短的答案是:使用它的最后一个对象。这是通过使用引用计数机制来处理的。每当有人复制Mat对象的标题时,矩阵的计数器就会增加。每当头部被清洁时,这个计数器就会减少。当计数器达到零时,矩阵也被释放。有时你也想复制矩阵本身,所以OpenCV提供了cv :: Mat :: clone()cv :: Mat :: copyTo()函数。

Mat F = A.clone();
Mat G;
A.copyTo(G);

现在修改F或G不会影响Mat头指向的矩阵。所有这一切你需要记住的是:

  • OpenCV功能的输出图像分配是自动的(除非另有说明)。
  • 您不需要考虑OpenCVs C ++界面的内存管理。
  • 赋值运算符和复制构造函数只复制标题。
  • 可以使用cv :: Mat :: clone()cv :: Mat :: copyTo()函数复制图像的基础矩阵。

存储方法

这是关于如何存储像素值。您可以选择使用的颜色空间和数据类型。颜色空间是指我们如何组合颜色分量以编码给定的颜色。最简单的一个是灰色,我们可以使用的颜色是黑色和白色。这些组合使我们能够创建许多灰色阴影。

对于丰富多彩的方式,我们有更多的选择方法。他们每个人都将它们分解成三到四个基本组件,我们可以使用这些组合来创建其他组件。最流行的是RGB,主要是因为这也是我们的眼睛如何建立颜色。其基色为红,绿,蓝。为了编码颜色的透明度有时是第四个元素:添加了α(A)。

然而,还有许多其他颜色系统都有自己的优势:

  • RGB是最常见的,因为我们的眼睛使用类似的东西,但请记住,OpenCV标准显示系统使用BGR颜色空间(红色和蓝色通道的开关)组成颜色。
  • HSV和HLS将颜色分解为色调,饱和度和值/亮度分量,这是我们描述颜色的更自然的方式。例如,您可能会忽略最后一个组件,使您的算法对输入图像的光线条件不太敏感。
  • YCrCb被流行的JPEG图像格式使用。
  • CIE L * a * b *是感知统一的颜色空间,如果您需要测量给定颜色与其他颜色的距离,则可方便使用。

每个建筑组件都有自己的有效域。这导致使用的数据类型。我们如何存储组件定义了我们在其域中的控件。可能的最小数据类型是char,这意味着一个字节或8位。这可能是无符号的(因此可以存储从0到255的值)或带符号(从-127到+127的值)。尽管在三个组件的情况下,这已经提供了1600万个可能的颜色来表示(像在RGB情况下),我们可以通过使用浮点数(4字节= 32位)或双(8字节= 64位)数据来获得更精细的控制每个组件的类型。然而,请记住,增加组件的大小也会增加内存中整个画面的大小。

明确创建一个Mat对象

加载,修改和保存图像教程中,您已经学习了如何使用cv :: imwrite()函数将矩阵写入图像文件。但是,为了调试目的,查看实际值更为方便。您可以使用Mat的“操作符”来执行此操作。请注意,这仅适用于二维矩阵。

虽然Mat作为一个图像容器非常好,但它也是一个通用的矩阵类。因此,可以创建和操纵多维矩阵。您可以通过多种方式创建Mat对象:

    Mat M(2,2,CV_8UC3,Scalar(0,0,255));
    cout << “M =” << endl << “” <<“M << endl << endl;

MatBasicContainerOut1

对于二维和多通道图像,我们首先定义它们的大小:行和列数明智。

然后,我们需要指定用于存储元素的数据类型和每个矩阵点的通道数。为此,我们根据以下约定构造了多个定义:

CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]

例如,CV_8UC3意味着我们使用8位长的无符号字符类型,每个像素有三个形成三个通道。这是最多四个通道号预定义的。该CV ::标量是四个元件短矢量。指定这一点,您可以使用自定义值初始化所有矩阵点。如果您需要更多功能,您可以使用上部宏来创建类型,在括号中设置通道号,如下所示。

  • 使用C / C ++数组并通过构造函数进行初始化
    int sz[3] = {2,2,2};
    Mat L(3,sz, CV_8UC(1), Scalar::all(0));

上面的例子显示了如何创建一个具有两维以上的矩阵。指定其维度,然后传递一个包含每个维的大小的指针,其余的保持不变。

    M.create(4,4,CV_8UC(2));
    cout << “M =” << endl << “”   <<“M << endl << endl;

MatBasicContainerOut2

您无法使用此结构初始化矩阵值。如果新的大小不适合旧的,它将仅重新分配其矩阵数据存储器。

    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;

MatBasicContainerOut3

  • 对于小矩阵,您可以使用逗号分隔的初始化器或初始化器列表(在最后一种情况下需要C ++ 11支持):
    Mat C =(Mat_ <double>(3,3)<< 0,-1,0,-1,5,-1,0,-1,0);
    cout << “C =” << endl << “”“ << C << endl << endl;
    C =(Mat_ <double>({0,-1,0,-1,5,-1,0,-1,0}))。reshape(3);
    cout << “C =” << endl << “”“ << C << endl << endl;

MatBasicContainerOut6

为现有的Mat对象和cv :: Mat :: clonecv :: Mat :: copyTo创建一个新标题。

    Mat RowClone = C.row(1).clone();
    cout << “RowClone =” << endl << “” << RowClone << endl << endl;

MatBasicContainerOut7

注意
您可以使用cv :: randu()函数填入随机值的矩阵您需要给出随机值的较低和较高值:
    Mat R = Mat(3,2,CV_8UC3);
    randu(R,Scalar :: all(0),Scalar :: all(255));

输出格式

在上述示例中,您可以看到默认的格式化选项。然而,OpenCV允许您格式化矩阵输出:

  • Default
    cout << “R(default)=” << endl << R << endl << endl;

MatBasicContainerOut8

  • Python

    cout << "R (python)  = " << endl << format(R, Formatter::FMT_PYTHON) << endl << endl;

MatBasicContainerOut16

  • Comma separated values (CSV)

    cout << “R(csv)=” << endl << format(R,Formatter :: FMT_CSV)<< endl << endl;

MatBasicContainerOut10

  • NumPy

    cout << "R (numpy)   = " << endl << format(R, Formatter::FMT_NUMPY ) << endl << endl;

MatBasicContainerOut9

  • C

    cout << “R(c)=” << endl << format(R,Formatter :: FMT_C)<< endl << endl;

MatBasicContainerOut11

其他常用项​​目的输出

OpenCV还通过<<运算符来支持其他常见OpenCV数据结构的输出:

  • 2D Point
    Point2f P(5,1);
    cout << “Point(2D)=” << P << endl << endl;

MatBasicContainerOut12

  • 3D Point

    Point3f P3f(2, 6, 7);
    cout << "Point (3D) = " << P3f << endl << endl;

MatBasicContainerOut13

    vector<float> v;
    v.push_back( (float)CV_PI);   v.push_back(2);    v.push_back(3.01f);
    cout << "Vector of floats via Mat = " << Mat(v) << endl << endl;

MatBasicContainerOut14

  • std::vector of points

    vector<Point2f> vPoints(20);
    for (size_t i = 0; i < vPoints.size(); ++i)
        vPoints[i] = Point2f((float)(i * 5), (float)(i % 7));
    cout << "A vector of 2D Points = " << vPoints << endl << endl;

MatBasicContainerOut15

这里的大多数样品已被包含在一个小的控制台应用程序中。您可以从这里或cpp示例的核心部分下载。