• 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 }
  • 相关阅读:
    ARM处理器
    进程和线程通俗理解
    const与指针
    字符提取命令
    ThinkPHP之视图模版的使用
    ThinkPHP之MVC与URL访问
    ThinkPHP之项目搭建
    android之文件存储和读取
    cryptdb中wrapper.lua的分析
    cryptDB安装分析
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/13815282.html
Copyright © 2020-2023  润新知