上一个学习笔记中对Mat进行了详细的介绍,并且简单介绍了Mat元素访问的3种基本方法,通过ptr行指针、data数据指针和at模板函数。本章以二维矩阵为例,对各种访问方式进行详细的介绍,并给出各种访问方式在debug模式下的效率对比结果。
1、执行时间获取
要比较执行效率,就要获取某一段程序执行的时间,可以利用opencv提供的c接口和c++接口两种方法,都是利用系统计数和单位时间计数频率计算。
第一种:cvGetTickCount() 和 cvGetTickFrequency() ,比值为毫秒ms
double exec_time = (double)cvGetTickCount();
// do something ...
exec_time = ((double)cvGetTickCount() - exec_time)/cvGetTickFrequency(); //结果为ms
第二种:getTickCount() 和 getTickCountFrequency(), 比值为秒s
double exec_time = (double)getTickCount();
// do something ...
exec_time = ((double)getTickCount() - exec_time)*1000/getTickFrequency(); //结果为ms
2、Mat元素访问方式比较
对矩阵的访问和复制,给定矩阵 Mat M = Mat::ones(100, 100, CV_64F);
1)、用ptr行指针
for (int i = 0; i < M.rows; i++)
{
const double *Mi = M.ptr<double>(i); // 第i行指针
for (int j = 0; j < M.cols; j++)
sum += Mi[j]; // 第j列元素 32us
}
另外,通过判断数据内存区域为连续,可以将矩阵当做一个长向量进行访问(下面改用首地址用data获取,但是data默认是 uchar* 类型,需要进行强制转换):
if (M.isContinuous()) // 一般手动分配或者深复制的矩阵,都是连续的。
{
//const double*Mi = M.ptr<double>(0); 32us
const double*Mi = (double *)M.data; 32us
for (int j = 0; j < M.cols*M.rows; j++)
sum += Mi[j];
}
2)、用data指针
注意用 step 和 step1 获取一行元素占用字节数后强制转换的区别。
for (int i = 0; i < M.rows; i++)
{
const double *Mi = (double *)( M.data + i*M.step[0]); // 用data获取行指针,类型需要强制转换
//const double *Mi = (double *)M.data + i*M.step1(0); //另一获取行元素字节数的方法,注意同上比较
// 2种方法都是 32us
for (int j = 0; j < M.cols; j++)
sum += Mi[j];
}
另外可以直接找到第i行j列的元素的指针,之后用*取值。
for (int i = 0; i < M.rows; i++)
{
for (int j = 0; j < M.cols; j++)
//sum += *( (double*)M.data + i*M.step1(0) + j * M.step1(1) ); //同样注意两种方法
sum += *(double*)( M.data + i*M.step[0] + j * M.step[1] ); // 2种方法都是 485us
}
3)、用at函数
直接定位到第i行j列的元素。
for (int i = 0; i < M.rows; i++)
for (int j = 0; j < M.cols; j++)
sum += M.at<double>(i, j); // 730us
4)、用迭代器访问
迭代器也是需要指定数据类型,begin()和end()返回起始、终止迭代器(可以当做一个地址连续的指针),通过*访问当前元素,当it自加到等于it_end时遍历结束(说明*it_end不是最后一个元素)。
MatConstIterator_<double> it = M.begin<double>(); // 起始迭代器
MatConstIterator_<double> it_end = M.end<double>(); // 终止迭代器
for (; it != it_end; ++it) // 自加遍历结束后,it为it_end
sum += *it; // 940us
小结:上面的1) ~ 4)都是对同一个矩阵进行遍历求和,且在debug模式下,比较而言,用data和ptr的方式先获取行指针、再获取列指针的方式,比用指针直接定位到(i, j)效率高,at其次,迭代器最慢。在release模式下,各种方法的执行效率相同,且基本都在30us比debug模式略快。因此,可以根据个人喜好选择哪一种方式,毕竟最后交付的都是release版本。
5)、用其他数据结构进行访问
Mat m = (Mat_<float>(2, 6) << 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12 );
coutMat("m = ", m);
Point2f p = m.at<Vec2f>(0,2);
// vec2f(0/1,0/1/2), vec3f(0/1,0/1);
cout << p << endl; // 5,6 // vecNf(i,j) , i=0~rows-1, j=0~cols/N-1 ( cols%2==0 )
上例中,矩阵m是一个类型为float的2行6列的单通道矩阵,但是通过Vec2f访问,也就是一个长度为2的浮点类型矢量,最后将Vec2f取到的元素赋值给一个Point2f的一个浮点二维点。
Mat中是允许用其他类型进行访问,但是数据类型和元素个数有要求。这里Mat是float,那么取值时at的Vec类型应该也为float;Mat一行的元素个数必须是Vec类型元素个数的整数倍,本例中一行元素个数为6,Vec2f的元素个数为2,正好整除为3。
另外,本例中用Vec2f对Mat取值时,at的的第二维索引值要效益是上述的整数倍数值,也就是3,at(i,j)中第一维i取值为0、1,第二维j取值为0、1、2。 原来的矩阵变为 { (1,2), (3,4), (5,6) ; (7,8), (9,10), (11,12)},2行3列,每个元素都是Vec2f或者Point2f,还可以看成为一个2行3列的2通道矩阵。 这也是多通道矩阵元素访问的一种方式。
6)、多通道元素的访问
下面的矩阵是用Mat_创建,类型为Vec3b,可看做RGB图像,通常这种方式就是为了处理图像。第三种用ptr获取了行指针,再定位某一列是最快的(还可以用data获取行指针,可自己编写),另外2种较慢。当然这些比较都是在debug下的,release下指针比另外两种略快一点。
Mat_<Vec3b> img(240, 320, Vec3b(0, 255, 0)); //初始化
//第一种 9.3ms
double t = (double)getTickCount();
for (int i = 0; i < img.rows; i++)
for (int j = 0; j < img.cols; j++)
img(i, i) = Vec3b(255, 255, 255);
cout << ((double)getTickCount() - t)*1000. / getTickFrequency() << "ms" << endl; // 9.3 ms
// 第二种 21ms
for (int i = 0; i < img.rows; i++)
for (int j = 0; j < img.cols; j++)
{
img(i, j)[0] = 255;
img(i, j)[1] = 255;
img(i, j)[2] = 255;
}
cout << ((double)getTickCount() - t)*1000. / getTickFrequency() << "ms" << endl; // 21 ms
// 第三种 0.26ms
for (int i = 0; i < img.rows; i++)
{
uchar *Mi = img.ptr<uchar>(i);
for (int j = 0; j < img.cols; j++)
{
Mi[3 * j + 0] = 255;
Mi[3 * j + 1] = 255;
Mi[3 * j + 2] = 255;
// 两种方式相同
// (Mi + 3 * j)[0] = 255;
// (Mi + 3 * j)[1] = 255;
// (Mi + 3 * j)[2] = 255;
}
}
cout << ((double)getTickCount() - t)*1000. / getTickFrequency() << "ms" << endl; // 0.26 ms
Ps:Mat()构造的多通道矩阵,访问也可以通过Vec访问某个元素直接访问或者赋值,也可以用按照第三种方式用指针找到元素的各个通道分别赋值。
总之,对于Mat的创建和访问,需要灵活对待,能用最好的方式当然最好;在release下效率基本都是一样的,选择最直观的亦可。