数字图像中的每个点都称为像素(对于图像元素),并且每个像素可以存储一个或多个值,这取决于它是否是仅存储一个值的黑白图像(也称为二进制图像,比如只存储0或1),还是存储两个值的灰度图像,或者是存储三个值的彩色图像。这些值通常在整数0~255,但也可以使用其他范围,比如在高动态范围成像(high dynamic range imaging,简称HDRI)或热图像领域中的浮点数0~1。
图像是以矩阵格式存储的,其中的每个像素都有一个位置,并且可以通过列和行的编号来引用。OpenCV
用Mat
类来达到这个目的。在灰度图像中,使用单个矩阵,如下图所示。
在下图所示的彩色图像中,使用了一个宽度×高度×颜色通道数的矩阵。
但Mat
类不仅仅用于存储图像,它还能存储任何类型和不同大小的矩阵。你可以将其用作代数矩阵并用它执行运算。在内存中,矩阵被保存为按列和行排序的数组或值序列。表2-1显示BGR
图像格式的像素序列。
关于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
函数中输出图像的内存分配是自动完成的(如果不特别指定的话);使用OpenCV
的C++
接口时不需要考虑内存释放问题;赋值运算符和拷贝构造函数(构造函数)只复制信总头;使用函数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 [ Thechannel 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 对象运算
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;
理论上会有如下结果,但是调试发现报错,待继续分析。
其它运算请参考:
/2.4/modules/core/doc/core.html