opencv Mat元素的访问方法

本文讲述了OpenCV中几种访问矩阵元素的方法,在指定平台上给出性能比较,分析每种矩阵元素访问方法的代码复杂度,易用性。


一、预备设置

本文假设你已经正确配置了opencv的环境,为方便大家实验,在文中也给出了编译源程序的Makefile,其内容如代码段1所示。

采用如代码段2所示的计时函数,这段代码你可以在我之前的博文中找到,abtic() 可以返回微秒(10^-6秒)级,而且兼容WindowsLinux系统。

本文使用彩色图像做实验,所以矩阵是2维的3通道的。

  1. CC = g++   
  2. CPPFLAGS = -O3 `pkg-config --cflags opencv`   
  3. CPPLIB   = `pkg-config --libs opencv`  
  4.   
  5. OBJS = test.o   
  6.   
  7. main.exe : $(OBJS)  
  8.   $(CC) $(CPPFLAGS) $^ -o $@ $(CPPLIB)  
  9.   
  10. test.o: test.cpp  
  11.   $(CC) -c $(CPPFLAGS) $^ -o $@  
  12.   
  13. clean:  
  14.   rm -rf *.out main.exe *.o  
  15.   
  16. run:  
  17.   ./main.exe  
代码段 1. Makefile文件的内容


  1. #if defined(_WIN32) && defined(_MSC_VER)  
  2. #include <windows.h>  
  3. double abtic() {  
  4.   __int64 freq;  
  5.   __int64 clock;  
  6.   QueryPerformanceFrequency( (LARGE_INTEGER *)&freq );  
  7.   QueryPerformanceCounter( (LARGE_INTEGER *)&clock );  
  8.   return (double)clock/freq*1000*1000;  
  9. }  
  10. #else  
  11. #include <time.h>  
  12. #include <sys/time.h>  
  13. double abtic() {  
  14.   double result = 0.0;  
  15.   struct timeval tv;  
  16.   gettimeofday( &tv, NULL );  
  17.   result = tv.tv_sec*1000*1000 + tv.tv_usec;  
  18.   return result;  
  19. }  
  20. #endif /* _WIN32 */  
代码段 2. 计时函数abtic()的定义


二、测试算法

    文中用于测试算法:将矩阵中每个元素乘以一个标量,写入一个新的矩阵,每个通道操作独立。

    如果用im(r,c,k)表示矩阵im的第r行、第c列、第k个通道的值的话,算法为:om(r,c,k) = im(r,c,k)*scale;其中scale是一个大于0、小于1的浮点数。


三、五种Mat元素的访问方法


方法1、使用Mat的成员函数at<>()

    Mat的成员函数at()是一个模板函数,我们这里用的是二维矩阵,所以我们使用的at()函数的声明如代码段3所示(取自OpenCV的源文件)。

  1. template<typename _Tp> _Tp& at(int i0, int i1);  
代码段3 .at()函数的声明


    代码段4是本文第二部分描述的算法的实现,矩阵元素使用at<>()函数来索引。

  1. Vec3b pix;  
  2. for (int r = 0; r < im.rows; r++)  
  3. {  
  4.   for (int c = 0; c < im.cols; c++)  
  5.   {     
  6.     pix = im.at<Vec3b>(r,c);  
  7.     pix = pix*scale;  
  8.     om.at<Vec3b>(r,c) = pix;  
  9.   }     
  10. }  
代码段4. 使用at<>()函数访问矩阵元素

    注意:使用at函数时,应该知道矩阵元素的类型和通道数,根据矩阵元素类型和通道数来确定at函数传递的类型,代码段4中使用的是Vec3b这个元素类型,他是一个包含3个unsigned char类型向量。之所以采用这个类型来接受at的返回值,是因为,我们的矩阵im是3通道,类型为unsigned char类型的。


方法2、使用Mat的成员函数ptr<>()

    此函数也是模板函数,我们将会用到的ptr函数声明如代码段5所示。此函数返回指定的数据行的首地址。

  1. template<typename _Tp> _Tp* ptr(int i0=0);  
代码段 5. ptr成员函数的声明

    使用ptr<>()成员函数完成本文第二部分所述算法的代码如代码段6所示。

  1. Vec3b *ppix_im(NULL);  
  2. Vec3b *ppix_om(NULL);  
  3. for (int r = 0; r < im.rows; r++)  
  4. {  
  5.   ppix_im = im.ptr<Vec3b>(r);  
  6.   ppix_om = om.ptr<Vec3b>(r);  
  7.   for (int c = 0; c < im.cols; c++)  
  8.   {  
  9.      ppix_om[c] = ppix_im[c]*scale;  
  10.   }  
  11. }  
代码段 6. 使用ptr访问矩阵元素


