1. 程式人生 > 實用技巧 >OpenCV 使用forEach進行並行畫素訪問

OpenCV 使用forEach進行並行畫素訪問

OpenCV中使用forEach進行並行畫素訪問

在本教程中,我們將比較Mat類的forEach方法的效能和訪問和轉換OpenCV中畫素值的其他方式。 我們將展示如何使用at方法甚至高效地使用指標演算法,forEach比使用at方法快得多。

OpenCV中有隱藏的寶石,有時不是很知名。 其中一個隱藏的寶石是Mat類的forEach方法,它利用機器上的所有核心在每個畫素上應用任何函式。

讓我們先定義一個函式complexThreshold。 它採用一個RGB畫素值並對其應用一個複雜的閾值。

 1 // Define a pixel 
 2 typedef Point3_<uint8_t> Pixel;
3 4 // A complicated threshold is defined so 5 // a non-trivial amount of computation 6 // is done at each pixel. 7 void complicatedThreshold(Pixel &pixel) 8 { 9 if (pow(double(pixel.x)/10,2.5) > 100) 10 { 11 pixel.x = 255; 12 pixel.y = 255; 13 pixel.z = 255; 14 } 15 else 16
{ 17 pixel.x = 0; 18 pixel.y = 0; 19 pixel.z = 0; 20 } 21 }

與簡單的閾值相比,這個函式在計算上要重得多。 這樣,我們不僅僅是測試畫素訪問時間,而且每個畫素操作的計算量都很大時,forEach如何使用所有核心。

接下來,我們將通過四種不同的方式將這個函式應用到影象中的每個畫素,並檢查相關的效能。

方法1:使用at方法的樸素畫素訪問

Mat類有一個方便的方法來訪問影象中位置(行,列)的畫素。 以下程式碼使用at方法來訪問每個畫素並將複雜的閾值應用於它。

 1 // Naive pixel access
 2 // Loop over all rows
3 for (int r = 0; r < image.rows; r++) 4 { 5 // Loop over all columns 6 for ( int c = 0; c < image.cols; c++) 7 { 8 // Obtain pixel at (r, c) 9 Pixel pixel = image.at<Pixel>(r, c); 10 // Apply complicatedTreshold 11 complicatedThreshold(pixel); 12 // Put result back 13 image.at<Pixel>(r, c) = pixel; 14 } 15 }

上面的方法被認為是低效的,因為每次我們呼叫at方法時,記憶體中畫素的位置正在被計算。 這涉及乘法操作。 不使用畫素位於連續的儲存器塊中的事實。

方法2:使用指標演算法進行畫素訪問

在OpenCV中,一行中的所有畫素都儲存在一個連續的記憶體塊中。 如果使用create建立了Mat物件,則所有畫素都儲存在一個連續的記憶體塊中。 由於我們正在從磁碟讀取影象,imread使用create方法,因此我們可以簡單地使用不需要乘法的指標運算來遍歷所有畫素。

程式碼如下所示。

 1 // Using pointer arithmetic
 2 
 3 // Get pointer to first pixel
 4 Pixel* pixel = image1.ptr<Pixel>(0,0);
 5 
 6 // Mat objects created using the create method are stored
 7 // in one continous memory block.
 8 const Pixel* endPixel = pixel + image1.cols * image1.rows;
 9 // Loop over all pixels
10 for (; pixel != endPixel; pixel++)
11 {
12   complicatedThreshold(*pixel);
13 }

方法3:使用forEach

Mat類的forEach方法接受一個函式操作符。 用法是

void cv::Mat::forEach   (const Functor &operation)  

瞭解上述用法的最簡單的方法是通過下面的示例。 我們定義了一個用於forEach的函式物件(Operator)。

1 // Parallel execution with function object.
2 struct Operator
3 {
4   void operator ()(Pixel &pixel, const int * position) const
5   {
6     // Perform a simple threshold operation
7     complicatedThreshold(pixel);
8   }
9 };

呼叫forEach很簡單,只需要一行程式碼即可完成

1 // Call forEach
2 image2.forEach<Pixel>(Operator());

方法4:在C ++ 11 Lambda中使用forEach

1 image3.forEach<Pixel>
2 (
3   [](Pixel &pixel, const int * position) -> void
4   {
5     complicatedThreshold(pixel);
6   }
7 );

比較forEach的效能

複雜閾值函式連續五次應用於大小為9000 x 6750的大影象的所有畫素。 實驗中使用的2.5 GHz Intel Core i7處理器有四個核心。 以下時間已經獲得。 請注意,使用forEach比使用Naive Pixel Access或Pointer Arithmetic方法快五倍。

Method TypeTime ( milliseconds )
Naive Pixel Access 6656
Pointer Arithmetic 6575
forEach 1221
forEach (C++11 Lambda) 1272

我已經在OpenCV中編寫了十多年的程式碼,每當我必須編寫訪問畫素的優化程式碼時,我都會使用指標演算法而不是naive 的方法。 不過,在寫這篇博文的時候,我驚訝地發現,即使是大圖片,這兩種方法之間似乎也沒有什麼區別。

完整程式碼:

  1 // Include OpenCV header
  2 #include <opencv2/opencv.hpp>
  3 
  4 // Use cv and std namespaces
  5 using namespace cv;
  6 using namespace std;
  7 
  8 // Define a pixel
  9 typedef Point3_<uint8_t> Pixel;
 10 
 11 // tic is called to start timer
 12 void tic(double &t)
 13 {
 14     t = (double)getTickCount();
 15 }
 16 
 17 // toc is called to end timer
 18 double toc(double &t)
 19 {
 20     return ((double)getTickCount() - t) / getTickFrequency();
 21 }
 22 
 23 void complicatedThreshold(Pixel &pixel)
 24 {
 25     if (pow(double(pixel.x) / 10, 2.5) > 100)
 26     {
 27         pixel.x = 255;
 28         pixel.y = 255;
 29         pixel.z = 255;
 30     }
 31     else
 32     {
 33         pixel.x = 0;
 34         pixel.y = 0;
 35         pixel.z = 0;
 36     }
 37 }
 38 
 39 
 40 
 41 // Parallel execution with function object.
 42 struct Operator
 43 {
 44     void operator ()(Pixel &pixel, const int * position) const
 45     {
 46         // Perform a simple threshold operation
 47         complicatedThreshold(pixel);
 48     }
 49 };
 50 
 51 
 52 int main(int argc, char** argv)
 53 {
 54     // Read image
 55     Mat image = imread("butterfly.jpg");
 56 
 57     // Scale image 30x
 58     resize(image, image, Size(), 30, 30);
 59 
 60     // Print image size
 61     cout << "Image size " << image.size() << endl;
 62 
 63     // Number of trials
 64     int numTrials = 5;
 65 
 66     // Print number of trials
 67     cout << "Number of trials : " << numTrials << endl;
 68 
 69     // Make two copies
 70     Mat image1 = image.clone();
 71     Mat image2 = image.clone();
 72     Mat image3 = image.clone();
 73 
 74     // Start timer
 75     double t;
 76     tic(t);
 77 
 78     for (int n = 0; n < numTrials; n++)
 79     {
 80         // Naive pixel access
 81         // Loop over all rows
 82         for (int r = 0; r < image.rows; r++)
 83         {
 84             // Loop over all columns
 85             for (int c = 0; c < image.cols; c++)
 86             {
 87                 // Obtain pixel at (r, c)
 88                 Pixel pixel = image.at<Pixel>(r, c);
 89                 // Apply complicatedTreshold
 90                 complicatedThreshold(pixel);
 91                 // Put result back
 92                 image.at<Pixel>(r, c) = pixel;
 93             }
 94 
 95         }
 96     }
 97 
 98     cout << "Naive way: " << toc(t) << endl;
 99 
100 
101     // Start timer
102     tic(t);
103 
104     // image1 is guaranteed to be continous, but
105     // if you are curious uncomment the line below
106     // cout << "Image 1 is continous : " << image1.isContinuous() << endl;
107 
108     for (int n = 0; n < numTrials; n++)
109     {
110         // Get pointer to first pixel
111         Pixel* pixel = image1.ptr<Pixel>(0, 0);
112 
113         // Mat objects created using the create method are stored
114         // in one continous memory block.
115         const Pixel* endPixel = pixel + image1.cols * image1.rows;
116 
117         // Loop over all pixels
118         for (; pixel != endPixel; pixel++)
119         {
120             complicatedThreshold(*pixel);
121         }
122 
123 
124     }
125     cout << "Pointer Arithmetic " << toc(t) << endl;
126     tic(t);
127 
128     for (int n = 0; n < numTrials; n++)
129     {
130         image2.forEach<Pixel>(Operator());
131     }
132     cout << "forEach : " << toc(t) << endl;
133 
134 #if __cplusplus >= 201103L || (__cplusplus < 200000 && __cplusplus > 199711L)
135     tic(t);
136 
137     for (int n = 0; n < numTrials; n++)
138     {
139         // Parallel execution using C++11 lambda.
140         image3.forEach<Pixel>
141             (
142                 [](Pixel &pixel, const int * position) -> void
143         {
144             complicatedThreshold(pixel);
145         }
146         );
147     }
148     cout << "forEach C++11 : " << toc(t) << endl;
149 
150 #endif
151 
152     return EXIT_SUCCESS;
153 }