上一個學習筆記中對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下效率基本都是一樣的,選擇最直覺的亦可。