天天看點

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(); //結果為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下效率基本都是一樣的,選擇最直覺的亦可。