方法3、使用迭代器

    这里使用的迭代器是OpenCV自己定义的。使用迭代器完成第二部分所述算法的代码如代码段7所示。

  1. MatIterator_<Vec3b> it_im, itEnd_im;  
  2. MatIterator_<Vec3b> it_om;  
  3. it_im    = im.begin<Vec3b>();  
  4. itEnd_im = im.end<Vec3b>();  
  5. it_om    = om.begin<Vec3b>();  
  6. for (; it_im != itEnd_im; it_im++, it_om++)  
  7. {  
  8.   *it_om = (*it_im)*scale;  
  9. }  
代码段 7. 使用迭代器访问矩阵元素


方法4、使用Mat_简化索引

    Mat_这个类的元素访问比较容易一点,把原Mat类的对象可以直接赋值给Mat_对象,当然赋值操作并不会开辟新的数据空间,这点大家放心。也就是说使用Mat_时,不会在内存拷贝上花时间。使用这种方法完成第二部分所述算法的代码如代码段8所示。

  1. Mat_<Vec3b> im_, om_;  
  2. im_ = im;  
  3. om_ = om;  
  4. for (int r = 0; r < im.rows; r++)  
  5. {  
  6.   for (int c = 0; c < im.cols; c++)  
  7.   {  
  8.     om_(r,c) = im_(r,c) * scale;  
  9.   }  
  10. }  
代码段 8. 使用Mat_访问矩阵数据元素


方法5、使用OpenCV原有的实现

    我们的算法实际上OpenCV中已经有实现。就是×运算符重载,代码如代码段9所示。

  1. om = im*scale;  
代码段 9. 使用OpenCV的原有实现访问矩阵元素


四、实验测试

1、测试代码

    为了测试方便,将前面的方法统一写到一个c++源文件test.cpp中,其内容如代码段10所示。

  1. /************************************************************************* 
  2.   > File Name: test.cpp 
  3.   > Author: aban 
  4.   > Mail: sawpara@126.com  
  5.   > Created Time: 2014年06月13日 星期五 18时47分19秒 
  6.  ************************************************************************/  
  7.   
  8.   
  9. #include <iostream>  
  10. #include <opencv2/opencv.hpp>  
  11. using namespace cv;  
  12. using namespace std;  
  13.   
  14. #if defined(_WIN32) && defined(_MSC_VER)  
  15. #include <windows.h>  
  16. double abtic() {  
  17.     __int64 freq;  
  18.     __int64 clock;  
  19.     QueryPerformanceFrequency( (LARGE_INTEGER *)&freq );  
  20.     QueryPerformanceCounter( (LARGE_INTEGER *)&clock );  
  21.     return (double)clock/freq*1000*1000;  
  22. }  
  23. #else  
  24. #include <time.h>  
  25. #include <sys/time.h>  
  26. double abtic() {  
  27.     double result = 0.0;  
  28.     struct timeval tv;  
  29.     gettimeofday( &tv, NULL );  
  30.     result = tv.tv_sec*1000*1000 + tv.tv_usec;  
  31.     return result;  
  32. }  
  33. #endif /* _WIN32 */  
  34.   
  35. #define ISSHOW 0  
  36.   
  37. int main(int argc, char** argv)  
  38. {  
  39.     double tRecorder(0.0);  
  40.     Mat im = imread("./bigim.tif");  
  41.     Mat om;  
  42.     om.create(im.rows, im.cols, CV_8UC3);  
  43.   
  44. #if ISSHOW  
  45.     imshow("orignal Image", im);  
  46.     waitKey();  
  47. #endif  
  48.       
  49.     float scale = 150.0f/255.0f;  
  50.   
  51.     // 1. using at()  
  52.     tRecorder = abtic();  
  53.     Vec3b pix;  
  54.     for (int r = 0; r < im.rows; r++)  
  55.     {  
  56.         for (int c = 0; c < im.cols; c++)  
  57.         {  
  58.             pix = im.at<Vec3b>(r,c);  
  59.             pix = pix*scale;  
  60.             om.at<Vec3b>(r,c) = pix;  
  61.         }  
  62.     }  
  63.     cout << (abtic() - tRecorder) << " using at<>()" << endl;  
  64. #if ISSHOW  
  65.     imshow("Scaled Image: using at<>()", om);  
  66.     waitKey();  
  67. #endif  
  68.   
  69.     // 2. using ptr  
  70.     tRecorder = abtic();  
  71.     Vec3b *ppix_im(NULL);  
  72.     Vec3b *ppix_om(NULL);  
  73.     for (int r = 0; r < im.rows; r++)  
  74.     {  
  75.         ppix_im = im.ptr<Vec3b>(r);  
  76.         ppix_om = om.ptr<Vec3b>(r);  
  77.         for (int c = 0; c < im.cols; c++)  
  78.         {  
  79.              ppix_om[c] = ppix_im[c]*scale;  
  80.         }  
  81.     }  
  82.     cout << (abtic() - tRecorder) << " using ptr<>() " << endl;  
  83. #if ISSHOW  
  84.     imshow("Scaled Image: using ptr<>()", om);  
  85.     waitKey();  
  86. #endif  
  87.   
  88.     // 3. using iterator  
  89.     tRecorder = abtic();  
  90.     MatIterator_<Vec3b> it_im, itEnd_im;  
  91.     MatIterator_<Vec3b> it_om;  
  92.     it_im    = im.begin<Vec3b>();  
  93.     itEnd_im = im.end<Vec3b>();  
  94.     it_om    = om.begin<Vec3b>();  
  95.     for (; it_im != itEnd_im; it_im++, it_om++)  
  96.     {  
  97.         *it_om = (*it_im)*scale;  
  98.     }  
  99.     cout << (abtic() - tRecorder) << " using iterator " << endl;  
  100. #if ISSHOW  
  101.     imshow("Scaled Image: using iterator", om);  
  102.     waitKey();  
  103. #endif  
  104.   
  105.     // 4. using Mat_  
  106.     tRecorder = abtic();  
  107.     Mat_<Vec3b> im_, om_;  
  108.     im_ = im;  
  109.     om_ = om;  
  110.     for (int r = 0; r < im.rows; r++)  
  111.     {  
  112.         for (int c = 0; c < im.cols; c++)  
  113.         {  
  114.             om_(r,c) = im_(r,c) * scale;  
  115.         }  
  116.     }  
  117.     cout << (abtic() - tRecorder) << " using Mat_ " << endl;  
  118. #if ISSHOW  
  119.     imshow("Scaled Image: using Mat_", om);  
  120.     waitKey();  
  121. #endif  
  122.   
  123.     // 5. using *  
  124.     tRecorder = abtic();  
  125.     om = im*scale;  
  126.     cout << (abtic() - tRecorder) << " using * " << endl;  
  127. #if ISSHOW  
  128.     imshow("Scaled Image: using *", om);  
  129.     waitKey();  
  130. #endif  
  131.   
  132.     return 0;  
  133. }  
