zoukankan      html  css  js  c++  java
  • OpenCV -- 使用forEach进行并行像素访问

    OpenCV中使用forEach进行并行像素访问

    在本教程中,我们将比较Mat类的forEach方法的性能和访问和转换OpenCV中像素值的其他方式。 我们将展示如何使用at方法甚至高效地使用指针算法,forEach比使用at方法快得多。

    OpenCV中有隐藏的宝石,有时不是很知名。 其中一个隐藏的宝石是Mat类的forEach方法,它利用机器上的所有内核在每个像素上应用任何函数。

    让我们先定义一个函数complexThreshold。 它采用一个RGB像素值并对其应用一个复杂的阈值。

    // Define a pixel 
    typedef Point3_<uint8_t> Pixel;
    
    // A complicated threshold is defined so 
    // a non-trivial amount of computation 
    // is done at each pixel. 
    void complicatedThreshold(Pixel &pixel)
    {
      if (pow(double(pixel.x)/10,2.5) > 100)
      {
        pixel.x = 255;
        pixel.y = 255;
        pixel.z = 255;
      }
      else
      {
        pixel.x = 0;
        pixel.y = 0;
        pixel.z = 0;
      }
    }

    与简单的阈值相比,这个函数在计算上要重得多。 这样,我们不仅仅是测试像素访问时间,而且每个像素操作的计算量都很大时,forEach如何使用所有内核。

    接下来,我们将通过四种不同的方式将这个函数应用到图像中的每个像素,并检查相关的性能。

    方法1:使用at方法的朴素像素访问

    Mat类有一个方便的方法来访问图像中位置(行,列)的像素。 以下代码使用at方法来访问每个像素并将复杂的阈值应用于它。

    // Naive pixel access
    // Loop over all rows
    for (int r = 0; r < image.rows; r++)
    {
      // Loop over all columns
      for ( int c = 0; c < image.cols; c++)
      {
        // Obtain pixel at (r, c)
        Pixel pixel = image.at<Pixel>(r, c);
        // Apply complicatedTreshold
        complicatedThreshold(pixel);
        // Put result back
        image.at<Pixel>(r, c) = pixel;
      }
    
    }

    上面的方法被认为是低效的,因为每次我们调用at方法时,内存中像素的位置正在被计算。 这涉及乘法操作。 不使用像素位于连续的存储器块中的事实。

    方法2:使用指针算法进行像素访问

    在OpenCV中,一行中的所有像素都存储在一个连续的内存块中。 如果使用create创建了Mat对象,则所有像素都存储在一个连续的内存块中。 由于我们正在从磁盘读取图像,imread使用create方法,因此我们可以简单地使用不需要乘法的指针运算来遍历所有像素。

    代码如下所示。

    // Using pointer arithmetic
    
    // Get pointer to first pixel
    Pixel* pixel = image1.ptr<Pixel>(0,0);
    
    // Mat objects created using the create method are stored
    // in one continous memory block.
    const Pixel* endPixel = pixel + image1.cols * image1.rows;
    // Loop over all pixels
    for (; pixel != endPixel; pixel++)
    {
      complicatedThreshold(*pixel);
    }

    方法3:使用forEach

    Mat类的forEach方法接受一个函数操作符。 用法是

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

    了解上述用法的最简单的方法是通过下面的示例。 我们定义了一个用于forEach的函数对象(Operator)。

    // Parallel execution with function object.
    struct Operator
    {
      void operator ()(Pixel &pixel, const int * position) const
      {
        // Perform a simple threshold operation
        complicatedThreshold(pixel);
      }
    };

    调用forEach很简单,只需要一行代码即可完成

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

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

    image3.forEach<Pixel>
    (
      [](Pixel &pixel, const int * position) -> void
      {
        complicatedThreshold(pixel);
      }
    );

    比较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 的方法。 不过,在写这篇博文的时候,我惊讶地发现,即使是大图片,这两种方法之间似乎也没有什么区别。

    完整代码:

    // Include OpenCV header
    #include <opencv2/opencv.hpp>
    
    // Use cv and std namespaces
    using namespace cv;
    using namespace std;
    
    // Define a pixel
    typedef Point3_<uint8_t> Pixel;
    
    // tic is called to start timer
    void tic(double &t)
    {
        t = (double)getTickCount();
    }
    
    // toc is called to end timer
    double toc(double &t)
    {
        return ((double)getTickCount() - t) / getTickFrequency();
    }
    
    void complicatedThreshold(Pixel &pixel)
    {
        if (pow(double(pixel.x) / 10, 2.5) > 100)
        {
            pixel.x = 255;
            pixel.y = 255;
            pixel.z = 255;
        }
        else
        {
            pixel.x = 0;
            pixel.y = 0;
            pixel.z = 0;
        }
    }
    
    
    
    // Parallel execution with function object.
    struct Operator
    {
        void operator ()(Pixel &pixel, const int * position) const
        {
            // Perform a simple threshold operation
            complicatedThreshold(pixel);
        }
    };
    
    
    int main(int argc, char** argv)
    {
        // Read image
        Mat image = imread("butterfly.jpg");
    
        // Scale image 30x
        resize(image, image, Size(), 30, 30);
    
        // Print image size
        cout << "Image size " << image.size() << endl;
    
        // Number of trials
        int numTrials = 5;
    
        // Print number of trials
        cout << "Number of trials : " << numTrials << endl;
    
        // Make two copies
        Mat image1 = image.clone();
        Mat image2 = image.clone();
        Mat image3 = image.clone();
    
        // Start timer
        double t;
        tic(t);
    
        for (int n = 0; n < numTrials; n++)
        {
            // Naive pixel access
            // Loop over all rows
            for (int r = 0; r < image.rows; r++)
            {
                // Loop over all columns
                for (int c = 0; c < image.cols; c++)
                {
                    // Obtain pixel at (r, c)
                    Pixel pixel = image.at<Pixel>(r, c);
                    // Apply complicatedTreshold
                    complicatedThreshold(pixel);
                    // Put result back
                    image.at<Pixel>(r, c) = pixel;
                }
    
            }
        }
    
        cout << "Naive way: " << toc(t) << endl;
    
    
        // Start timer
        tic(t);
    
        // image1 is guaranteed to be continous, but
        // if you are curious uncomment the line below
        // cout << "Image 1 is continous : " << image1.isContinuous() << endl;
    
        for (int n = 0; n < numTrials; n++)
        {
            // Get pointer to first pixel
            Pixel* pixel = image1.ptr<Pixel>(0, 0);
    
            // Mat objects created using the create method are stored
            // in one continous memory block.
            const Pixel* endPixel = pixel + image1.cols * image1.rows;
    
            // Loop over all pixels
            for (; pixel != endPixel; pixel++)
            {
                complicatedThreshold(*pixel);
            }
    
    
        }
        cout << "Pointer Arithmetic " << toc(t) << endl;
        tic(t);
    
        for (int n = 0; n < numTrials; n++)
        {
            image2.forEach<Pixel>(Operator());
        }
        cout << "forEach : " << toc(t) << endl;
    
    #if __cplusplus >= 201103L || (__cplusplus < 200000 && __cplusplus > 199711L)
        tic(t);
    
        for (int n = 0; n < numTrials; n++)
        {
            // Parallel execution using C++11 lambda.
            image3.forEach<Pixel>
                (
                    [](Pixel &pixel, const int * position) -> void
            {
                complicatedThreshold(pixel);
            }
            );
        }
        cout << "forEach C++11 : " << toc(t) << endl;
    
    #endif
    
        return EXIT_SUCCESS;
    }

    https://blog.csdn.net/wc781708249/article/details/78500839

  • 相关阅读:
    文字列をバイトで切る
    把SmartForm转换成PDF
    相对布局和网格布局
    帧布局和表格布局
    计算器布局
    课堂总结和练习
    Android UI组件
    2层导航
    导航
    课堂总结
  • 原文地址:https://www.cnblogs.com/zzzsj/p/14609093.html
Copyright © 2011-2022 走看看