OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算

数字图像中的每个点都称为像素(对于图像元素),并且每个像素可以存储一个或多个值,这取决于它是否是仅存储一个值的黑白图像(也称为二进制图像,比如只存储0或1),还是存储两个值的灰度图像,或者是存储三个值的彩色图像。这些值通常在整数0~255,但也可以使用其他范围,比如在高动态范围成像(high dynamic range imaging,简称HDRI)或热图像领域中的浮点数0~1。

图像是以矩阵格式存储的,其中的每个像素都有一个位置,并且可以通过列和行的编号来引用。 OpenCVMat 类来达到这个目的。在灰度图像中,使用单个矩阵,如下图所示。

OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算_第1张图片
在下图所示的彩色图像中,使用了一个宽度×高度×颜色通道数的矩阵。

OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算_第2张图片

Mat 类不仅仅用于存储图像,它还能存储任何类型和不同大小的矩阵。你可以将其用作代数矩阵并用它执行运算。在内存中,矩阵被保存为按列和行排序的数组或值序列。表2-1显示 BGR 图像格式的像素序列。

OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算_第3张图片

关于 Mat 类, 首先我们要知道的是:

  • 不必再手动为其幵辟空间。
  • 不必再在不需要时立即将空M释放。

这里指的是手动开辟空间并非必须,但它依旧是存在的—大多数 OpenCV 函数仍会手动地为输出数据幵辟空间。当传递一个已经存在的 Mat 对象时, 开辟好的矩阵空间会被重用。也就是说, 我们每次都使用大小正好的内存来完成任务。

1. Mat 结构

Mat 是一个类, 由两个数据部分组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等信息) 和一个指向存储所有像素值的矩阵(根据所选存储方法的不同,矩阵可以是不同的维数)的指针。

矩阵头的尺寸是常数值, 但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。因此,
当在程序中传递图像并创建副本时,大的开销是由矩阵造成的, 而不是信息头。

为了解决此问题, OpenCV 使用了引用计数机制。其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。而拷贝构造函数则只复制信息头和矩阵指针, 而不复制矩阵。

Mat A, C; / / 仅创建信息头部分
A = imread("1.jpg") / / 这里为矩阵开辟内存
Mat B(A); / /使用拷贝构造函教
C = A; / /賦值运算符

以上代码中的所有 Mat 对象最终都指向同一个也是唯一一个数据矩阵。虽然它们的信息头不同,但通过任何一个对象所做的改变也会影响其他对象。

我们可以创建只引用部分数据的信息头。比如想要创建一个感兴趣区域(ROI)只需要创建包含边界信息的信息头:

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

如果想复制矩阵本身( 不只是信息头和矩阵指针),这时可以使用函数 clone() 或者 copyTo()

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

现在改变 F 或者 G 就不会影响 Mat 信息头所指向的矩阵。

总结如下:

  • OpenCV 函数中输出图像的内存分配是自动完成的(如果不特别指定的话);
  • 使用 OpenCVC++ 接口时不需要考虑内存释放问题;
  • 赋值运算符和拷贝构造函数(构造函数)只复制信总头;
  • 使用函数 clone() 或者 copyTo() 来复制一幅图像的矩阵;

2. 像素值的存储方法

存储像素值需要指定颜色空间和数据类型。

  • 颜色空间是指针对一个给定的颜色,如何组合颜色元素以对其编码。最简单的颜色空间要属灰度级空间, 只处理黑色和白色, 对它们进行组合便可以产生不同程度的灰色。

对于彩色方式则有更多种类的颜色空间, 但不论哪种方式都是把颜色分成三个或者四个基元素,通过组合基元素可以产生所有的颜色。 RGB 颜色空间是最常用的一种颜色空间,。它的基色是红色、绿色和蓝色,有时为了表示透明颜色也会加入第四个元素alpha ( A)。

每个组成元素都有自己的定义域, 而定义域収决于其数据类型,如何存储一个元素决定了我们在其定义域上能够控制的精度。最小的数据类型是 char ,占一个字节或者 8 位,可以是有符号型(0 到255 之间)或无符号型( -127 到+127之间)。尽管使用三个 char 型元素已经可以表示 1600 万种可能的颜色(使用 RGB 颜色空间),但若使用 float ( 4 字节,32 位)或 double ( 8 字节,64 位)则能给出更加精细的颜色分辨能力。但同时也要切记, 增加元素的尺寸也会增加图像所占的内存空间。

3. 创建 Mat 对象方法

