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 Type Time ( 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 }

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM