zoukankan      html  css  js  c++  java
  • OpenCV:Mat元素访问方法、演出、代码的复杂性和安全性分析

    欢迎转载。尊重原创,因此,请注明出处:

    http://blog.csdn.net/bendanban/article/details/30527785


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


    一、预备设置

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

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

    本文使用彩色图像做实验,所以矩阵是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文件的内容


    #if defined(_WIN32) && defined(_MSC_VER)
    #include <windows.h>
    double abtic() {
      __int64 freq;
      __int64 clock;
      QueryPerformanceFrequency( (LARGE_INTEGER *)&freq );
      QueryPerformanceCounter( (LARGE_INTEGER *)&clock );
      return (double)clock/freq*1000*1000;
    }
    #else
    #include <time.h>
    #include <sys/time.h>
    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. 计时函数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的源文件)。

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


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

      Vec3b pix;
      for (int r = 0; r < im.rows; r++)
      {
        for (int c = 0; c < im.cols; c++)
        {   
          pix = im.at<Vec3b>(r,c);
          pix = pix*scale;
          om.at<Vec3b>(r,c) = pix;
        }   
      }
    
    代码段4. 使用at<>()函数訪问矩阵元素

        注意:使用at函数时,应该知道矩阵元素的类型和通道数,依据矩阵元素类型和通道数来确定at函数传递的类型,代码段4中使用的是Vec3b这个元素类型,他是一个包括3个unsigned char类型向量。

    之所以採用这个类型来接受at的返回值,是由于,我们的矩阵im是3通道,类型为unsigned char类型的。


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

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

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

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

      Vec3b *ppix_im(NULL);
      Vec3b *ppix_om(NULL);
      for (int r = 0; r < im.rows; r++)
      {
        ppix_im = im.ptr<Vec3b>(r);
        ppix_om = om.ptr<Vec3b>(r);
        for (int c = 0; c < im.cols; c++)
        {
           ppix_om[c] = ppix_im[c]*scale;
        }
      }
    
    代码段 6. 使用ptr訪问矩阵元素


    方法3、使用迭代器

        这里使用的迭代器是OpenCV自定义的。

    使用迭代器完毕第二部分所述算法的代码如代码段7所看到的。

      MatIterator_<Vec3b> it_im, itEnd_im;
      MatIterator_<Vec3b> it_om;
      it_im    = im.begin<Vec3b>();
      itEnd_im = im.end<Vec3b>();
      it_om    = om.begin<Vec3b>();
      for (; it_im != itEnd_im; it_im++, it_om++)
      {
        *it_om = (*it_im)*scale;
      }
    
    代码段 7. 使用迭代器訪问矩阵元素


    方法4、使用Mat_简化索引

        Mat_这个类的元素訪问比較easy一点,把原Mat类的对象能够直接赋值给Mat_对象,当然赋值操作并不会开辟新的数据空间,这点大家放心。也就是说使用Mat_时。不会在内存拷贝上花时间。使用这样的方法完毕第二部分所述算法的代码如代码段8所看到的。

      Mat_<Vec3b> 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_訪问矩阵数据元素


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

        我们的算法实际上OpenCV中已经有实现。

    就是×运算符重载,代码如代码段9所看到的。

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


    四、实验測试

    1、測试代码

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

    /*************************************************************************
      > File Name: test.cpp
      > Author: aban
      > Mail: sawpara@126.com 
      > Created Time: 2014年06月13日 星期五 18时47分19秒
     ************************************************************************/
    
    
    #include <iostream>
    #include <opencv2/opencv.hpp>
    using namespace cv;
    using namespace std;
    
    #if defined(_WIN32) && defined(_MSC_VER)
    #include <windows.h>
    double abtic() {
    	__int64 freq;
    	__int64 clock;
    	QueryPerformanceFrequency( (LARGE_INTEGER *)&freq );
    	QueryPerformanceCounter( (LARGE_INTEGER *)&clock );
    	return (double)clock/freq*1000*1000;
    }
    #else
    #include <time.h>
    #include <sys/time.h>
    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<Vec3b>(r,c);
    			pix = pix*scale;
    			om.at<Vec3b>(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<Vec3b>(r);
    		ppix_om = om.ptr<Vec3b>(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_<Vec3b> it_im, itEnd_im;
    	MatIterator_<Vec3b> it_om;
    	it_im    = im.begin<Vec3b>();
    	itEnd_im = im.end<Vec3b>();
    	it_om    = om.begin<Vec3b>();
    	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_<Vec3b> 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。或者保存成你希望的某个名字,可是同一时候应该改动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时。当中一次运行结果:

    489570 using at<>()
    467315 using ptr<>() 
    468603 using iterator 
    469041 using Mat_ 
    621367 using * 

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

    2.48216e+06 using at<>()
    2.15397e+06 using ptr<>() 
    3.80784e+06 using iterator 
    2.38941e+06 using Mat_ 
    621099 using * 


    4、实验分析

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



    假设你的产品要求运行速度,从-O3条件下的输出结果能够看出,ptr这样的方式速度略微快一点。可是他们的区别并不大,所以应该再考虑代码的复杂度。


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

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

    其次。我觉得复杂度较小的是方法一,由于它实际上能够不借用pix变量。完毕前述算法,使用变量数较少。代码量也不多。

    Mat_和ptr这两种方式的复杂度差点儿相同,假设使用指针是一种略微难一点的方式的话。那么Mat_的复杂度能够觉得略微小一点。

    一般觉得迭代器是C++里面比較高级的特性。也是学习C++最靠后的技术,再加上它使用了指针,假设指针算是比較难掌握的技术的话,使用迭代器这样的方式复杂度能够说是最复杂的了。


    有些情况下,须要考虑安全性,比方防止越界訪问。假设你不想考虑过多边界的问题,使用迭代器或许是一种不错的选择!



    五、总结


    选择哪种元素訪问方式。应该依据自己的实际应用环境,详细分析作出决定。

    主要考虑三个因素:性能、代码复杂度、安全性,依据自己的程序类型,选择。





    版权声明:本文博主原创文章,博客,未经同意不得转载。

  • 相关阅读:
    【转载】Linux下各文件夹的含义和用途
    【转载】Linux 通过mount -o loop 配置本地.iso镜像为yum源(yum仓库)
    Fedora 和 RedHat 以及 SUSE 中 YUM 工具的使用
    【转】下载对应内核版本的asmlib
    【转】VMWare vCenter 6.0安装配置
    【转】在VMware中为Linux系统安装VM-Tools的详解教程
    【转】虚拟化(五):vsphere高可用群集与容错
    html拼接时onclick事件传递json对象
    bootstrap table 解析写死的json.并且把进度条放进列中。
    开发规范实体和值对象
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4804624.html
Copyright © 2011-2022 走看看