数字图像中的每个点都称为像素(对于图像元素),并且每个像素可以存储一个或多个值,这取决于它是否是仅存储一个值的黑白图像(也称为二进制图像,比如只存储0或1),还是存储两个值的灰度图像,或者是存储三个值的彩色图像。这些值通常在整数0~255,但也可以使用其他范围,比如在高动态范围成像(high dynamic range imaging,简称HDRI)或热图像领域中的浮点数0~1。
图像是以矩阵格式存储的,其中的每个像素都有一个位置,并且可以通过列和行的编号来引用。 OpenCV
用 Mat
类来达到这个目的。在灰度图像中,使用单个矩阵,如下图所示。
在下图所示的彩色图像中,使用了一个宽度×高度×颜色通道数的矩阵。
但 Mat
类不仅仅用于存储图像,它还能存储任何类型和不同大小的矩阵。你可以将其用作代数矩阵并用它执行运算。在内存中,矩阵被保存为按列和行排序的数组或值序列。表2-1显示 BGR
图像格式的像素序列。
关于 Mat
类, 首先我们要知道的是:
这里指的是手动开辟空间并非必须,但它依旧是存在的—大多数 OpenCV
函数仍会手动地为输出数据幵辟空间。当传递一个已经存在的 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
函数中输出图像的内存分配是自动完成的(如果不特别指定的话);OpenCV
的 C++
接口时不需要考虑内存释放问题;clone()
或者 copyTo()
来复制一幅图像的矩阵;存储像素值需要指定颜色空间和数据类型。
对于彩色方式则有更多种类的颜色空间, 但不论哪种方式都是把颜色分成三个或者四个基元素,通过组合基元素可以产生所有的颜色。 RGB
颜色空间是最常用的一种颜色空间,。它的基色是红色、绿色和蓝色,有时为了表示透明颜色也会加入第四个元素alpha ( A)。
每个组成元素都有自己的定义域, 而定义域収决于其数据类型,如何存储一个元素决定了我们在其定义域上能够控制的精度。最小的数据类型是 char
,占一个字节或者 8 位,可以是有符号型(0 到255 之间)或无符号型( -127 到+127之间)。尽管使用三个 char
型元素已经可以表示 1600 万种可能的颜色(使用 RGB
颜色空间),但若使用 float
( 4 字节,32 位)或 double
( 8 字节,64 位)则能给出更加精细的颜色分辨能力。但同时也要切记, 增加元素的尺寸也会增加图像所占的内存空间。
我们可以通过 Mat
的运算符 <<
来查看其值。但要记住, Mat
的运算符 <<
只对二维矩阵有效。 Mat
不但是一个非常有用的图像容器类,同时也是一个通用的矩阵类,我们也可以用它来创建和操作多维矩阵。
创建一个 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;
示例代码:
int sz[3] = {2,1,3};
Mat M(3, sz, CV_8UC3, Scalar::all(0));
上面的例子演示了如何创建一个超过两维的矩阵:指定维数,然后传递一个指向一个数组的指针,这个数组包含每个维度的尺寸。
利用 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]
需要注意的是,此创建方法不能为矩阵设初值,只是在改变尺寸时重新为矩阵数据开辟内存而已。
示例代码:
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]
示例代码
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]
使用成员函数 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]
OpenCV
的 Mat
类能够执行所有的矩阵运算。我们可以用 +
和 -
运算符来加上或减去两个相同大小的矩阵,如以下代码块所示:
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;
运算结果如下图:
我们可以用 *
运算符乘以一个标量,或者用 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;
其它运算请参考:
https://docs.opencv.org/2.4/modules/core/doc/core.html