我们可以通过 Mat 的运算符 << 来查看其值。但要记住, Mat 的运算符 << 只对二维矩阵有效。 Mat 不但是一个非常有用的图像容器类,同时也是一个通用的矩阵类,我们也可以用它来创建和操作多维矩阵。

创建一个 Mat 对象有多种方法, 列举如下:

3.1 使用 Mat 构造函数

最常用的方法是直接使用 Mat() 构造函数,示例如下:

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

输出结果:

M is 
[  0,   0, 255,   0,   0, 255;
   0,   0, 255,   0,   0, 255;
   0,   0, 255,   0,   0, 255]

对于二维多通道图像, 首先要定义其尺寸,即行数和列数。然后,需要指定存储元素的数据类型以及每个矩阵点的通道数。为此,依据下面的规则有多种定义:

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

即:

cv_[位数][带符号与否][类型前缀]c[通道数]

比如 CV_8UC3 表示使用 8 位的 unsigned char 型,每个像素由三个元素组成三通道。而预先定义的通道数可以多达四个。另外, Scalar 是个 short 型的向量。

受支持的类型取决于要存储的数字类型和通道数,最常见的类型如下:

CV_8UC1;
CV_8UC3;
CV_8UC4;

CV_32FC1;
CV_32FC3;
CV_32FC4;

3.2 通过C++构造函数进行初始化

示例代码:

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

上面的例子演示了如何创建一个超过两维的矩阵:指定维数,然后传递一个指向一个数组的指针,这个数组包含每个维度的尺寸。

3.3 利用 create() 函数

利用 Mat 类中的 Create() 成员函数进行 Mat 类的初始化操作,示范代码如下。

	Mat M;
	M.create(4, 3, CV_8UC(2));
	cout << "M is "<< endl << M << endl;

输出结果:

M is 
[  0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0]

需要注意的是,此创建方法不能为矩阵设初值,只是在改变尺寸时重新为矩阵数据开辟内存而已。

3.4 使用 zeros()、ones()、eyes() 初始化

示例代码:

	Mat E = Mat::eye(3, 3, CV_64F);
	cout << "E is "<< endl << E << endl;
	Mat O = Mat::ones(3, 2, CV_32F);
	cout << "O is "<< endl << O << endl;
	Mat Z = Mat::zeros(3, 2, CV_8UC1);
	cout << "Z is "<< endl << Z << endl;

输出结果:

E is 
[1, 0, 0;
 0, 1, 0;
 0, 0, 1]
 
O is 
[1, 1;
 1, 1;
 1, 1]
 
Z is 
[  0,   0;
   0,   0;
   0,   0]

3.5 对小矩阵使用逗号分隔初始化

示例代码

	Mat C = (Mat_<double> (3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0 );
	cout << "C is "<< endl << C << endl;

输出结果:

C is 
[0, -1, 0;
 -1, 5, -1;
 0, -1, 0]

3.6 为已存在的对象创建新信息头

使用成员函数 clone() 或者 copyTo() 为一个已存在的 Mat 对象创建一个新的信息头,示范代码如下。

	Mat C = (Mat_<double> (3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0 );
	Mat RowClone = C.row (1).clone() ;
	cout << "RowClone is "<< endl << RowClone << endl;

输出结果:

RowClone is 
[-1, 5, -1]

4. Mat 对象运算

OpenCVMat 类能够执行所有的矩阵运算。我们可以用 +- 运算符来加上或减去两个相同大小的矩阵,如以下代码块所示:

	Mat a = Mat::eye(Size(3, 2), CV_32F);
	Mat b = Mat::ones(Size(3, 2), CV_32F);
	Mat c = a + b;
	Mat d = a - b;

运算结果如下图:
OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算_第4张图片
我们可以用 * 运算符乘以一个标量,或者用 mul 函数乘以矩阵的每个元素,也可以用 * 运算符执行矩阵乘法:

	Mat a = Mat::eye(2, 3, CV_32F);
	Mat b = Mat::ones(3, 2, CV_32F);
	// Mat a2 = a * 2;
	// Mat d = (a + 1).mul(b + 3);
	// Mat ab = a * b;
	cout << a * 2 << endl;
	cout << (a + 1).mul(b + 3) << endl;
	cout << a * b << endl;
	// cout << a + b << endl;
	// cout << a - b << endl;

理论上会有如下结果,但是调试发现报错,待继续分析。
OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算_第5张图片

其它运算请参考:
https://docs.opencv.org/2.4/modules/core/doc/core.html

你可能感兴趣的:(OpenCV)