Mat类型可以被认为是OpenCV库的核心。 OpenCV库中绝大多数的函数都是Mat类的成员,以Mat作为参数,或者Mat作为返回值。
Mat类用于表示任意维数的密集数组。即使对于数组中的该条目为零,也存在与该条目相对应的数据值。大多数图像都以密集阵列的形式存储。在稀疏数组的情况下,通常只存储非零条目。如果许多条目都是零,那么可以节省大量的存储空间。使用稀疏数组而不是密集数组的常见情况是直方图。对于许多直方图,大多数条目都是零,并且存储所有这些零不是必需的。对于稀疏数组的情况,OpenCV有另一种数据结构,SparseMat。
如果你熟悉OpenCV库的C接口(2.1之前的版本),您将会记住IplImage和CvMat的数据类型。 你也可能记得CvArr。 在C ++实现中,所有这些都消失了,用Mat代替。Mat类可以用于任何维数的数组。数据被存储在阵列中,被认为是"光栅扫描顺序"的n维数字。这意味着在一维数组中,元素是顺序的。在二维数组中,数据按行组织,每行依次出现。对于三维阵列,每个平面都是逐行填充的。
每个Mat都包含一个标志元素,指示数组内容,一个dims元素指示维数,rows和cols元素指示行数和列数,指向数据指针的位置数组数据被存储,一个类似于Ptr <>的的引用计数器。数据数组被布置成使得其索引由(i0,ii,...,iNd-1)给出的元素的地址是:
在二维数组的简单情况下,这可以简化为:
&(mtxi, j)= mtx.data +mtx.step 0 *i +mtx.step 1 *j
Mat中每个数据元素本身可以是单个数字,也可以是多个数字。在多个数字的情况下,这就是多通道数组。一个数组可能被认为是一个32位浮点数的二维三通道数组;在这种情况下,数组的元素是三个32位浮点数,大小为12个字节。在内存中布局时,数组的行可能不是绝对顺序的;在下一个之前可能会有小的间隙缓冲每一行。一个n维单通道阵列和一个(n-1)维多通道阵列之间的区别在于,这个填充将始终发生在整行的末尾,即元素中的通道将始终是连续的)。
可以简单地通过实例化一个类型为Mat的变量来创建一个数组。以这种方式创建的数组没有大小和数据类型。但是,可以使用create()等成员函数分配数据。 create()的一个变体将多个行,多个列和一个类型作为参数,并将该数组表示为一个二维对象。数组的类型决定了它具有哪种元素以及通道的数量。所有这些类型都在库中定义,并具有CV_ {8U,16S,16U,32S,32F,64F} C {1,2,3}的格式。例如,CV_32FC3意味着一个32位浮点三通道阵列。
也可以在首次分配矩阵时指定这些内容。Mat有许多构造函数,其中一个与create()具有相同的参数。例如:
cv::Mat m;
// 创建3行10列3通道32位浮点型数据
m.create( 3, 10, CV_32FC3 );
//设置第一个通道为1.0,第二个通道为0.0,第三个通道为1.0
m.setTo( cv::Scalar( 1.0f, 0.0f, 1.0f ) );
//上面的定义与下面的语句等价
Mat m( 3, 10, CV_32FC3, cv::Scalar( 1.0f, 0.0f, 1.0f ) );
Mat对象实际上是数据区域的头,原则上它是一个完全独立的东西。 例如,可以将一个矩阵n分配给另一个矩阵m(即,m = n)。 在这种情况下,m中的数据指针将被改变为指向与n相同的数据。 先前由m的数据元素指向的数据将被释放。同时,它们现在共享的数据区域的引用计数器将递增。同时将更新m成员的数据(如行,列和标志),以准确描述m中数据指向的数据。
表1是Mat的构造函数的完整列表。但事实上,大多数时候可能使用其中的一小部分。
表1列出了Mat对象的构造函数。除了默认的构造函数之外,它们分为三个基本类别:有些需要多行和多列来创建二维数组的类,有些使用Size对象创建,有些构造n维数组并要求指定维数并传入指定每个维的大小的整数数组的指针。此外,其中一些允许初始化数据,或者通过Scalar提供初始化值(这时整个数组将被初始化为该值),或者通过提供指向数据块的指针来指定。在后一种情况下,只是为现有数据创建一个数据头,不复制数据;将数据成员设置为指向由数据参数指示的数据)。
表2的复制构造函数显示了如何从另一个数组创建一个数组。除了基本的复制构造函数外,还有三种方法用于从现有数组的子区域构建数组,以及使用某个矩阵表达式的结果初始化新矩阵的构造函数。
也可以从OpenCV2.1版之前的CvMat或IplImage结构创建新的C ++风格的Mat结构。在这种情况下,可以采用下面表3这种方式转换。
这些构造函数可能比你刚开始认识OpenCV的时候要多得多。还有一种构造函数是模板构造函数。 这些被称为模板构造函数是因为它们从本身就是模板的东西构建了Mat的实例。 这些构造函数允许使用任意Vec <>或Matx <>来创建具有相应维数和类型的Mat数组,或者使用任意类型的STL向量<>对象来构造一个相同类型的数组。表4是这种构造方式。
Mat类还提供了许多静态成员函数来创建特定类型的常用数组(表4-5)。 这些函数包括zero(),ones()和eye()等函数,它们分别构造一个全是零的矩阵,一个全是1矩阵的矩阵和一个单位矩阵。表5是这种构造方式
对于Mat中的数据访问方式可以参考第6部分的说明。
有时候我们需要访问数组中的某一块元素,可能是选择一行或一列,或原始数组的任何子区域。有很多方法可以做到这一点,表6是Mat类的成员函数,并返回调用它们的数组的子部分。
这些方法中最简单的方法是row()和col()。与row()和col()紧密相关的是rowRange()和colRange()。这些函数基本上是一样的,只不过他们会提取一个具有多个连续行(或列)的数组。可以通过以下两种方式之一调用这两个函数:指定一个整数开始和结束行(或列),或者通过传递一个行(或列)的Range对象。范围包括开始索引但不包括结束索引。
除了从m.diag()返回的数组引用矩阵的对角线元素之外,成员函数diag()与row()或col()的作用相同。 m.diag()需要一个整数参数,用于指示要提取哪个对角线。如果该参数为零,那么它将是主对角线。如果是正数,则它将从阵列上半部分的主对角线偏移该距离。如果它是负数,那么它将来自阵列的下半部分。
提取子矩阵的最后一种方法是使用operator()。使用这个运算符,你可以传递一对范围(行的Range和列的Range)或者从Rect指定你想要的区域。