OpenCV:Mat元素訪問方法、性能、代碼復(fù)雜度以及安全性分析
歡迎轉(zhuǎn)載,尊重原創(chuàng),所以轉(zhuǎn)載請注明出處:
http://blog.csdn.net/bendanban/article/details/30527785
本文講述了OpenCV中幾種訪問矩陣元素的方法,在指定平臺上給出性能比較,分析每種矩陣元素訪問方法的代碼復(fù)雜度,易用性。
本文假設(shè)你已經(jīng)正確配置了OpenCV的環(huán)境,為方便大家實(shí)驗(yàn),在文中也給出了編譯源程序的Makefile,其內(nèi)容如代碼段1所示。
采用如代碼段2所示的計時函數(shù),這段代碼你可以在我之前的博文中找到,abtic() 可以返回微秒(10^-6秒)級,而且兼容Windows和Linux系統(tǒng)。
本文使用彩色圖像做實(shí)驗(yàn),所以矩陣是2維的3通道的。
CC = g++
CPPFLAGS = -O3 `pkg-config --cflags opencv`
CPPLIB = `pkg-config --libs opencv`
OBJS = test.o
main.exe : $(OBJS)
$(CC) $(CPPFLAGS) $^ -o $@ $(CPPLIB)
test.o: test.cpp
$(CC) -c $(CPPFLAGS) $^ -o $@
clean:
rm -rf *.out main.exe *.o
run:
./main.exe
代碼段 1. Makefile文件的內(nèi)容#if defined(_WIN32) && defined(_MSC_VER)
#include
double abtic() {
__int64 freq;
__int64 clock;
QueryPerformanceFrequency( (LARGE_INTEGER *)&freq );
QueryPerformanceCounter( (LARGE_INTEGER *)&clock );
return (double)clock/freq*1000*1000;
}
#else
#include
#include
double abtic() {
double result = 0.0;
struct timeval tv;
gettimeofday( &tv, NULL );
result = tv.tv_sec*1000*1000 + tv.tv_usec;
return result;
}
#endif /* _WIN32 */
代碼段 2. 計時函數(shù)abtic()的定義??? 文中用于測試的算法:將矩陣中每個元素乘以一個標(biāo)量,寫入一個新的矩陣,每個通道操作獨(dú)立。
??? 如果用im(r,c,k)表示矩陣im的第r行、第c列、第k個通道的值的話,算法為:om(r,c,k) = im(r,c,k)*scale;其中scale是一個大于0、小于1的浮點(diǎn)數(shù)。
??? Mat的成員函數(shù)at()是一個模板函數(shù),我們這里用的是二維矩陣,所以我們使用的at()函數(shù)的聲明如代碼段3所示(取自OpenCV的源文件)。
template _Tp& at(int i0, int i1);
代碼段3 .at()函數(shù)的聲明??? 代碼段4是本文第二部分描述的算法的實(shí)現(xiàn),矩陣元素使用at<>()函數(shù)來索引。
Vec3b pix;
for (int r = 0; r < im.rows; r++)
{
for (int c = 0; c < im.cols; c++)
{
pix = im.at(r,c);
pix = pix*scale;
om.at(r,c) = pix;
}
}
代碼段4. 使用at<>()函數(shù)訪問矩陣元素??? 注意:使用at函數(shù)時,應(yīng)該知道矩陣元素的類型和通道數(shù),根據(jù)矩陣元素類型和通道數(shù)來確定at函數(shù)傳遞的類型,代碼段4中使用的是Vec3b這個元素類型,他是一個包含3個unsigned char類型向量。之所以采用這個類型來接受at的返回值,是因?yàn)?,我們的矩陣im是3通道,類型為unsigned char類型的。
??? 此函數(shù)也是模板函數(shù),我們將會用到的ptr函數(shù)聲明如代碼段5所示。此函數(shù)返回指定的數(shù)據(jù)行的首地址。
template _Tp* ptr(int i0=0);
代碼段 5. ptr成員函數(shù)的聲明??? 使用ptr<>()成員函數(shù)完成本文第二部分所述算法的代碼如代碼段6所示。
Vec3b *ppix_im(NULL);
Vec3b *ppix_om(NULL);
for (int r = 0; r < im.rows; r++)
{
ppix_im = im.ptr(r);
ppix_om = om.ptr(r);
for (int c = 0; c < im.cols; c++)
{
ppix_om[c] = ppix_im[c]*scale;
}
}
代碼段 6. 使用ptr訪問矩陣元素??? 這里使用的迭代器是OpenCV自己定義的。使用迭代器完成第二部分所述算法的代碼如代碼段7所示。
MatIterator_ it_im, itEnd_im;
MatIterator_ it_om;
it_im = im.begin();
itEnd_im = im.end();
it_om = om.begin();
for (; it_im != itEnd_im; it_im++, it_om++)
{
*it_om = (*it_im)*scale;
}
代碼段 7. 使用迭代器訪問矩陣元素??? Mat_這個類的元素訪問比較容易一點(diǎn),把原Mat類的對象可以直接賦值給Mat_對象,當(dāng)然賦值操作并不會開辟新的數(shù)據(jù)空間,這點(diǎn)大家放心。也就是說使用Mat_時,不會在內(nèi)存拷貝上花時間。使用這種方法完成第二部分所述算法的代碼如代碼段8所示。
Mat_ im_, om_;
im_ = im;
om_ = om;
for (int r = 0; r < im.rows; r++)
{
for (int c = 0; c < im.cols; c++)
{
om_(r,c) = im_(r,c) * scale;
}
}
代碼段 8. 使用Mat_訪問矩陣數(shù)據(jù)元素??? 我們的算法實(shí)際上OpenCV中已經(jīng)有實(shí)現(xiàn)。就是×運(yùn)算符重載,代碼如代碼段9所示。
om = im*scale;
代碼段 9. 使用OpenCV的原有實(shí)現(xiàn)訪問矩陣元素??? 為了測試方便,將前面的方法統(tǒng)一寫到一個c++源文件test.cpp中,其內(nèi)容如代碼段10所示。
/*************************************************************************
> File Name: test.cpp
> Author: aban
> Mail: sawpara@126.com
> Created Time: 2014年06月13日 星期五 18時47分19秒
************************************************************************/
#include
#include
using namespace cv;
using namespace std;
#if defined(_WIN32) && defined(_MSC_VER)
#include
double abtic() {
__int64 freq;
__int64 clock;
QueryPerformanceFrequency( (LARGE_INTEGER *)&freq );
QueryPerformanceCounter( (LARGE_INTEGER *)&clock );
return (double)clock/freq*1000*1000;
}
#else
#include
#include
double abtic() {
double result = 0.0;
struct timeval tv;
gettimeofday( &tv, NULL );
result = tv.tv_sec*1000*1000 + tv.tv_usec;
return result;
}
#endif /* _WIN32 */
#define ISSHOW 0
int main(int argc, char** argv)
{
double tRecorder(0.0);
Mat im = imread("./bigim.tif");
Mat om;
om.create(im.rows, im.cols, CV_8UC3);
#if ISSHOW
imshow("orignal Image", im);
waitKey();
#endif
float scale = 150.0f/255.0f;
// 1. using at()
tRecorder = abtic();
Vec3b pix;
for (int r = 0; r < im.rows; r++)
{
for (int c = 0; c < im.cols; c++)
{
pix = im.at(r,c);
pix = pix*scale;
om.at(r,c) = pix;
}
}
cout << (abtic() - tRecorder) << " using at<>()" << endl;
#if ISSHOW
imshow("Scaled Image: using at<>()", om);
waitKey();
#endif
// 2. using ptr
tRecorder = abtic();
Vec3b *ppix_im(NULL);
Vec3b *ppix_om(NULL);
for (int r = 0; r < im.rows; r++)
{
ppix_im = im.ptr(r);
ppix_om = om.ptr(r);
for (int c = 0; c < im.cols; c++)
{
ppix_om[c] = ppix_im[c]*scale;
}
}
cout << (abtic() - tRecorder) << " using ptr<>() " << endl;
#if ISSHOW
imshow("Scaled Image: using ptr<>()", om);
waitKey();
#endif
// 3. using iterator
tRecorder = abtic();
MatIterator_ it_im, itEnd_im;
MatIterator_ it_om;
it_im = im.begin();
itEnd_im = im.end();
it_om = om.begin();
for (; it_im != itEnd_im; it_im++, it_om++)
{
*it_om = (*it_im)*scale;
}
cout << (abtic() - tRecorder) << " using iterator " << endl;
#if ISSHOW
imshow("Scaled Image: using iterator", om);
waitKey();
#endif
// 4. using Mat_
tRecorder = abtic();
Mat_ im_, om_;
im_ = im;
om_ = om;
for (int r = 0; r < im.rows; r++)
{
for (int c = 0; c < im.cols; c++)
{
om_(r,c) = im_(r,c) * scale;
}
}
cout << (abtic() - tRecorder) << " using Mat_ " << endl;
#if ISSHOW
imshow("Scaled Image: using Mat_", om);
waitKey();
#endif
// 5. using *
tRecorder = abtic();
om = im*scale;
cout << (abtic() - tRecorder) << " using * " << endl;
#if ISSHOW
imshow("Scaled Image: using *", om);
waitKey();
#endif
return 0;
}
代碼段10. 測試代碼??? 如果你想使用第一部分提到的Makefile,你需要將代碼段10保存成test.cpp,或者保存成你希望的某個名字,但是同時應(yīng)該修改Makfile中的所有“test.cpp”。
??? 在正確執(zhí)行之前,將代碼段10中的第40行代碼改成你的圖片名稱。
CPU:Intel(R) Pentium(R) CPU G840 @ 2.80GHz
G++:4.8.2
OpenCV : 2.4.9
編譯選項(xiàng)使用-O3時,其中一次執(zhí)行結(jié)果:
489570 using at<>()
467315 using ptr<>()
468603 using iterator
469041 using Mat_
621367 using *
編譯選項(xiàng)使用-O0 -g時,其中一次執(zhí)行結(jié)果:
2.48216e+06 using at<>()
2.15397e+06 using ptr<>()
3.80784e+06 using iterator
2.38941e+06 using Mat_
621099 using *
從上面的結(jié)果可以看出,使用×?xí)r,在兩種模式下,計算速度差不多,這實(shí)際是由于我們的程序調(diào)用的OpenCV的庫函數(shù),而這個庫函數(shù)調(diào)用的是同一個。
如果你的產(chǎn)品要求執(zhí)行速度,從-O3條件下的輸出結(jié)果可以看出,ptr這種方式速度稍微快一點(diǎn)。但是他們的差別并不大,所以應(yīng)該再考慮代碼的復(fù)雜度。
代碼復(fù)雜度用代碼量(代碼行數(shù)、列數(shù))、使用變量的個數(shù)、使用變量個類型掌握難度(比如指針可能難一點(diǎn))等因素來度量。
最小的就是使用×了(最后一個方法)。雖然他的復(fù)雜度較小,實(shí)際只有一行代碼,但是對于實(shí)際的應(yīng)用,你要想調(diào)用OpenCV已經(jīng)實(shí)現(xiàn)的功能,首先要確定OpenCV里已經(jīng)實(shí)現(xiàn)了這個功能。
其次,我認(rèn)為復(fù)雜度較小的是方法一,因?yàn)樗鼘?shí)際上可以不借用pix變量,完成前述算法,使用變量數(shù)較少,代碼量也不多。
Mat_和ptr這兩種方式的復(fù)雜度差不多,如果使用指針是一種稍微難一點(diǎn)的方式的話,那么Mat_的復(fù)雜度可以認(rèn)為稍微小一點(diǎn)。
一般認(rèn)為迭代器是C++里面比較高級的特性,也是學(xué)習(xí)C++最靠后的技術(shù),再加上它使用了指針,如果指針?biāo)闶潜容^難掌握的技術(shù)的話,使用迭代器這種方式復(fù)雜度可以說是最復(fù)雜的了。
有些情況下,需要考慮安全性,比如防止越界訪問,如果你不想考慮過多邊界的問題,使用迭代器也許是一種不錯的選擇!
選擇哪種元素訪問方式,應(yīng)該根據(jù)自己的實(shí)際應(yīng)用環(huán)境,具體分析作出決定。主要考慮三個因素:性能、代碼復(fù)雜度、安全性,根據(jù)自己的程序類型,選擇。