[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 章節.


發佈了57 篇原創文章 · 獲贊 95 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章