代码段10. 测试代码

    如果你想使用第一部分提到的Makefile,你需要将代码段10保存成test.cpp,或者保存成你希望的某个名字,但是同时应该修改Makfile中的所有“test.cpp”。

    在正确执行之前,将代码段10中的第40行代码改成你的图片名称。


2、实验平台


CPU:Intel(R) Pentium(R) CPU G840 @ 2.80GHz

G++:4.8.2

OpenCV : 2.4.9


3、实验结果


编译选项使用-O3时,其中一次执行结果:

  1. 489570 using at<>()  
  2. 467315 using ptr<>()   
  3. 468603 using iterator   
  4. 469041 using Mat_   
  5. 621367 using *   

编译选项使用-O0 -g时,其中一次执行结果:

  1. 2.48216e+06 using at<>()  
  2. 2.15397e+06 using ptr<>()   
  3. 3.80784e+06 using iterator   
  4. 2.38941e+06 using Mat_   
  5. 621099 using *   


4、实验分析

从上面的结果可以看出,使用×时,在两种模式下,计算速度差不多,这实际是由于我们的程序调用的OpenCV的库函数,而这个库函数调用的是同一个。


如果你的产品要求执行速度,从-O3条件下的输出结果可以看出,ptr这种方式速度稍微快一点。但是他们的差别并不大,所以应该再考虑代码的复杂度。


代码复杂度用代码量(代码行数、列数)、使用变量的个数、使用变量个类型掌握难度(比如指针可能难一点)等因素来度量。

最小的就是使用×了(最后一个方法)。虽然他的复杂度较小,实际只有一行代码,但是对于实际的应用,你要想调用OpenCV已经实现的功能,首先要确定OpenCV里已经实现了这个功能。

其次,我认为复杂度较小的是方法一,因为它实际上可以不借用pix变量,完成前述算法,使用变量数较少,代码量也不多。

Mat_和ptr这两种方式的复杂度差不多,如果使用指针是一种稍微难一点的方式的话,那么Mat_的复杂度可以认为稍微小一点。

一般认为迭代器是C++里面比较高级的特性,也是学习C++最靠后的技术,再加上它使用了指针,如果指针算是比较难掌握的技术的话,使用迭代器这种方式复杂度可以说是最复杂的了。


有些情况下,需要考虑安全性,比如防止越界访问,如果你不想考虑过多边界的问题,使用迭代器也许是一种不错的选择!


五、总结


选择哪种元素访问方式,应该根据自己的实际应用环境,具体分析作出决定。主要考虑三个因素:性能、代码复杂度、安全性,根据自己的程序类型,选择。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值