【opencv15】cv::Mat类单独访问数组元素
1.利用at<>()成员函数访问数组元素
最基础的直接访问手段是通过模板成员函数at<>(),对数组元素进行访问。因为是模板函数,所以该函数可以接受各种类型和维度的参数。使用该函数访问数组元素的例子如下:
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
int main()
{
Mat m_1 = cv::Mat::eye(10,10,CV_32FC1);
Mat m_2 = cv::Mat::eye(3, 3, CV_32FC3);
int sz[] = { 3, 3, 3 };
Mat m_3(3, sz, CV_32FC3, Scalar::all(0));
//part one
printf(
"Float Element (3,3) is %f\n",
m_1.at<float>(3, 3)
);
//part two
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf(
"Float Element (x,y)=(%d,%d) is (C0,C1,C2)=(%f,%f,%f)\n",
i,
j,
m_2.at<cv::Vec3f>(i, j)[0],
m_2.at<cv::Vec3f>(i, j)[1],
m_2.at<cv::Vec3f>(i, j)[2]
);
}
}
//part three
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
for (int k = 0; k < 3; k++)
{
printf(
"Float Element (x,y,z)=(%d,%d,%d) is (C0,C1,C2)=(%f)\n",
i,
j,
k,
m_3.at<cv::Vec3f>(i, j,k)[0],
m_3.at<cv::Vec3f>(i, j,k)[1],
m_3.at<cv::Vec3f>(i, j,k)[2]
);
}
}
}
getchar();
return 0;
}
代码输出结果
结果分析:上述代码有三个部分。
部分一:访问的是二维,单通道数组。
//part 1
Mat m_1 = cv::Mat::eye(10,10,CV_32FC1);
printf(
"Float Element (3,3) is %f\n",
m_1.at<float>(3, 3)
);
- Mat m_1 = cv::Mat::eye(10,10,CV_32FC1):定义了一个10乘以10的32float的一维单位数组(CV_32FC1)
-
m_1.at<float>(3,3):模板成员函数m_1.at<>,输入模板参数float,(3,3)表示访问的是一维数组中第四行第四列的元素,如下图所示。
二维数组
部分二:访问的是二维,三通道数组。
//part 2
Mat m_2 = cv::Mat::eye(3,3,CV_32FC3);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf(
"Float Element (x,y)=(%d,%d) is (Z0,Z1,Z2)=(%f,%f,%f)\n",
i,
j,
m_2.at<cv::Vec3f>(i, j)[0],
m_2.at<cv::Vec3f>(i, j)[1],
m_2.at<cv::Vec3f>(i, j)[2]
);
}
}
- Mat m_2 = cv::Mat::eye(3,3,CV_32FC3):定义了一个3乘以3的32float的三维单位数组(CV_32FC3)
- m_2.at<cv::Vec3f>(i, j)[0]:模板成员函数m_2.at<>,输入模板参数cv::Vec3f,(i,j)[0]表示访问的是三维数组中第i+1行,第j+1列,通道一的元素。
- m_2.at<cv::Vec3f>(i, j)[1]:模板成员函数m_2.at<>,输入模板参数cv::Vec3f,(i,j)[0]表示访问的是三维数组中第i+1行,第j+1列,通道二的元素。
- m_2.at<cv::Vec3f>(i, j)[2]:模板成员函数m_2.at<>,输入模板参数cv::Vec3f,(i,j)[0]表示访问的是三维数组中第i+1行,第j+1列,通道三的元素。
-
cv::Vec3f:对于访问多通道(本例中为3通道)数组而言,不能直接用float作为模板参数,因为正如上面三个访问元素语句所示,其实对于当前类型而言直接访问的不是单个元素,而是所有通道中的第i+1行,j+1列的元素向量(m_2.at<cv::Vec3f>(i, j)),空间示意图如下图所示。因此在访问多通道数组时,需要传入固定向量类型作为模板参数。
上图eye成员函数生成的三通道矩阵示意图
部分三:访问的是三维,三通道数组。
//part three
int sz[] = { 3, 3, 3 };
Mat m_3(3, sz, CV_32FC3, Scalar::all(0));
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
for (int k = 0; k < 3; k++)
{
printf(
"Float Element (x,y,z)=(%d,%d,%d) is (C0,C1,C2)=(%f)\n",
i,
j,
k,
//对应的是此种类型CV_32FC3,若类型为CV_32FC1时
//需要用 m_3.at<float>(i, j,k)访问。
m_3.at<cv::Vec3f>(i, j,k)[0],
m_3.at<cv::Vec3f>(i, j,k)[1],
m_3.at<cv::Vec3f>(i, j,k)[2]
);
}
}
}
该部分通过直接访问的手段,让我们能够进一步感受到,多维度和多通道的区别。下图之前也有用到过,多维度和多通道的概念切记不要混淆。
- m_3.at<float>(i, j,k):上述注释中也有提到,当数据类型为单通道的时候,需要用float访问,多通道的时候需要用固定向量类的对应格式的别名来访问(此例中是三通道浮点数所以用Vec3f)。
2.利用ptr<>()成员函数访问数组元素
要访问二维数组,还可以提取指向数组特定行的C样式指针。 这是通过cv :: Mat的ptr <>()模板成员函数完成的(回想一下,数组中的数据是连续的,因此以这种方式访问特定列是没有意义的; 我们很快就会看到正确的方法。)
与at>()一样,ptr <>()是一个使用类型名称实例化的模板函数。 它需要一个整数参数来指示您希望获得指针的行。 该函数返回一个指向构造数组的基本类型的指针(即,如果数组类型是CV_32FC3,则返回值的类型为float *)。因此,给定float类型的三通道矩阵mtx,构造mtx.ptr <Vec3f>(3)将返回指向mtx第3行中第一个元素的第一个(浮点)通道的指针。这通常是 访问数组元素的最快方法,因为一旦有了指针,就可以找到数据。
因此有两种方法可以在矩阵mtx中获得指向数据的指针。 一种是上述的使用ptr <>()成员函数。 另一种是直接使用成员指针数据,并使用成员数组step []来计算地址。后一种选择类似于C接口中常用的选项,但在at<>(),ptr <>()和迭代器访问数组的过程中,通常不再优先 。 话虽如此,直接地址计算可能仍然是最有效的,特别是当您处理大于两个维度的数组时。
关于C风格的指针访问,最终要的一点是, 如果要访问数组中的所有内容,您可能希望能够一次迭代一行; 这是因为行不一定会在数组中连续打包(packed?)。但是成员函数isContinuous()会告诉你行是否在数组中连续打包(packed)。如果判断完了之后,当前数组中行是连续的,那么你只需要找到第一行中第一个元素的指针,那么你就可以访问真个数组,就好像这个多维数组是个巨大的一维数组一样。
3.利用迭代器访问数组元素
顺序访问的另一种形式是使用cv :: Mat中内置的迭代器机制。这种机制和STL标准库中的提供的机制非常的类似。基本思想是OpenCV提供了迭代器模板。cv :: Mat成员函数begin()和end()返回此类型的对象。这种迭代方法很方便,因为迭代器足够智能,可以自动处理连续打包和非连续打包,以及处理数组中的任意数量的维度。
必须声明每个迭代器,并在声明阶段将其指定为构造数组对象的类型。以下是迭代器的一个简单示例,用于计算三通道元素的三维数组中的“最长”元素。
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
int main()
{
//int sz[] = { 3, 3, 3 };
//Mat m_3(3, sz, CV_32FC3, Scalar::all(0));
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
Mat_<Vec3f>::iterator it = m.begin<Vec3f>();
Mat_<Vec3f>::iterator itend = m.end<Vec3f>();
while (it != itend) {
(*it)[0] = 255;
(*it)[1] = 255;
(*it)[2] = 255;
it++;
}
getchar();
return 0;
}
- cv::randu(m, -1.0f, 1.0f):随机生成-1到1的值,并填满多维多通道数组。
- Mat_<Vec3f>::iterator it = m.begin<Vec3f>():利用begin成员函数,返回Vec3f类型的指针(迭代器),指向数组的头部。
- Mat_<Vec3f>::iterator itend = m.end<Vec3f>():利用end成员函数,返回Vec3f类型的指针(迭代器),指向数组的尾部。
- (*it)[0] = 255;
(*it)[1] = 255;
(*it)[2] = 255:对每一个位置的三个通道作一个相同的操作。
在对整个数组执行操作时,通常会使用基于迭代器的访问,或者在多个数组之间执行一些基于元素级的操作(Elementwise)。比如将两个数组相加或将数组从RGB颜色空间转换为HSV颜色空间这样的案例中,对于每个像素位置而言,进行的都是相同精确操作,正如上述代码所示,此时使用迭代器会比较方便。
给自己打个小广告
本人211硕士毕业生,目前从事深度学习,机器学习计算机视觉算法行业,目前正在将我的各类学习笔记发布在我的公众号中,希望感兴趣一起学习的同学们可以关注下~~~
本人微信公众号:yuanCruise