9.读取Mat类元素
1、Mat类常用属
属性 |
作用 |
cols |
矩阵的列数 |
rows |
矩阵的行数 |
step |
以字节为单位的矩阵的有效宽度 |
elemSize() |
每个元素的字节数 |
total() |
矩阵中元素的个数 |
channels() |
矩阵的通道数 |
2、通过at方法读取Mat类矩阵中的元素
通过at方法读取矩阵元素分为针对单通道的读取方法和针对多通道的读取方法,在代码清单2-19中给出了通过at方法读取单通道矩阵元素的代码。
代码清单2-19 at方法读取Mat类单通道矩阵元素 cv::Mat a = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9); int value = (int)a.at<uchar>(0, 0);
通过at方法读取元素需要在后面跟上“<数据类型>”,如果此处的数据类型与矩阵定义时的数据类型不相同,就会出现因数据类型不匹配的报错信息。该方法以坐标的形式给出需要读取的元素坐标(行数,列数)。需要说明的是,如果矩阵定义的是uchar类型的数据,在需要输入数据的时候,需要强制转换成int类型的数据进行输出,否则输出的结果并不是整数。
由于单通道图像是一个二维矩阵,因此在at方法的最后给出二维平面坐标即可访问对应位置元素。而多通道矩阵每一个元素坐标处都是多个数据,因此引入一个变量用于表示同一元素多个数据。在openCV 中,针对3通道矩阵,定义了cv::Vec3b、cv::Vec3s、cv::Vec3w、cv::Vec3d、cv::Vec3f、cv::Vec3i六种类型用于表示同一个元素的三个通道数据。通过这六种数据类型可以总结出其命名规则,其中的数字表示通道的个数,最后一位是数据类型的缩写,b是uchar类型的缩写、s是short类型的缩写、w是ushort类型的缩写、d是double类型的缩写、f是float类型的缩写、i是int类型的缩写。当然OpenCV也为2通道和4通道定义了对应的变量类型,其命名方式也遵循这个命名规则,例如2通道和4通道的uchar类型分别用cv::Vec2b和cv::Vec4b表示。代码清单2-20中给出了通过at方法读取多通道矩阵的实现代码。
代码清单2-20 at方法读取Mat类多通道矩阵元素 cv::Mat b(3, 4, CV_8UC3, cv::Scalar(0, 0, 1)); cv::Vec3b vc3 = b.at<cv::Vec3b>(0, 0); int first = (int)vc3.val[0]; int second = (int)vc3.val[1]; int third = (int)vc3.val[2];
在使用多通道变量类型时,同样需要注意at方法中数据变量类型与矩阵的数据变量类型相对应,并且cv::Vec3b类型在输入每个通道数据时需要将其变量类型强制转成int类型。不过,如果直接将at方法读取出的数据直接赋值给cv::Vec3i类型变量,就不需要在输出每个通道数据时进行数据类型的强制转换。
前面我们分析过Mat类矩阵在内存中的存放方式,矩阵中每一行中的每个元素都是挨着存放,如果找到每一行元素的起始地址位置,那么读取矩阵中每一行不同位置的元素就是将指针在起始位置向后移动若干位即可。在代码清单2-21中给出了通过指针ptr读取Mat类矩阵元素的代码实现。
代码清单2-21 指针ptr读取Mat类矩阵元素 cv::Mat b(3, 4, CV_8UC3, cv::Scalar(0, 0, 1)); for (int i = 0; i < b.rows; i++) { uchar* ptr = b.ptr<uchar>(i); for (int j = 0; j < b.cols*b.channels(); j++) { cout << (int)ptr[j] << endl; } }
在程序里,首先有一个大循环用来控制矩阵中每一行,之后定义一个uchar类型的指针ptr,在定义时需要声明Mat类矩阵的变量类型,并在定义最后用小括号声明指针指向的Mat类矩阵的哪一行。第二个循环控制用于输出矩阵中每一行所有通道的数据。根据图2-5中所示的存储形式,每一行中存储的数据数量为列数与通道数的乘积,即指针可以向后移动cols*channels()-1位,如第7行代码所示,指针向后移动的位数在中括号给出。程序中给出了循环遍历Mat类矩阵中的每一个数据的方法,当我们能够确定需要访问的数据时,可以直接通过给出行数和指针后移的位数进行访问,例如当读取第2行数据中第3个数据时,可以用a.ptr<uchar>(1)[2]这样的形式来直接访问。
3、通过迭代器访问Mat类矩阵中的元素
Mat类变量同时也是一个容器变量,所以Mat类变量拥有迭代器,用于访问Mat类变量中的数据,通过迭代器可以实现对矩阵中每一个元素的遍历,代码实现在代码清单2-22中给出。
代码清单2-22 指针ptr读取Mat类矩阵元素 cv::MatIterator_<uchar> it = a.begin<uchar>(); cv::MatIterator_<uchar> it_end = a.end<uchar>(); for (int i = 0; it != it_end; it++) { cout << (int)(*it) << " "; if ((++i% a.cols) == 0) { cout << endl; } }
Mat类的迭代器变量类型是cv::MatIterator_< >,在定义时同样需要在括号中声明数据的变量类型。Mat类迭代器的起始是Mat.begin< >(),结束是Mat.end< >(),与其他迭代器用法相同,通过“++”运算实现指针位置向下迭代,数据的读取方式是先读取第一个元素的每一个通道,之后再读取第二个元素的每一个通道,直到最后一个元素的最后一个通道。
4、通过矩阵元素地址定位方式访问元素
前面三种读取元素的方式都需要知道Mat类矩阵存储数据的类型,而且在从认知上,我们更希望能够通过声明“第x行第x列第x通道”的方式来读取某个通道内的数据,代码清单2-23中给出的就是这种读取数据的方式。
代码清单2-23 通过矩阵元素地址定位方式访问元素 (int)(*(b.data + b.step[0] * row + b.step[1] * col + channel));
代码中row变量的含义是某个数据所在元素的行数,col变量的含义是某个数据所在元素的列数,channel变量的含义是某个数据所在元素的通道数。这种方式与我们通过指针读取数据的形式类似,都是通过将首个数据的地址指针移动若干位后指向需要读取的数据,只不过这种方式可以通过直接给出行、列和通道数进行读取,不需要用户再进行计算某个数据在这行数据存储空间中的位置。