今天我们主要学习一下OpenCV中最重要的数据类型--数组Mat,这个结构可以视为是OpenCV所有C++实现的核心,OpenCV中所有主要函数都或是Mat类的成员,或是将Mat类作为参数,或是返回一个Mat类型。很少有函数和这三者都没有关系的。
每一个Mat矩阵,都包含一个表示它数据类型的flag成员,一个表示其维度的成员dims,分别表示行和列数的成员rows和cols(dims>2无效),一个指向真正数据存储地址的指针data,一个表示该内存区域有多少个引用的refcount成员,类似前面学的Ptr<>引用计数器,数据实体data的结构被step描述。
下面我们来看一下如何创建一个数组,下表是Mat类的构造函数(非复制构造函数):
上表的构造函数虽然很详细,但常用的也就前几个。
下表是Mat类的复制构造函数,展示了如何从一个数组创建另一个数组,可以从一个已经存在的数组的子区域创建一个数组,或者从一些矩阵表达中生成一些新的矩阵,如下表:
模板构造函数并不会从Mat中创建一个模板出来,而是根据模板创建一个Mat实例,这些构造函数允许通过模板类Vec<>或Matx<>来创建一个对应维度和类型的Mat,或者使用一个STL vector<>来创建一个相同类型的数组。如下表:
Mat类也提供了一些静态方法来创建一些常用的数组,如下表,这些方法包括zeros(),ones(),eye(),对应的将建立一个全为0的矩阵,一个全为1的矩阵及一个单位矩阵。
在图像处理的过程中,经常会对图像的像素进行操作,那么该怎么访问图像的像素呢?通常用到的有三种访问数据的方法:
直接访问是通过模板函数at<>来实现的。这个函数有很多种变体,对不同维度的数组有不同的参数要求。这个函数的工作方式是先将at<>()特化到矩阵所包含的数据类型,然后通过你想要数据的行和列的位置来访问该元素,简单实例如下:
cv::Mat m = cv::Mat::eye( 10, 10, 32FC1 );
printf(
"Element (3,3) is %f\n",
m.at<float>(3,3)
);
多通道数组的操作与单通道数组相类似,如下:
cv::Mat m = cv::Mat::eye( 10, 10, 32FC2 );
printf(
"Element (3,3) is (%f,%f)\n",
m.at<cv::Vec2f>(3,3)[0],
m.at<cv::Vec2f>(3,3)[1]
);
为了访问二维数组,可以使用C风格的指针来指定某一行(由于数据是按行连续组织的,所以不能指定某一列),这个工作由cv::Mat类的成员函数ptr<>()完成。由于at<>()和ptr<>()都是模板函数,所以需要一个类型名来实例化。函数接收一个整型参数来指示希望指针指向的行,返回一个和矩阵原始数据类型相同的数据指针,比如,如果数组类型是CV_32FC3,那么它将会返回一个float*指针。 因此,给定一个类型为float三通道的矩阵mtx,那么结构体mtx.ptr<Vec3f>(3)将会返回mtx的第三行指向的第一个元素第一个通道的指针,这通常是访问数组最快的方式。
OpenCV提供一对迭代器模板,一个用于只读(const)数组的和一个用于非只读的(ono-const)数组的。上述两个迭代器分别被命名为,MatConstIterator<>和MatIterator<>。Mat的成员函数begin()和end()会返回这种类型的对象。因为迭代器具有足够的智能来处理连续的内存区域和非连续的内存区域,所以这种用法非常方便,不管在哪一种维度的数组中都非常有效。 所有的迭代器都必须在数组建立的时候声明并且指定一个对象类型。下面看一个简单的例子:
int sz[3] = { 4, 4, 4 };
cv::Mat m( 3, sz, CV_32FC3 ); // A three-dimensional array of size 4-by-4-by-4
cv::randu( m, -1.0f, 1.0f ); // fill with random numbers from -1.0 to 1.0
float max = 0.0f; // minimum possible value of L2 norm
cv::MatConstIterator<cv::Vec3f> it = m.begin();
while( it != m.end() ) {
len2 = (*it)[0]*(*it)[0]+(*it)[1]*(*it)[1]+(*it)[2]*(*it)[2];
if( len2 > max ) max = len2;
it++;
}
我们还可以将一个数组的子集作为另一个数组访问。这个子集可能是一行或者一列,也可能是原始数据的一个子集。如下表,有很多方法都可以让我们完成这个目的,所有的方法都是Mat的成员函数,并且都返回我们所请求的数组的子集。最简单的方法就是row()和col(),它将一个整型变量作为参数并返回这个变量所指引的行或列。
作为简单代数表达的补充,下表列出了可使用的代数操作的样例。
上面我们学习了大量的Mat的成员函数,但是还有一些很常用的成员函数,如下表:
至此,我们一起学习了OpenCV中最重要的一个类型,下一期,我们将一起学习图像处理中的矩阵操作。
智能算法,与您携手,沉淀自己,引领AI!