[OpenCV2]编写有效率的图像循环

这本章的前几节,我们提出了遍历一幅图像像素的几个不同方法.这这节,我们会比较这些不同方法的处理效率.

当你写一个图像处理函数,处理效率是经常关系的事.当设计你的功能函数的时候,需要频繁的检查你的代码的处理效率,主要是为了发现使你的程序跑的慢的瓶颈.然而,为了是程序看起来简单易懂不做优化是非常需要注意的,除非不是必须的.简单的代码确实容易调试和维护.但是影响一个程序效率的关键代码需要大量优化.

How to do it...

为了测试一个功能函数或者一部分代码执行的时间,有一个非常方法的OpenCV函数cv::getTickCount().这个函数给出了电脑从开始到结束的时钟周期数.因为我们一般希望给出的结果是以毫秒为单位的,我们需要另一个方法cv::getTickFrequency().这个函数给出我们每秒的周期数.测试一段代码的所用时间的一般模式如下:

double duration;
duration = static_cast<double>(cv::getTickCount());
colorReduce(image); // the function to be tested
duration = static_cast<double>(cv::getTickCount())-duration;
duration /= cv::getTickFrequency(); // the elapsed time in ms
这个duration的结果通常是几次运行的平均值.

在测试colorReduce功能中,我们首先使用at方法遍历每个像素.主循环实现如下:

for (int j=0; j<nl; j++) {
          for (int i=0; i<nc; i++) {
           // process each pixel ---------------------                 
           image.at<cv::Vec3b>(j,i)[0]=
               image.at<cv::Vec3b>(j,i)[0]/div*div + div/2;
           image.at<cv::Vec3b>(j,i)[1]=    
              image.at<cv::Vec3b>(j,i)[1]/div*div + div/2;
           image.at<cv::Vec3b>(j,i)[2]=    
              image.at<cv::Vec3b>(j,i)[2]/div*div + div/2;
           // end of pixel processing ----------------           } // end of line                   
      }

How it works ...

我们使用实现clolorReduce功能不同的方法分别计算执行时间.如果使用不同电脑结果会不同,这里我们使用2.2GHZ的奔腾双核处理器.这样可以更好的说明他们的相对差异.我们的结果取处理一幅4288×2848图像的平均值.这个结果展示如下:


首先,我们展示了三种颜色缩减处理方法和在使用指针遍历图像There's more 提出的一种方法(1-4行).正如我们期望的,使用位操作运算是最快的,仅仅只用了35ms.使用整数的除法花费了37ms,取模计算花费了52ms.最快和最慢相差近50%.这说明,在网络的情况找出最有效率的处理图像循环的方法是很有必要的.注意在第5行中,需要重新分配一个输出图像时,而不是直接处理源图像,处理事件变为了44ms.这个额外的时间花费是由于内存的更多开销.

在一个循环中,你需要避免重复计算可预先计算的值.这明显花费时间.例如,如果在内循环如下实现:

 int nc= image.cols * image.channels(); 
 …
      for (int i=0; i<nc; i++) {
和下面这个:

     for (int i=0; i<image.cols * image.channels(); i++) {
在一个循环中,你需要一次又一次使用获取元素总数的方法.你会发现你总共用了65ms,比原始的35ms花费了80%的额外时间.(第六行)

在第7行使用容器颜色缩减的方法,展示了用容器遍历数组结果更慢需要67ms.这个容器的主要目的是使遍历图像简单,不易出错.不是优化处理进程.

第8行使用之前提到的at方法. 允许需要花费80ms.这个方法应该被用来访问单个像素,而不是遍历整个像素.

一个较短的循环但是有一些语句通常是比一个长循环但是语句很少更有效率,即使处理的总元素数是相同的.同样的,如果你对一个像素有N种不同的操作,把所有操作写在一个循环中,比写在N的循环,一个循环处理一个操作要好的多.你可能乐于使用循环,但是长循环应该做较少的处理工作.如同我们的例子,我们能在内循环处理所有的三个通道,声明一个变量保存一行的像素数来代替在循环中使用方法来获取总数.这个颜色缩减功能如下写(这个是最快的方法):

void colorReduce(cv::Mat &image, int div=64) {
     int nl= image.rows; // number of lines
     int nc= image.cols ; // number of columns
     // is it a continous image?
     if (image.isContinuous())  {
        // then no padded pixels
        nc= nc*nl; 
        nl= 1;  // it is now a 1D array
      }
     int n= static_cast<int>(
              log(static_cast<double>(div))/log(2.0));
     // mask used to round the pixel value
     uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
     // for all pixels         
     for (int j=0; j<nl; j++) {
          // pointer to first column of line j
          uchar* data= image.ptr<uchar>(j);
          for (int i=0; i<nc; i++) {
 
            // process each pixel ---------------------
            *data++= *data&mask + div/2;
            *data++= *data&mask + div/2;
            *data++= *data&mask + div/2;
            // end of pixel processing ----------------
          } // end of line                   
     }
}
使用这个修改后的方法,现在执行只要29ms(第九行).我们也添加这个连续性测试,如果图像是连续的图像,使用一个循环代替双重循环.对于一个很大的图像,在我们的的测试中,这种优化是不重要的,但是使用这个方法是个很好的习惯,它能显著的增长速度.

There's more ...

由于多核处理器的出现,多线程方法是另一个方法去增加算法的运行效率.OpenMP和英特尔线程块(TBB)是两个流行的APIs用于创建和管理你的线程.

See also

使用OpenCV2执行图像操作实现颜色缩减的另一个方法请看  Performing simple image arithmetic 章节.


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值