OpenCv2 学习笔记(6) Mat元素访问和效率比较

上一个学习笔记中对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(); 

第二种:getTickCount() 和 getTickCountFrequency(), 比值为秒s

double exec_time = (double)getTickCount();
// do something ...
exec_time = ((double)getTickCount() - exec_time)*1000/getTickFrequency();  

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;                     
    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] );   
}

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);             

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;                                      

 

 
小结:上面的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;    //  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下效率基本都是一样的,选择最直观的亦可。

猜你喜欢

转载自blog.csdn.net/wanggao_1990/article/details/53264754