Mat-基本图像容器()

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

                                                    Mat-基本图像容器()

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

Mat

OpenCV 自2001年以来一直存在。在那些日子里, 库是围绕一个 C 接口构建的, 为了将图像存储在内存中, 他们使用了一个名为 It云图的 C 结构。这是你会在大多数较旧的教程和教育材料中看到的。这样做的问题是, 它给桌子带来了 C 语言的所有细节。最大的问题是手动内存管理。它建立在用户负责处理内存分配和取消分配的假设之上。虽然这不是较小程序的问题, 但一旦您的代码库增长, 处理这一切将更加困难, 而不是专注于解决您的开发目标。

幸运的是, C++ 出现了, 并引入了类的概念, 使用户更容易通过自动内存管理 (或多或少)。好消息是, C++ 与 C 完全兼容, 因此进行更改不会产生兼容性问题。因此, OpenCV 2.0 引入了一个新的 C++ 接口, 它提供了一种新的做事方式, 这意味着您不需要摆弄内存管理, 使代码简洁 (写得更少, 实现更多)。C++ 接口的主要缺点是, 目前许多嵌入式开发系统只支持 C。因此, 除非您的目标是嵌入式平台, 否则使用旧方法是没有意义的 (除非您是受虐狂程序员, 并且您请求麻烦)。

关于 Matt, 您首先需要知道的是, 您不再需要手动分配其内存, 并在不需要时立即释放它。虽然这样做仍然是可能的, 但大多数 OpenCV 函数将自动分配其输出数据。如果您传递了一个已经存在的 Mat 对象 (该对象已经为矩阵分配了所需的空间), 这将被重用, 这是一个很好的奖励。换句话说, 我们在任何时候使用的内存都和执行任务所需的内存一样多。

Mat 基本上是一个包含两个数据部分的类: 矩阵标头 (包含矩阵大小等信息、用于存储的方法、地址是存储的矩阵等) 和指向包含像素值的矩阵的指针 (取任何尺寸, 具体取决于为存储选择的方法)。矩阵标头大小是恒定的, 但是矩阵本身的大小可能因图像而异, 通常按数量级较大。

OpenCV 是一个图像处理库。它包含大量的图像处理功能。为了解决计算难题, 大多数时候您将使用库的多个函数。正因为如此, 将图像传递给函数是一种常见的做法。我们不应该忘记, 我们谈论的是图像处理算法, 这些算法往往是相当重的计算。我们最不愿意做的事情是通过制作潜在大图像的不必要副本来进一步降低程序的速度。

为了解决这个问题, OpenCV 使用了一个参考计数系统。其想法是, 每个 Mat 对象都有自己的标头, 但是矩阵可以通过让它们的矩阵指针指向相同的地址来在它们的两个实例之间共享。此外, 复制运算符将只复制标头和指针到大矩阵, 而不复制到数据本身。

 Mat-基本图像容器()

所有上述对象, 最后, 指向相同的单个数据矩阵。但是, 它们的标头是不同的, 使用其中任何一个进行修改也会影响所有其他标题。实际上, 不同的对象只是为相同的基础数据提供不同的访问方法。不过, 它们的标题部分是不同的。真正有趣的是, 您可以创建只引用完整数据的一个小节的标头。例如, 要在图像中创建感兴趣的区域 (ROI), 只需使用新边界创建一个新标头:Mat-基本图像容器() 

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

Mat-基本图像容器()

现在修改FG不会影响Mat头指向的矩阵。你需要记住的是:

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

存储方法

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

对于丰富多彩的方式,我们有更多的方法可供选择。它们中的每一个都将其分解为三个或四个基本组件,我们可以使用这些组合来创建其他组件。最受欢迎的是RGB,主要是因为这也是我们的眼睛如何建立颜色。它的基色是红色,绿色和蓝色。要编码颜色的透明度,有时会加第四个元素:添加alpha(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对象:

  • cv :: Mat :: Mat构造函数

    Mat M(2,2,CV_8UC3Scalar(0,0,255));

    cout << “M =” << endl << “” << M << endl << endl;

    Mat-基本图像容器()

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

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

    CV_ [每个项目的位数] [有符号或无符号] [类型前缀] C [通道号]

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

  • 使用C / C ++数组并通过构造函数初始化

    int sz [3] = {2,2,2};

    Mat L(3,sz,CV_8UC(1),Scalar :: all(0));

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

  • cv :: Mat :: create function:

    M.create(4,4,CV_8UC(2));

    cout << “M =” << endl << “” << M << endl << endl;

    Mat-基本图像容器()

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

  • MATLAB样式初始化器:cv :: Mat :: zeroscv :: Mat :: onescv :: Mat :: eye。指定要使用的大小和数据类型:

    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;

    Mat-基本图像容器()

  • 对于小型矩阵,您可以使用逗号分隔的初始化程序或初始化程序列表(在最后一种情况下需要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;

    Mat-基本图像容器()

  • 为现有Mat对象创建一个新头,cv :: Mat :: clonecv :: Mat :: copyTo它。

    Mat RowClone = C.row(1).clone();

    cout << “RowClone =” << endl << “” << RowClone << endl << endl;

    Mat-基本图像容器()

    注意

    您可以使用cv :: randu()函数填充具有随机值的矩阵。您需要为随机值指定较低和较高值:

    Mat R = Mat(3,2,CV_8UC3);

    randu(R,Scalar :: all(0),Scalar :: all(255));

    输出格式

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

  • 默认

    cout << “R(默认)=” << endl << R << endl << endl;

    Mat-基本图像容器()

  • 蟒蛇

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

    Mat-基本图像容器()

  • 逗号分隔值(CSV)

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

    Mat-基本图像容器()

  • NumPy的

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

    Mat-基本图像容器()

  • C

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

    Mat-基本图像容器()

其他常见项目的输出

OpenCV也通过<<运算符提供对其他常见OpenCV数据结构输出的支持:

  • 2D点

    Point2f P(5,1);

    cout << “Point(2D)=” << P << endl << endl;

    Mat-基本图像容器()

  • 3D点

    Point3f P3f(2,6,7);

    cout << “Point(3D)=” << P3f << endl << endl;

    Mat-基本图像容器()

  • std :: vector via cv :: Mat

    vector <float> v;

    v.push_back((float)CV_PI); v.push_back(2); v.push_back(3.01f);

    cout << “浮动的向量通过Mat =” << Mat(v)<< endl << endl;

    Mat-基本图像容器()

  • std ::点的向量

    vector <Point2f> vPoints(20);

    for(size_t i = 0; i <vPoints.size(); ++ i)

    vPoints [i] = Point2f((float)(i * 5),(float)(i%7));

    cout << “2D点的矢量=” << vPoints << endl << endl;

    Mat-基本图像容器()

这里的大多数示例都包含在一个小型控制台应用程序中。您可以从此处或cpp示例的核心部分下载它。

您还可以在YouTube上找到有关此内容的快速视频演示。

上一篇:后端开发、bigdata、cv、nlp实习+秋招面试交流群


下一篇:TensorFlow 2学习和工业CV领域应用