博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OpenCV Mat —— 基本的图像容器
阅读量:7051 次
发布时间:2019-06-28

本文共 5386 字,大约阅读时间需要 17 分钟。

目标

现实中我们有很多种方法来获取数字图像:数字摄像头、扫描仪、计算机断层扫描以及核磁共振生成图像等等。对我们人类来说这些设备生成的结果我们称之为图像。而我们从这些设备获取的图像最终是以组成点阵的数值来表示的。

就好像是一张车的图片中就是包含了点阵强度值的矩阵。我们可以根据需要来获取或者存储点阵,但最终所有计算机中的图片就剩下点阵以及描述点阵的信息。OpenCV 是一个计算机视觉库,主要用来处理和操作这类图像信息。因此你首先需要熟悉的是 OpenCV 是如何存取图像的。

Mat

OpenCV 项目大约在 2001 年推出,之前主要是提供了 C 接口并通过名为 IplImage  的 C 结构体来处理内存中的图像。在一些老的教程和学习材料中你经常会看到这个结构体。使用这个结构体的问题是让 OpenCV 严重受限于 C 语言的特性和缺点,最大的问题就是需要进行手工内存管理。它要求用户必须小心的操作内存的分配和释放。对一些小程序而言,这不是什么大问题,但是一旦你的代码量增长越来越迅速时,这个问题变得非常严重。

幸运的是,C++ 语言实现了类的概念,可以轻松的实现自动化的内存管理(或多或少)。好消息是 C++ 完全兼容 C 语言,因此改用 C++ 并没有兼容性问题需要解决。所以 OpenCV 2.0 引入了全新的 C++ 接口,意味着你无需再关系内存管理的问题,让你的代码运行更加可靠。而 C++ 接口的缺点是很多嵌入式开发系统当前还只是支持 C 语言。因此,除非你使用一些特定的嵌入式系统,否则没有理由继续使用老的接口(除非你就是想自寻烦恼)。

首先我们需要了解的是 Mat 无需手工进行内存的分配和释放。虽然这样仍然只是一种可能性,因为绝大多数的 OpenCV 函数将自动的分配输出数据所需的内存。作为一个很好的红利,如果你传递一个已有的而且已经分配了阵列内存空间的 Mat 对象,它会被重用。换句话说,任何时候我们只需要使用最少的内存来执行各种任务。

Mat 是一个类,包含了阵列的头(阵列大小、存储的方法以及存储地址等等)和指向阵列点阵数据的指针(维度取决于存储的方法)。阵列的头部大小是一个常量,不同图片的头部存放的阵列大小是不同的。

OpenCV 是一个图像处理库。其包含大量各种图像处理函数。为了满足计算的要求,绝大多数时间你都会使用多个 OpenCV 函数。例如传递图片给某个函数是经常需要做的。我们别忘了我们正在讨论图像处理算法,这往往是非常沉重的计算。最后我们需要做的是进一步提升程序的速度,减少潜在的不必要的大图片拷贝。

为了解决这个问题,OpenCV 使用引用计数系统。这个思路就是每个 Mat 拥有独立的头部信息,而阵列数据是共享的。此外,拷贝操作只拷贝头部而不拷贝数据。

Mat A, C;  // 创建头部A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 分配阵列Mat B(A);  // 使用拷贝构造函数C = A;     // 赋值操作

上述代码中所有的对象都指向同一个数据阵列。而它们的头部是不同的,这样的话对某个对象进行操作就会影响到其他的对象。实际上不同的对象只是提供不同的访问方法来访问相同的底层数据。真正有趣的是你可以创建只指向部分数据的头部。例如,你可以使用如下代码来创建图像的兴趣点,包含新的头部和新的边界:

Mat D (A, Rect(10, 10, 100, 100) ); // 使用矩形Mat E = A(Range::all(), Range(1,3)); // 使用行列边界

这时候你可能会问该阵列本身是否属于多个 Mat 对象,这些对象负责在其不需要的时候进行数据清理。最短的回答就是:它使用的是最后一个对象。这是通过引用计数机制来实现的。当某人拷贝一个 Mat 对象的头部,该阵列的计数器就会加1.当头部被清理时计数器就会减1.当计数器值为0的时候,阵列就会被释放。有时候你也想拷贝阵列本身,OpenCV 提供了  和  函数。

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

现在修改 F 或者 G 都不会影响 Mat 头部所指向的阵列。你需要记住的是:

  • 为 OpenCV 函数输出图像的内存分配是自动的(除非特别说明)
  • 使用 OpenCV 的 C++ 接口不需要考虑内存管理的问题
  • 赋值操作和拷贝构造函数只拷贝了头部信息
  • 图像底层的矩阵可以通过  和  函数进行拷贝

存储函数

这是关于点阵值的存储问题。你可以选择色彩空间和所使用的数据类型。色彩空间指的是我们如何利用给定的颜色代码组合成颜色组件。最简单的是灰度图,我们所需要处理的颜色只有黑白两色。这样的组合可以让我们创建很多灰色阴影。

而我们有很多的方法来处理彩色图。每一种方法都至少包含 3 到 4 中基本组件,我们可以对这些进行合并来创建彩色图。最通用的是 RGB,主要因为这是我们眼睛对色彩的识别方式。其基准色是红、绿、蓝。为了生成透明图像我们还需要第四个元素 —— alpha(A).

