在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)

如何利用OpenCV的parallel_for_并行化代码

目标

本教程的目标是展示如何使用OpenCV的parallel_for_框架轻松实现代码并行化。为了说明这个概念,我们将编写一个程序,利用几乎所有的CPU负载来绘制Mandelbrot集合。完整的教程代码在这里。如果想了解更多关于多线程的信息,请参考本教程中提及的参考书或课程。

预备条件

首先是搭建OpenCV并行框架。在OpenCV3.2中,可以按此顺序使用以下并行框架:

  1. 英特尔线程构建模块(第三方库,应该明确启用)
  2. C =并行C / C 编程语言扩展(第三方库,应该明确启用)
  3. OpenMP(集成的编译器,应明确启用)
  4. APPLE GCD(系统层面,自动使用(仅适用APPLE))
  5. Windows RT并发(系统层面,自动使用(仅适用Windows RT))
  6. Windows并发(部分运行时间,自动使用(仅适用Windows - MSVC > = 10))
  7. Pthreads (如果适用)

正如前面所述,OpenCV库可以使用多个并行框架。有些并行库为第三方提供的库,建立时应明确地用CMake(如TBB,C =)启用,其余均为自动可用的平台(例如APPLE GCD),但是,无论是直接使用并行框架还是利用CMake启用并行框架并重建库,首先要做的是启用并行框架。

第二个(弱)预备条件与任务相关,因为不是所有任务的计算都可以/适合以并行方式来运行。为了尽量保持简单,可以将任务分解为与存储器无关的多个元素,从而使其更加容易实现并行化。在计算机视觉处理过程中,由于大多数时间里一个像素的处理不依赖于其它像素的状态,所以往往更加容易实现并行化。

简单的示例:绘制Mandelbrot集合

这个例子中将展示如何绘制Mandelbrot集合,将普通的顺序代码实现并行化计算。

理论

Mandelbrot集合的名称是数学家阿德里恩·多迪(Adrien Douady)为悼念数学家蒙德布罗特(Mandelbrot),以他的名字来命名的。它在数学界之外,作为分形类的一个例子,在图像表示领域非常著名。Mandelbrot集合为一组自相似的重复图案在不同尺度下重复显示结果。为了进一步深入介绍,可以参考Wikipedia article。在这里,仅介绍利用公式绘制Mandelbrot集合(选自维基百科的文章)。

Mandelbrot集合是在复平面中一组值C沿着0轨迹的二次迭代映射的边界。

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(1)

即,复数c作为Mandelbrot集的一部分,从 Z0 = 0开始重复进行迭代,当n趋近于无穷大时,Zn的绝对值的边界值,它可以表示为:

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(2)

伪代码

生成Mandelbrot集合的简单的算法被称为“逃逸时间算法”。为渲染图像中的每个像素,根据复数值是否在边界范围之内,利用递推关系进行测试。经过数次迭代之后,不属于Mandelbrot集合的像素将快速逃逸,留下来的将是属于Mandelbrot集合的像素。随着计算时间的增加,迭代后的高阶值将产生一个更详细的图像。在这里使用实现“逃逸”所需要的迭代次数来描绘图像中的像素值。

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(3)

将伪代码和理论相关联之后,得到:

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(4)

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(5)

在上图中,复数的实部在x轴上,复数的虚部在y轴上。通过对图形局部放大,可以看到整个形状均重复可见。

代码实现逃逸时间算法的实现

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(6)

在这里,我们使用了std::complex模板类来表示复数。利用这个函数来进行测试,以检查像素是否在集合之中,并返回“逃逸”迭代。

顺序的Mandelbrot实现

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(7)

在此程序中,通过依次遍历渲染图像中的像素来进行测试,以检查像素是否属于Mandelbrot集合。

需要做的另一件事是把像素坐标转换Mandelbrot集合空间:

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(8)

最后,将灰度值分配给像素,使用以下规则:

  • 当迭代次数达到最大值时,像素为黑色(假定像素在Mandelbrot集合中);
  • 否则根据逃脱“逃逸迭代”和缩放尺度,为像素分配一个灰度值,以适应灰度范围。

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(9)

使用线性缩放转换不足以感知的灰度变化。为了克服这个问题,使用一个平方根转换来提升感知度(引用了Jeremy D. Frens博客中的内容):

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(10)

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(11)

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(12)

绿色曲线对应于简单的线性缩放转换,蓝色曲线对应于平方根转换,可以从中观察到的最低值如何沿着斜坡正向上升。

并行Mandelbrot实现

在顺序的Mandelbrot实现中,每个像素被独立计算。为了优化计算,我们可以利用现代处理器的多核架构并行执行多个像素的计算,利用OpenCV的CV :: parallel_for_框架可以轻松实现。

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(13)

第一件事是声明一个继承CV :: ParallelLoopBody的自定义类覆盖virtual void operator ()(const cv::Range& range) const。

operator ()表示将通过一个独立的线程来处理像素的子集,这种拆分是自动完成的,以平均分配计算负荷,为此必须将像素索引坐标转换成2D [行,列]坐标。还要注意的是,必须保持图像的mat对象引用值,以便能够适时地对图像进行修改。

调用并行执行程序:

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(14)

在这里,range表示将要执行的操作总数,即图像中的像素总数。使用CV :: setNumThreads设置线程数,还可以使用CV :: parallel_for_中的 nstripes参数指定拆分的数量CV :: parallel_for_。例如,如果处理器有4个线程,则设置CV :: setNumThreads(2)或者设置nstripes = 2应该是一样的,默认情况下它会使用所有可用的处理器线程,但拆分后只有两个线程。

为了简化并行的实现,C 11标准删除了ParallelMandelbrot类,采用lambda表达式代替它:

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(15)

运行结果

可以在这里找到完整的教程源代码,并行实现的性能取决于CPU的种型。例如,在4核/ 8线程的CPU上,可以提速6.9倍左右。如果要问,为什么达不到8倍速,其中有很多因素;主要原因是由于:

  • 创建和管理线程的额外开销;
  • 并行运行的后台进程;
  • 带2个逻辑线程的4硬件核与8硬件核之间是有区别的。

由教程代码生成的输出图像(可以对代码进行修改,以使用更多次的迭代,根据逃逸迭代次数来分配像素颜色,并使用调色板以获得更美的图像):

在opencv中基本用到的数据类型(OpenCV之九如何利用OpenCV的parallel)(16)

Mandelbrot集合XMIN = -2.1,XMAX = 0.6,YMIN = -1.2,YMAX = 1.2,maxIterations = 500

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页