精华内容
下载资源
问答
  • 2019 08 licese
  • 前言:对于一些基本的循环运算,如果我们直接使用循环,即便是使用指针,运算效率也不高,如果我们使用并行计算,会大大提升运算效率,OpenCV里面的很多运算都是使用了并行加速的,本文主要介绍Parallel_for_与...

    前言:对于一些基本的循环运算,如果我们直接使用循环,即便是使用指针,运算效率也不高,如果我们使用并行计算,会大大提升运算效率,OpenCV里面的很多运算都是使用了并行加速的,本文主要介绍Parallel_for_与ParallelLoopBody教程的使用方法。我看了网上的很多教程,其实都是同一篇文章转来转去,而且版本比较低,决定自己写一篇,本文使用OpenCV4.1.1 ,需要使用 <opencv2/core/utility.hpp>   这个头文件。

    一、先从构造函数的运算符重载说起

    我们在调用函数的时候,实际上是使用了括号()运算符,构造函数也是普通的函数,所以也用到了括号运算符,那如果想要重载这个括号()运算符怎么做呢?先来看一个简单的小例子:

    namespace myanimal
    	{	
    		class Animal
    		{
    		public:
    			Animal(float weight_,int age_) 
    			{
    				weight = weight_;
    				age = age_;
    			}
    			void operator()(float height) const //重载操作符()
    			{
    				height = 100.0;
    				std::cout << "the age is : " << age << std::endl;
    				std::cout << "the weight is : " << weight << std::endl;
    				std::cout << "the height is : " << height << std::endl;
    				
    			}
    
    		private:
    			float weight;
    			int age;
    		};
    
    	}

    现在调用:

    int main(int argc, char* argv[])
    {
        myanimal::Animal animal(50.0,25);  //创建对象
    	animal(100.0);                     //通过对象调用重载的括号 () 运算符
    	
    	getchar();
    	return 0;
    }
    /*
    the age is : 25
    the weight is : 50
    the height is : 100
    */

    总结:

    (1)重载的括号运算符就像一个对象的方法一杨,依旧是通过对象去调用,调用的方式为 “对象名(参数列表)” 这样的形式;

    (2)重载括号运算符的一般操作为  “返回类型  operator(参数列表)”  ,后面的const可以不要,参数列表可以使任意的,

    没有参数,则调用方式为:obj()

    一个参数,则调用方式为:obj(参数1)

    多个参数,则调用方式为:obj(参数1,参数2,......)

    二、Parallel_for_结合ParallelLoopBody使用的一般步骤

    使用步骤一般遵循三步走的原则

    (1)第一步:自定义一个类或者是一个结构体,使这个结构体或者是类继承自ParallelLoopBody类,如下:

    class MyParallelClass : public ParallelLoopBody
    {}
    struct MyParallelStruct : public ParallelLoopBody
    {}

    (2)第二步:在自定义的类或者是结构体中,重写括号运算符(),注意:虽然前面讲括号运算符重载可以接受任意数量的参数,但是这里只能接受一个Range类型的参数(这是与一般的重载不一样的地方),因为后面的parallel_for_需要使用,如下:

    void operator()(const Range& range)
    {
       //在这里面进行“循环操作”
    }

    (3)第三步:使用parallel_for_进行并行处理

    首先看一下parallel_for_的函数原型

    #include <opencv2/core/utility.hpp>   //本文使用OpenCV4.1.1 ,需要使用这个头文件
    CV_EXPORTS void parallel_for_(const Range& range, const ParallelLoopBody& body, double nstripes=-1.);
    

    参数解释如下:

    • const Range& range,  即重载的括号运算符里面的参数,是一个Range类型
    • const ParallelLoopBody& body,  即自己实现的从ParallelLoopBody类继承的类或者是结构体对象
    • double nstripes=-1

    怎么使用呢?如下:

    parallel_for_(Range(start, end), MyParallelClass(构造函数列表));
    //Range(start, end) 就是一个Range对象
    //MyParallelClass(构造函数列表) 就是一个继承自ParallelLoopBody的类的对象

    疑问???

    前面在要使用重载的括号运算符里面的内容,需要显式的调用 obj(参数列表)  才行,在这里应该这么写才行

    MyParallelClass obj = MyParallelClass(构造函数列表));  //构造对象
    obj(Range(start, end));   //调用

    这样写当然不会有什么问题,但是这样的执行方式,在括号重载运算符里面的内容是按照顺序执行的,并没有并发处理,如果是对于耗时任务,没有节约时间,那直接通过

    parallel_for_(Range(start, end), MyParallelClass(构造函数列表));

    处理,没有显式的调用重载的括号运算符,但实际上是隐式调用了的,而且以并发方式进行处理重载运算里面的内容。

     

    三、Parallel_for_结合ParallelLoopBody的加速效果实验

    3.1 自定义类实现

    任务描述:我要定义两个Mat矩阵的逐元素乘积,如下所示

    (1)自定义一个类继承自ParallelLoopBody,并且重载括号运算

    #include <opencv2/core/utility.hpp>  //引入此头文件
    #include <opencv2/opencv.hpp>
    
    namespace cv
    {
    	namespace mygemm
    	{
    		class ParallelAdd : public ParallelLoopBody//参考官方给出的answer,构造一个并行的循环体类
    		{
    		public:
    			ParallelAdd(Mat& _src1,Mat& _src2,Mat _result)    //构造函数
    			{
    				src1 = _src1;
    				src2 = _src2;
    				result = _result;
    				CV_Assert((src1.rows == src2.rows) && (src1.rows == src2.rows));
    				rows = src1.rows;
    				cols = src1.cols;
    			} 
    			
    			void operator()(const Range& range) const //重载操作符()
    			{
    				int step = (int)(result.step / result.elemSize1());//获取每一行的元素总个数(相当于cols*channels,等同于step1)
    				
    				for (int col = range.start; col < range.end; ++col)
    				{
    					float * pData = (float*)result.col(col).data;
    					float * p1 = (float*)src1.col(col).data;
    					float * p2 = (float*)src2.col(col).data;
    					for (int row = 0; row < result.rows; ++row)
    						pData[row*step] = p1[row*step] * p2[row*step];
    				}
    			}
    
    		private:
    			Mat src1;
    			Mat src2;
    			Mat result;
    			int rows;
    			int cols;
    		};
        }
    }

    可见重载的运算符里面是一个耗时操作,现在定义两种方式来实现两个Mat的逐元素乘积,一种是普通的逐元素处理,另一种是使用parallel进行并发处理,分别通过两个函数完成,如下:

    //直接通过obj()形式调用,不采用并发处理
    void testParallelClassWithFor(Mat _src1,Mat _src2,Mat result)
    {
    	result = Mat(_src1.rows, _src1.cols, _src1.type());
    	int step = (int)(result.step / result.elemSize1());
    	int totalCols = _src1.cols;
    	typedef cv::mygemm::ParallelAdd ParallelAdd;
    	ParallelAdd add = ParallelAdd(_src1, _src2, result);
    	add(Range(0, totalCols));  //直接调用,没有并发
    	}
    
    	
    void testParallelClassTestWithParallel_for_(Mat _src1,Mat _src2,Mat result)
    {
    	result = Mat(_src1.rows, _src1.cols, _src1.type());
    	int step = (int)(result.step / result.elemSize1());
    	int totalCols = _src1.cols;
    	typedef cv::mygemm::ParallelAdd ParallelAdd;
    	parallel_for_(Range(0, totalCols), ParallelAdd(_src1,_src2,result));  //隐式调用,并发
    }

    现在开始测试耗时对比

    #include <opencv2/opencv.hpp>
    #include <time.h>
    
    #include "my_gemm.hpp"  //自己写的头文件,即前面所定义的类和函数
    
    using namespace cv;
    using namespace std;
    
    int main(int argc, char* argv[])
    {
    	Mat testInput1 = Mat::ones(6400, 5400, CV_32F);
    	Mat testInput2 = Mat::ones(6400, 5400, CV_32F);
    
    	Mat result1, result2, result3;
    	clock_t start, stop;
    
        //****************测试耗时对比****************************
    
    	start = clock();
    	mygemm::testParallelClassWithFor(testInput1, testInput2, result1);
    	stop = clock();
    	cout << "Running time using \'general for \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
    
    	start = clock();
    	megemm::testParallelClassWithParallel_for_(testInput1, testInput2, result2);
    	stop = clock();
    	cout << "Running time using \'parallel for \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
    
    	start = clock();
    	result3 = testInput1.mul(testInput2);
    	stop = clock();
    	cout << "Running time using \'mul function \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
    
     
    	getchar();
    	return 0;
    }
    /*
    Running time using 'general for ':645ms
    Running time using 'parallel for ':449ms
    Running time using 'mul function ':70ms
    */

    总结:我们可以看见,使用parallel_for_并发的方式的的确确比直接调用快一些,快了将近200ms,但是依旧没有使用OpenCV自带的标准函数 mul 函数速度快,因为,OpenCV实现的函数库不仅仅经过了并行处理,还是用了更强大的底层优化,所以,只要是OpenCV自己带的方法,一般都是优先使用,除非自己写的比OpenCV的还牛逼一些

     

    3.2 自定义结构体实现

    任务描述:现在定义一个并行运算的结构体,实现Mat逐元素的三次方运算

    (1)自定义一个结构体继承自ParallelLoopBody,并且重载括号运算,如下:

    namespace cv
    {
    	namespace mygemm
    	{
    		struct ParallelPow:ParallelLoopBody//构造一个供parallel_for使用的循环结构体
    		{
    			Mat* src;             //结构体成员,一个Mat类型的指针
    			ParallelPow(Mat& _src)//struct 结构体构造函数
    			{
    				src = &_src;
    			}
    			void operator()(const Range& range) const
    			{
    				Mat& result = *src;
    				int step = (int)(result.step / result.elemSize1());
    				for (int col = range.start; col < range.end; ++col)
    				{
    					float* pData = (float*)result.col(col).data;
    					for (int row = 0; row < result.rows; ++row)
    						pData[row*step] = std::pow(pData[row*step], 3); //逐元素求立方
    				}
    			}
    		};	
    	}
    }

    下面定义两个函数,一个是直接通过for循环逐元素进行立方运算,一个是通过parallel_for_并发运算的,通过两个函数实现,如下所示:

    void testParallelStructWithFor(Mat _src)
    {
    	int totalCols = _src.cols;
    	typedef cv::mygemm::ParallelPow ParallelPow;
    	ParallelPow obj = ParallelPow(_src);
    	obj(Range(0, totalCols));
    }
    
    void testParallelStructWithParallel_for(Mat _src)
    {
    	int totalCols = _src.cols;
    	typedef cv::mygemm::ParallelPow ParallelPow;
    	parallel_for_(Range(0, totalCols), ParallelPow(_src));
    }

    下面开始测试性能消耗对比

    #include <opencv2/opencv.hpp>
    #include <time.h>
    
    #include "my_gemm.hpp"
    
    using namespace cv;
    using namespace std;
    
    int main(int argc, char* argv[])
    {
    	Mat testInput1 = Mat::ones(6400, 5400, CV_32F);
    	Mat testInput2 = Mat::ones(6400, 5400, CV_32F);
    
    	Mat result1, result2, result3;
    	clock_t start, stop;
    
        
    	start = clock();
    	mygemm::testParallelStructWithFor(testInput1);
    	stop = clock();
    	cout << "Running time using \'general for \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
    
    	start = clock();
    	megemm::testParallelStructWithParallel_for(testInput1);
    	stop = clock();
    	cout << "Running time using \'parallel for \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
    
    	start = clock();
    	testInput1.mul(testInput1).mul(testInput1);
    	stop = clock();
    	cout << "Running time using \'mul function \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
    	
    
    	getchar();
    	return 0;
    }
    /*
    Running time using 'general for ':881ms
    Running time using 'parallel for ':195ms
    Running time using 'mul function ':76ms
    */

    我们发现,并行运算效率有着显著提升,但是相较于OpenCV的标准实现,依旧偏慢,

    总结:在能够使用OpenCV标准函数实现的时候,尽量不要再自己定义运算,标准的OpenCV函数式经过优化了的,运算效率很高,

     

     

     

     

     

    展开全文
  • 项目需要,正好遇到了parallel_for_()函数。发现其强大之处,特此记录一下。对原blog做一定的细节补充和详细介绍。 转载自:http://blog.csdn.net/chouclee/article/details/8682561 “OpenCV 从2.4.3开始加入了...

    项目需要,正好遇到了parallel_for_()函数。发现其强大之处,特此记录一下。对原blog做一定的细节补充和详细介绍。


    转载自:http://blog.csdn.net/chouclee/article/details/8682561


    OpenCV 从2.4.3开始加入了并行计算的函数parallel_for和parallel_for_(更准确地讲,parallel_for以前就存在于tbb模块中,但是OpenCV官网将其列在2.4.3.的New Features中,应该是重新改写过的)。

    2.4.3中自带的calcOpticalFlowPyrLK函数也用parallel_for重写过了,之前我一直认为parallel_for就是用来并行计算的,之前也自己写了一些用parallel_for实现的算法。直到今天在opencv官网中看到别人的提问,才发现parallel_for实际上是serial loop,而parallel_for_才是parallel loop(OpenCV官网answer)。

    为了比较for循环,parallel_for和parallel_for_ 三者的差异,下面做了一个简单的测试,对一个Mat中所有的元素(按列为单位)做立方操作。

    test.hpp

    /**@ Test parallel_for and parallel_for_ 
    /**@ Author: chouclee 
    /**@ 03/17/2013*/  
    #include <opencv2/core/internal.hpp>  
    namespace cv  
    {  
    namespace test  
    {  
        class parallelTestBody : public ParallelLoopBody//参考官方给出的answer,构造一个并行的循环体类  
        {  
        public:  
            parallelTestBody(Mat& _src)//class constructor  
            {  
                src = &_src;  
            }  
            void operator()(const Range& range) const// * 重载操作符(这里的操作,就是在并行计算中要执行的操作)  
            {										 // * 在执行中,这个operator会被调用
                Mat& srcMat = *src;  
                int stepSrc = (int)(srcMat.step/srcMat.elemSize1());//获取每一行的元素总个数(相当于cols*channels,等同于step1)  
                for (int colIdx = range.start; colIdx < range.end; ++colIdx)  
                {  
                    float* pData = (float*)srcMat.col(colIdx).data;  
                    for (int i = 0; i < srcMat.rows; ++i)  
                        pData[i*stepSrc] = std::pow(pData[i*stepSrc],3);  // * 计算立方
                }     
            }  
      
        private:  
            Mat* src;  
        };  
      
        struct parallelTestInvoker//构造一个供parallel_for使用的循环结构体  
        {  
            parallelTestInvoker(Mat& _src)//struct constructor  
            {  
                src = &_src;  
            }  
            void operator()(const BlockedRange& range) const//使用BlockedRange需要包含opencv2/core/internal.hpp  
            {  
                Mat& srcMat = *src;  
                int stepSrc = (int)(srcMat.step/srcMat.elemSize1());  
                for (int colIdx = range.begin(); colIdx < range.end(); ++colIdx)  
                {  
                    float* pData = (float*)srcMat.col(colIdx).data;  
                    for (int i = 0; i < srcMat.rows; ++i)  
                        pData[i*stepSrc] = std::pow(pData[i*stepSrc],3);  
                }  
            }  
            Mat* src;  
        };  
    }//namesapce test  
    void parallelTestWithFor(InputArray _src)//'for' loop  
    {  
        CV_Assert(_src.kind() == _InputArray::MAT);  
        Mat src = _src.getMat();  
        CV_Assert(src.isContinuous());  
        int stepSrc = (int)(src.step/src.elemSize1());  
        for (int x = 0; x < src.cols; ++x)  
        {  
            float* pData = (float*)src.col(x).data;  
            for (int y = 0; y < src.rows; ++y)  
                pData[y*stepSrc] = std::pow(pData[y*stepSrc], 3);  
        }  
    };  
      
    void parallelTestWithParallel_for(InputArray _src)//'parallel_for' loop  
    {  
        CV_Assert(_src.kind() == _InputArray::MAT); // * 错误检测  
        Mat src = _src.getMat();  
        int totalCols = src.cols;  
        typedef test::parallelTestInvoker parallelTestInvoker;  // * 为test::parallelTestInvoker定义别名(parallelTestInvoker),也就是改变了原先parallelTestInvoker这个名字的作用域,从 只能在std用 到 当前。
        parallel_for(BlockedRange(0, totalCols), parallelTestInvoker(src)); // * 注意调用语句,range是记录parallelTestBody循环体的首末位置。
    };  
      
    void parallelTestWithParallel_for_(InputArray _src)//'parallel_for_' loop  
    {  
        CV_Assert(_src.kind() == _InputArray::MAT);  
        Mat src = _src.getMat();  
        int totalCols = src.cols;  
        typedef test::parallelTestBody parallelTestBody;  
        parallel_for_(Range(0, totalCols), parallelTestBody(src));  // * 注意调用语句,range是记录parallelTestBody循环体的首末位置。
    };  
    }//namespace cv  

    main.cpp

    /**@ Test parallel_for and parallel_for_ 
    /**@ Author: chouclee 
    /**@ 03/17/2013*/  
    #include "stdafx.h"// 注意,要添加这句话
    #include <opencv2/opencv.hpp>  
    #include <time.h>  // 读取时间
    #include "test.hpp"  
    using namespace cv;  
    using namespace std;  
      
    int main(int argc, char* argv[])  
    {  
        Mat testInput = Mat::ones(40,400000, CV_32F);// 定义40行400000列,元素全为零的矩阵
        clock_t start, stop;  
      
        start = clock();  
        parallelTestWithFor(testInput);  
        stop = clock();  
        cout<<"Running time using \'for\':"<<(double)(stop - start)/CLOCKS_PER_SEC*1000<<"ms"<<endl;  
      
        start = clock();  
        parallelTestWithParallel_for(testInput);  
        stop = clock();  
        cout<<"Running time using \'parallel_for\':"<<(double)(stop - start)/CLOCKS_PER_SEC*1000<<"ms"<<endl;  
      
        start = clock();  
        parallelTestWithParallel_for_(testInput);  
        stop = clock();  
        cout<<"Running time using \'parallel_for_\':"<<(double)(stop - start)/CLOCKS_PER_SEC*1000<<"ms"<<endl;  
      
        system("pause");  
    }  

    “大多数情况下,parallel_for比for循环慢那么一丁丁点儿,有时甚至会比for循环快一些,总体上两者差不多,parallel_for_一直都是最快的。但上面的代码只是做测试使用(因此强制按列进行操作),实际上,像上面这种简单的操作,直接对Mat使用for循环和指针递增操作,只需要几十毫秒。但是,对于复杂算法,比如光流或之类的,使用parallel_for(虽然不是并行操作,但代码简洁易于维护,且速度和for循环差不多)或者parallel_for_将是不错的选择。”


    总而言之,就是要学会使用parallel_for_()。



    展开全文
  • OpenCV parallel_for_并行化代码OpenCV parallel_for_并行化代码目标前提简单示例:绘制一个Mandelbrot集理论伪码执行Escape 时间算法的实现`并行Mandelbrot实施结果 OpenCV parallel_for_并行化代码 目标 本教程的...

    OpenCV parallel_for_并行化代码

    目标

    本教程的目的是向您展示如何使用OpenCVparallel_for_框架轻松地并行化代码。为了说明这个概念,我们将编写一个程序来绘制Mandelbrot集,利用几乎所有可用的CPU负载。完整的教程代码在这里。本教程旨在保持简单性。

    前提

    第一个先决条件是使用并行框架构建OpenCV。在OpenCV 3.2中,以下并行框架按该顺序可用:

    英特尔线程构建模块(第3方库,应明确启用)
    C =并行C / C ++编程语言扩展(第3方库,应明确启用)
    OpenMP&#

    展开全文
  • OpenCV 并行计算函数 parallel_for_ 的使用Parallel_for_1. 先从构造函数的运算符重载说起2. Parallel_for_ 结合 ParallelLoopBody 使用的一般步骤3. Parallel_for_结合ParallelLoopBody的加速效果实验3.1 自定义类...


    摘录:

    1. OpenCV并行加速Parallel_for_与ParallelLoopBody教程
    2. opencv 并行计算函数 parallel_for_的使用
    3. 官网:Parallel Processing
    4. 高翔,张涛 《视觉SLAM十四讲》

    在使用 OpenCV 的过程中,对图片的处理计算量还是很大的,所以在实施运行的程序中如何高效的计算会节省很多时间。现有的方法有很多,如 OpenMp, TBB, OpenCL 当然还有 Nvidia 的 CUDA。

    CUDA 是个好东西,但是并不太适合毫秒级别的程序运行,单一张图片在 cpu 和 gpu 之间的传输时间就已经达到 300ms(使用 opencv 的cuda 库函数);在TX2上直接对cuda进行编程,数据的传输也是在 50ms(不包含初始化)以上,根本不能拿来做实时的运算。所以如何在cpu上更加高效的计算变得尤为重要。偶然间发现了 OpenCV 的并行计算函数 parallel_for_,它整合了上述的多个组件。

    对于一些基本的循环运算,如果我们直接使用循环,即便是使用指针,运算效率也不高,如果我们使用并行计算,会大大提升运算效率,OpenCV 里面的很多运算都是使用了并行加速的。在 OpenCV 3.2 中,并行框架按照以下顺序提供:

    1. 英特尔线程构建块(第三方库,应显式启用),如TBB(Thread Building Blocks)
    2. OpenMP(集成到编译器,应该被显式启用)
    3. APPLE GCD(系统范围广,自动使用(仅限APPLE))
    4. Windows RT并发(系统范围,自动使用(仅Windows RT))
    5. Windows并发(运行时的一部分,自动使用(仅限Windows) - MSVC ++> = 10))
    6. Pthreads(如果有的话)

    可以看到,OpenCV 库中可以使用多个并行框架:
    一些并行库是第三方库,必须在 CMake(例如TBB)中进行显式构建和启用,其他可以自动与平台(例如 APPLE GCD)一起使用,但是您应该可以使用这些库来访问并行框架直接或通过启用CMake中的选项并重建库;
    第二个(弱)前提条件与要实现的任务更相关,因为并不是所有的计算都是合适的/可以被平行地运行。为了保持简单,可以分解成多个基本操作而没有内存依赖性(无可能的竞争条件)的任务很容易并行化。计算机视觉处理通常易于并行化,因为大多数时间一个像素的处理不依赖于其他像素的状态。

    Parallel_for_

    本文主要描述 Parallel_for_与 ParallelLoopBody 的使用方法,需要使用 <opencv2/core/utility.hpp> 这个头文件。

    Parallel_for_ 的介绍为 parallel data processor,有两种使用方式:

    void cv::parallel_for_ (const Range &range, const ParallelLoopBody &body, double nstripes=-1.)
    
    static void cv::parallel_for_ (const Range &range, std::function< void(const Range &)> functor, double nstripes=-1.)
    

    1. 先从构造函数的运算符重载说起

    我们在调用函数的时候,实际上是使用了括号 ( ) 运算符,构造函数也是普通的函数,所以也用到了括号运算符,那如果想要重载这个括号 ( ) 运算符怎么做呢?先来看一个简单的小例子:

    class Animal
    {
    public:
    	Animal(float weight_,int age_) 
    	{
    		weight = weight_;
    		age = age_;
    	}
    	void operator()(float height) const //重载操作符()
    	{
    		height = 100.0;
    		std::cout << "the age is : " << age << std::endl;
    		std::cout << "the weight is : " << weight << std::endl;
    		std::cout << "the height is : " << height << std::endl;
    	}
    
    private:
    	float weight;
    	int age;
    };
    

    现在调用:

    int main(int argc, char* argv[])
    {
        myanimal::Animal animal(50.0,25);  //创建对象
    	animal(100.0);                     //通过对象调用重载的括号 () 运算符
    	
    	getchar();
    	return 0;
    }
    /*
    the age is : 25
    the weight is : 50
    the height is : 100
    */
    

    总结:

    (1)重载的括号运算符就像一个对象的方法一杨,依旧是通过对象去调用,调用的方式为 “对象名(参数列表)” 这样的形式;

    (2)重载括号运算符的一般操作为 “返回类型 operator(参数列表)” ,后面的const可以不要,参数列表可以使任意的,

    没有参数,则调用方式为:obj()
    一个参数,则调用方式为:obj(参数1)
    多个参数,则调用方式为:obj(参数1,参数2,…)

    2. Parallel_for_ 结合 ParallelLoopBody 使用的一般步骤

    使用步骤一般遵循三步走的原则

    (1)第一步:自定义一个类或者是一个结构体,使这个结构体或者是类继承自 ParallelLoopBody 类,如下:

    class MyParallelClass : public ParallelLoopBody
    {}
    struct MyParallelStruct : public ParallelLoopBody
    {}
    

    (2)第二步:在自定义的类或者是结构体中,重写括号运算符( ),注意:虽然前面讲括号运算符重载可以接受任意数量的参数,但是这里只能接受一个 Range 类型的参数(这是与一般的重载不一样的地方),因为后面的parallel_for_需要使用,如下:

    void operator()(const Range& range)
    {
       //在这里面进行“循环操作”
    }
    

    (3)第三步:使用 parallel_for_ 进行并行处理

    再看一下parallel_for_的函数原型

    CV_EXPORTS void parallel_for_(const Range& range, //重载的括号运算符里面的参数,是一个cv::Range类型
    						      const ParallelLoopBody& body, //自己实现的从ParallelLoopBody类继承的类或者是结构体对象  
    						      double nstripes=-1.);
    

    使用方式如下:

    parallel_for_(Range(start, end), MyParallelClass(构造函数列表));
    //Range(start, end) 就是一个Range对象
    //MyParallelClass(构造函数列表) 就是一个继承自ParallelLoopBody的类的对象
    

    常规的使用方式应该像如下这样才对。这样写本身没什么问题,但是这样的执行方式,在括号重载运算符里面的内容是按照顺序执行的,并没有并发处理

    MyParallelClass obj = MyParallelClass(构造函数列表));  //构造对象
    obj(Range(start, end));   //调用
    

    而上面所述的使用方式:

    parallel_for_(Range(start, end), MyParallelClass(构造函数列表));
    

    没有显式的调用重载的括号运算符,但实际上是隐式调用的,并且以并发方式处理重载运算里面的内容

    3. Parallel_for_结合ParallelLoopBody的加速效果实验

    3.1 自定义类实现

    任务描述:我要定义两个Mat矩阵的逐元素乘积,如下所示

    (1)自定义一个类继承自 ParallelLoopBody,并且重载括号运算

    class ParallelAdd : public ParallelLoopBody//参考官方给出的answer,构造一个并行的循环体类
    {
    public:
    	ParallelAdd(Mat& _src1,Mat& _src2,Mat _result)    //构造函数
    	{
    		src1 = _src1;
    		src2 = _src2;
    		result = _result;
    		CV_Assert((src1.rows == src2.rows) && (src1.cols == src2.cols));
    		rows = src1.rows;
    		cols = src1.cols;
    	} 
    	
    	void operator()(const Range& range) const //重载操作符()
    	{
    		int step = (int)(result.step / result.elemSize1());//获取每一行的元素总个数(相当于cols*channels,等同于step1)
    		
    		for (int col = range.start; col < range.end; ++col)
    		{
    			float * pData = (float*)result.col(col).data;
    			float * p1 = (float*)src1.col(col).data;
    			float * p2 = (float*)src2.col(col).data;
    			for (int row = 0; row < result.rows; ++row)
    				pData[row*step] = p1[row*step] * p2[row*step];
    		}
    	}
    
    private:
    	Mat src1;
    	Mat src2;
    	Mat result;
    	int rows;
    	int cols;
    };
    

    可见重载的运算符里面是一个耗时操作,现在定义两种方式来实现两个 Mat 的逐元素乘积,一种是普通的逐元素处理,另一种是使用parallel进行并发处理,分别通过两个函数完成,如下:

    //直接通过obj()形式调用,不采用并发处理
    void testParallelClassWithFor(Mat _src1,Mat _src2,Mat result)
    {
    	result = Mat(_src1.rows, _src1.cols, _src1.type());
    	int step = (int)(result.step / result.elemSize1());
    	int totalCols = _src1.cols;
    	ParallelAdd add = ParallelAdd(_src1, _src2, result);
    	add(Range(0, totalCols));  //直接调用,没有并发
    }
     
    	
    void testParallelClassTestWithParallel_for_(Mat _src1,Mat _src2,Mat result)
    {
    	result = Mat(_src1.rows, _src1.cols, _src1.type());
    	int step = (int)(result.step / result.elemSize1());
    	int totalCols = _src1.cols;
    	parallel_for_(Range(0, totalCols), ParallelAdd(_src1,_src2,result));  //隐式调用,并发
    }
    

    现在开始测试耗时对比:

    int main(int argc, char* argv[])
    {
    	Mat testInput1 = Mat::ones(6400, 5400, CV_32F);
    	Mat testInput2 = Mat::ones(6400, 5400, CV_32F);
     
    	Mat result1, result2, result3;
    	clock_t start, stop;
     
        //****************测试耗时对比****************************
     
    	start = clock();
    	testParallelClassWithFor(testInput1, testInput2, result1);
    	stop = clock();
    	cout << "Running time using \'general for \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
     
    	start = clock();
    	testParallelClassWithParallel_for_(testInput1, testInput2, result2);
    	stop = clock();
    	cout << "Running time using \'parallel for \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
     
    	start = clock();
    	result3 = testInput1.mul(testInput2);
    	stop = clock();
    	cout << "Running time using \'mul function \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
    	
    	return 0;
    }
    /*
    Running time using 'general for ':645ms
    Running time using 'parallel for ':449ms
    Running time using 'mul function ':70ms
    */
    

    总结:我们可以看见,使用 parallel_for_ 并发的方式的确比直接调用快一些,快了将近 200ms,但是依旧没有使用 OpenCV 自带的标准函数 mul 函数速度快,因为,OpenCV 实现的函数库不仅仅经过了并行处理,还是用了更强大的底层优化,所以,只要是 OpenCV 自己带的方法,一般都是优先使用,除非自己写的比 OpenCV 的还牛逼一些。

    3.2 自定义结构体实现

    任务描述:现在定义一个并行运算的结构体,实现Mat逐元素的三次方运算

    (1)自定义一个结构体继承自 ParallelLoopBody,并且重载括号运算,如下:

    struct ParallelPow:ParallelLoopBody//构造一个供parallel_for使用的循环结构体
    {
    	Mat* src;             //结构体成员,一个Mat类型的指针
    	ParallelPow(Mat& _src)//struct 结构体构造函数
    	{
    		src = &_src;
    	}
    	void operator()(const Range& range) const
    	{
    		Mat& result = *src;
    		int step = (int)(result.step / result.elemSize1());
    		for (int col = range.start; col < range.end; ++col)
    		{
    			float* pData = (float*)result.col(col).data;
    			for (int row = 0; row < result.rows; ++row)
    				pData[row*step] = std::pow(pData[row*step], 3); //逐元素求立方
    		}
    	}
    };	
    

    下面定义两个函数,一个是直接通过 for 循环逐元素进行立方运算,一个是通过 parallel_for_ 并发运算的,通过两个函数实现,如下所示:

    void testParallelStructWithFor(Mat _src)
    {
    	int totalCols = _src.cols;
    	ParallelPow obj = ParallelPow(_src);
    	obj(Range(0, totalCols));
    }
     
    void testParallelStructWithParallel_for(Mat _src)
    {
    	int totalCols = _src.cols;
    	parallel_for_(Range(0, totalCols), ParallelPow(_src));
    }
    

    现在开始测试耗时对比:

    int main(int argc, char* argv[])
    {
    	Mat testInput1 = Mat::ones(6400, 5400, CV_32F);
    	Mat testInput2 = Mat::ones(6400, 5400, CV_32F);
     
    	Mat result1, result2, result3;
    	clock_t start, stop;
    
    	start = clock();
    	testParallelStructWithFor(testInput1);
    	stop = clock();
    	cout << "Running time using \'general for \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
     
    	start = clock();
    	testParallelStructWithParallel_for(testInput1);
    	stop = clock();
    	cout << "Running time using \'parallel for \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
    	
    	start = clock();
    	testInput1.mul(testInput1).mul(testInput1);
    	stop = clock();
    	cout << "Running time using \'mul function \':" << (double)(stop - start) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
    	
    	return 0;
    }
    /*
    Running time using 'general for ':881ms
    Running time using 'parallel for ':195ms
    Running time using 'mul function ':76ms
    */
    

    可以发现,并行运算效率有着显著提升,但是相较于OpenCV的标准实现,依旧偏慢。因此在能够使用OpenCV标准函数实现的时候,尽量不要再自己定义运算,标准的OpenCV函数式经过优化了的,运算效率很高。

    4. Parallel_for_不结合与结合ParallelLoopBody比较

    事实上在c++11中,不一定要用一个类或结构体去继承并行计算循环体类(ParallelLoopBody),可以使用函数来替代。两种方式的比较在下面可见:

    class Parallel_My : public ParallelLoopBody
    {
    public:
        Parallel_My (Mat &img, const float x1, const float y1, const float scaleX, const float scaleY)
            : m_img(img), m_x1(x1), m_y1(y1), m_scaleX(scaleX), m_scaleY(scaleY)
        {
        }
    
        virtual void operator ()(const Range& range) const
        {
            for (int r = range.start; r < range.end; r++) //process of for loop
            {
              /***
    
              ***/
            }
        }
    
        Parallel_My& operator=(const Parallel_My &) {
            return *this;
        };
    
    private:
        Mat &m_img;
        float m_x1;
        float m_y1;
        float m_scaleX;
        float m_scaleY;
    };
    
    int main()
    {
        //! [mandelbrot-transformation]
        Mat Img(4800, 5400, CV_8U);
        float x1 = -2.1f, x2 = 0.6f;
        float y1 = -1.2f, y2 = 1.2f;
        float scaleX = mandelbrotImg.cols / (x2 - x1);
        float scaleY = mandelbrotImg.rows / (y2 - y1);
    
        #ifdef CV_CXX11 //method one
        parallel_for_(Range(0, Img.rows*tImg.cols), [&](const Range& range)
        {
            for (int r = range.start; r < range.end; r++) //这是需要并行计算的for循环
            {
    
            }
        });
    
        #else   //method two
        Parallel_My parallel_my0(Img, x1, y1, scaleX, scaleY);
        parallel_for_(Range(0, Img.rows*Img.cols), parallel_my0);
        #endif
    }
    

    5. 单层光流(补充内容)

    上面讲的应该比较清楚了,可以看一个关于光流的示例:

    class OpticalFlowTracker {
    public:
        OpticalFlowTracker(
            const Mat &img1_,
            const Mat &img2_,
            const vector<KeyPoint> &kp1_,
            vector<KeyPoint> &kp2_,
            vector<bool> &success_,
            bool inverse_ = true, bool has_initial_ = false) :
            img1(img1_), img2(img2_), kp1(kp1_), kp2(kp2_), success(success_), inverse(inverse_),
            has_initial(has_initial_) {}
    
        void calculateOpticalFlow(const Range &range);
    
    private:
        const Mat &img1;
        const Mat &img2;
        const vector<KeyPoint> &kp1;
        vector<KeyPoint> &kp2;
        vector<bool> &success;
        bool inverse = true;
        bool has_initial = false;
    };
    
    void OpticalFlowSingleLevel(
        const Mat &img1,
        const Mat &img2,
        const vector<KeyPoint> &kp1,
        vector<KeyPoint> &kp2,
        vector<bool> &success,
        bool inverse, bool has_initial) {
        kp2.resize(kp1.size());
        success.resize(kp1.size());
        OpticalFlowTracker tracker(img1, img2, kp1, kp2, success, inverse, has_initial);
        parallel_for_(Range(0, kp1.size()),
                      std::bind(&OpticalFlowTracker::calculateOpticalFlow, &tracker, placeholders::_1));
    }
    
    void OpticalFlowTracker::calculateOpticalFlow(const Range &range) {
        // parameters
        int half_patch_size = 4;
        int iterations = 10;
        for (size_t i = range.start; i < range.end; i++) {
            auto kp = kp1[i];
            double dx = 0, dy = 0; // dx,dy need to be estimated
            if (has_initial) {
                dx = kp2[i].pt.x - kp.pt.x;
                dy = kp2[i].pt.y - kp.pt.y;
            }
    
            double cost = 0, lastCost = 0;
            bool succ = true; // indicate if this point succeeded
    
            // Gauss-Newton iterations
            Eigen::Matrix2d H = Eigen::Matrix2d::Zero();    // hessian
            Eigen::Vector2d b = Eigen::Vector2d::Zero();    // bias
            Eigen::Vector2d J;  // jacobian
            for (int iter = 0; iter < iterations; iter++) {
                if (inverse == false) {
                    H = Eigen::Matrix2d::Zero();
                    b = Eigen::Vector2d::Zero();
                } else {
                    // only reset b
                    b = Eigen::Vector2d::Zero();
                }
    
                cost = 0;
    
                // compute cost and jacobian
                for (int x = -half_patch_size; x < half_patch_size; x++)
                    for (int y = -half_patch_size; y < half_patch_size; y++) {
                        double error = GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y) -
                                       GetPixelValue(img2, kp.pt.x + x + dx, kp.pt.y + y + dy);;  // Jacobian
                        if (inverse == false) {
                            J = -1.0 * Eigen::Vector2d(
                                0.5 * (GetPixelValue(img2, kp.pt.x + dx + x + 1, kp.pt.y + dy + y) -
                                       GetPixelValue(img2, kp.pt.x + dx + x - 1, kp.pt.y + dy + y)),
                                0.5 * (GetPixelValue(img2, kp.pt.x + dx + x, kp.pt.y + dy + y + 1) -
                                       GetPixelValue(img2, kp.pt.x + dx + x, kp.pt.y + dy + y - 1))
                            );
                        } else if (iter == 0) {
                            // in inverse mode, J keeps same for all iterations
                            // NOTE this J does not change when dx, dy is updated, so we can store it and only compute error
                            J = -1.0 * Eigen::Vector2d(
                                0.5 * (GetPixelValue(img1, kp.pt.x + x + 1, kp.pt.y + y) -
                                       GetPixelValue(img1, kp.pt.x + x - 1, kp.pt.y + y)),
                                0.5 * (GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y + 1) -
                                       GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y - 1))
                            );
                        }
                        // compute H, b and set cost;
                        b += -error * J;
                        cost += error * error;
                        if (inverse == false || iter == 0) {
                            // also update H
                            H += J * J.transpose();
                        }
                    }
    
                // compute update
                Eigen::Vector2d update = H.ldlt().solve(b);
    
                if (std::isnan(update[0])) {
                    // sometimes occurred when we have a black or white patch and H is irreversible
                    cout << "update is nan" << endl;
                    succ = false;
                    break;
                }
    
                if (iter > 0 && cost > lastCost) {
                    break;
                }
    
                // update dx, dy
                dx += update[0];
                dy += update[1];
                lastCost = cost;
                succ = true;
    
                if (update.norm() < 1e-2) {
                    // converge
                    break;
                }
            }
    
            success[i] = succ;
    
            // set kp2
            kp2[i].pt = kp.pt + Point2f(dx, dy);
        }
    }
    

    代码在 calculateOpticalFlow 函数中实现了单层光流函数,其中调用了 cv::parallel_for_ 并行调用 OpticalFlowTracker::calculateOpticalFlow 该函数计算指定范围内特征点的光流。这个并行 for 循环内部是 Intel tbb 库实现的,我们只需按照其接口,将函数本体定义出来,然后将函数作为 std::function 对象传递给它

    如果对 std::bind 不太熟悉,可以参考之前的文章:

    1. C++11中std::function和std::bind及在ROS12中的使用
    展开全文
  • OpenCV使用OMP完成并行运算,在使用AdaBoost检测的时候,在cascadedetect.cpp 里面,大量使用 ... parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yS...
  • opencv 并行计算函数 parallel_for_ 前面的话 在使用opencv的过程中,对图片的处理计算量还是很大的,所以在实施运行的程序中如何高效的计算会节省很多时间。现有的方法有很多,如OpenMp,TBB,OpenCL,当然...
  • 直接上代码 #include #include #include using namespace std; using namespace cv; class show{ public: show( int _x, int _y, int _z ): x... void show::showxyz(const Range &range) { for( auto i=range.start; i
  • OpenCV 从2.4.3开始加入了并行计算的函数parallel_for和parallel_for_(更准确地讲,parallel_for以前就存在于tbb模块中,但是OpenCV官网将其列在2.4.3.的New Features中,应该是重新改写过的)。 2.4.3中自带的...
  • C++AMP的“入口”即parallel_for_each,只有lambda表达式一个参数。任何可以适用表达式的地方都能适用lambda表达式,所有的lambda表达式都会以[]作为前缀,但是方括号可能不会像这样为空。这被称为“捕获字句”,...
  • parallel_for_函数出现错误,这是因为opencv3不支持std::bind函数,针对上述问题,现将解决方法记录如下: 安装opencv4,将原程序中cmakelists.txt中的opencv版本改为4, 程序可以成功运行; 修改类...
  • cv::parallel_for_ 的一个例子

    千次阅读 2019-01-20 22:12:51
    cv::parallel_for_是opencv封装的一个多线程接口,利用这个接口可以方便实现多线程,不用考虑底层细节,以下是一个具体的例子 继承ParallelLoopBody,重载运算符() class LoopBody : public cv::...
  • parallel_for_each

    千次阅读 2017-05-29 11:14:29
    // print_array complie with: /EHsc #include #include ..."Using for_each:" ... parallel_for_each(a.begin(), a.end(), [&]( int n) { cout ;} ); system( "pause" ); return 0 ; }
  • tbb基础之parallel_for用法详解

    千次阅读 2018-12-26 08:37:36
    要讲解parallel_for,我们首先讲一个例子,该例子是对数组的每一个元素进行遍历,常规的串行算法代码如下: template&lt;typename T&gt; void Visit( T var) { printf("%0.2f, ", var); } ...
  • Intel编译器Windows平台,可以实现内联汇编的编译,有可用的licence file,亲测可用!
  • 本节书摘来自异步社区出版社《C++ AMP:用Visual C++加速大规模并行计算》一书中的第3章,第3.6节,作者: 【美】Kate Gregory , Ade...3.6 parallel_for_each C++ AMP:用Visual C++加速大规模并行计算parallel_fo...
  • 英特尔 Parallel Studio XE 2013 旨在提高应用程序性能的领先开发套件 从今天的性能提升,到明天的性能飞跃 提供顶尖的应用程序性能,同时最小化开发、调优和测试的时间和工作量。英特尔® Parallel Studio XE 为 C...
  • C++ ppl.h的parallel_for语句的问题

    千次阅读 2018-08-08 10:07:08
    parallel_for的并行语句中,如果其中的变量定义在循环体外,会导致运行时间变慢很多,不知道是不是的运用有问题。比如以下三段代码:(原代码指没有使用并行执行的代码) 代码段1运行的速率比原代码还慢,而代码段2...
  • Intel parallel studio xe 2016 license, available for parallel studio 2016 update 1. Linux version.
  •  – Flag indicating whether the fixed-point maps are used for the nearest-neighbor or for a more complex interpolation. The function converts a pair of maps for  remap()  from one representation to...
  • Intel_Parallel_Studio_XE_Cluster_Edition_2019_Update_3.part4_Downloadly.ir.rar
  • Intel_Parallel_Studio_XE_Cluster_Edition_2018_Update_4_Linux.part5_Downloadly.ir.rar
  • 错误原因:softmax的输出层label数量错误,遇见这种报错cuba可以尝试切换到cpu去运行可以看到另外一种报错 切换cpu办法,1.71动态图
  • parallel_for(size_t(0), size, [&](size_t i) { for (size_t j = 0; j ; j++) { double temp = 0; for (int k = 0; k ; k++) { temp += m1[i][k] * m2[k][j]; } result[i][j] = temp; }...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 129,053
精华内容 51,621
关键字:

parallel_for_