不同的色彩方案有不同的优势:

  • RGB 最常用,因为跟我们的眼睛识别方式类似,但需要注意的是 OpenCV 显示系统用的是 BGR 色彩
  • HSV 和 HLS 将颜色分解成色调、饱和度和亮度组件,用来描述色彩更为直观。它可以让你忽略值组件,使你的算法对输入图像的光照条件不那么敏感
  • YCrCb 常用语 JPEG 图像格式
  • CIE L*a*b 是一个感知均匀的色彩空间,可以方便的用来计算从一个颜色到另外一个颜色的差异。

每个颜色组件都有其有效的域,这个决定了我们所使用的数据类型:我们是如何存储一个组件决定了我们在这个域上的控制。最小的数据类型是 char,相当于一个字节或者 8 位数据。这个可以是无符号的(可以存储 0 - 255) 或者有符号的(-127 - 127)。虽然在三组件情况下(如 BGR)已经提供了 1600 多万的色彩值。我们还可以使用 float (4 byte = 32 bit) 或者 double (8 byte = 64 bit) 数据类型来定义每个组件。不过,需要记住的是,提升组件的值同样也提升了整个图片占用内存的大小。

显式的创建 Mat 对象

在教程  中我们已经知道如何通过  函数将点阵数据写到图像文件中。这样做可以大大方便调试的过程。你可以使用 Mat 的 << 操作符,不过需要注意的是这个只适合二维的阵列。

虽然 Mat 作为一个图像的容器挺合适,但它同时也是一个矩阵类。所以可以用它来创建和操作多维的矩阵。有很多方法来创建一个 Mat 对象:

  •  构造函数

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

对于两个维度或者多个维度的图像我们首先要定义大小,包括行列数。

然后需要指定用来存储元素的数据类型和每个点阵的通道数量。可以使用使用如下代码来一次定义多个变量:

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

例如 CV_8UC3 意味着使用无符号 char 类型,8位长度长整数以及每个点阵使用 3 通道。最多可预定义 4 个通道数量。  是一个 4 元素的短整数向量。指定完后可以使用定制值来初始化点阵。如果你需要更多的通道,可以使用 upper 宏来创建,并在括号中指定通道数量,如下所示:

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

    int sz[3] = {
    2,2,2}; Mat L(3,sz, CV_8UC(1), Scalar::all(0));

    上述示例显示如何创建一个超过 2 个维度的阵列。指定阵列的维度数,并传递包含每个维度大小的指针。

  • 为已有的 IplImage 指针创建一个头部:

    IplImage* img = cvLoadImage("greatwave.png", 1);Mat mtx(img); // convert IplImage* -> Mat
  •  函数:

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

你不能在这个构造函数中初始化阵列值,它只在其阵列数据存储大小与老的不匹配时重新分配。

  • MATLAB 风格的初始化: , , . 指定大小和数据类型:

    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 = (Mat_
    (3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0); cout << "C = " << endl << " " << C << endl << endl;
  • 为一个已有的 Mat 对象创建一个新的头部,并进行  后者  .

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

注意

你可以使用  函数来为一个阵列填充随机值,需要指定随机值的上下限::

Mat R = Mat(3, 2, CV_8UC3);    randu(R, Scalar::all(0), Scalar::all(255));

输出格式化

在前面的例子中我们看到了默认的格式化选项。而 OpenCV 允许你自定义阵列输出的格式化方式::

  • 默认

    cout << "R (default) = " << endl <<        R           << endl << endl;
  • Python

    cout << "R (python)  = " << endl << format(R,"python") << endl << endl;
  • 逗号分隔的值 (CSV)

    cout << "R (csv)     = " << endl << format(R,"csv"   ) << endl << endl;
  • Numpy

    cout << "R (numpy)   = " << endl << format(R,"numpy" ) << endl << endl;
  • C

    cout << "R (c)       = " << endl << format(R,"C"     ) << endl << endl;

其他常用条目的输出

其他常用的 OpenCV 数据结构也可以使用 << 操作符来输出:

  • 2D Point

    Point2f P(5, 1);    cout << "Point (2D) = " << P << endl << endl;
    Default Output
  • 3D Point

    Point3f P3f(2, 6, 7);    cout << "Point (3D) = " << P3f << endl << endl;
    Default Output
  • std::vector via cv::Mat

    vector
    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;
    Default Output
  • std::vector of points

    vector
    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;
    Default Output

这里的大多数示例都包含一个小的控制台程序,你可以从这里  这些代码。

你也可以在  观看视频教程.

你可能感兴趣的文章
history详解
查看>>
使用TAR源码包安装程序
查看>>
MSF目录结构
查看>>
RHEL下部署heartbeat,实现简单故障转移群集
查看>>
SQL如何分批次查询
查看>>
Swift可选值Optionals
查看>>
VMware Workstation Pro 調整硬盤空間(下)
查看>>
在线将Apache Rewrite伪静态规则自动转换为Nginx Rewrite
查看>>
Hibernate实现,使用UUID.主键的生成策略
查看>>
在工作中经常使用的git命令笔记
查看>>
Centos6.4安装mysql-5.5.33绿色版
查看>>
Java反射
查看>>
vmware安装 深度完美ghost winXP SP3 详细图文教程,强调一些重难点与技巧,模拟生产环境必备!...
查看>>
为什么我的日志文件不能继续记录呢
查看>>
如何安装CRX格式?Chrome插件离线安装,CRX格式安装方法 JSON-handle Chrome插件下载...
查看>>
使用Azure Function + Cognitive Services 实现图片自动化审核
查看>>
log4j日志通过flume写入HDFS
查看>>
haproxy代理配置
查看>>
一些转载的知识
查看>>
我的友情链接
查看>>