c++伪代码_【2】OpenCV核心模块(9)使用OpenCV parallel_for_并行运行代码

51f8d31b3a295818dd26023aa1ba58f6.png

本文是OpenCV核心模块(core module)的最后一节内容。下一部分会更新OpenCV的图像处理模块,基本的图像处理算法都在这个模块中。

本文的目标是展示如何用parallel_for_框架快速实现代码的并行运行。下面例程中,并行方法使用几乎100%的CPU资源,绘制一个Mandelbrot集合图像。与单线程相比,速度提升约7倍。如果想了解更多的多线程编程信息,需要阅读相关的参考书或课程,本例只是简单的例子。

  • 原文网址How to use the OpenCV parallel_for_ to parallelize your code
  • 本地目录D:opencvsourcesdoctutorialscorehow_to_use_OpenCV_parallel_for_
  • 代码目录D:opencvsourcessamplescpptutorial_codecorehow_to_use_OpenCV_parallel_for_
  • GitHub 有相应文档和OpenCV源代码
  • 版本OpenCV4.1.2(版本兼容性见英文原文,部分文档适用于OpenCV2.0和3.0)
  • 环境Windows、C++、VS2019 Community

预先条件

首先,需要OpenCV编译时添加一个并行框架。在OpenCV3.2中,提供了并行框架:

  1. Intel TBB(第三方库,显式引用)
  2. C= 并行C/C++编程语言扩展(第三方库,显式引用)
  3. OpenMP(集成到编译器,显式引用)
  4. APPLE GCD(自动使用,苹果系统专用)
  5. Windows RT并发(自动使用,Windows RT专用)
  6. Windows 并发(运行时的一部分,自动使用,Windows专用-MSVC++10以上)
  7. Pthreads

上面的并行框架都可用于OpenCV库。一些第三方库需要在OpenCV库生成时显式引用,cmake时选中。其他的根据平台不同可以自动使用,但是要有直接使用的权限或者编译生成时选择使用。

另外,要执行的任务适合于并行计算。或者能够分解为并行计算的子任务。计算机视觉通常很容易并行化,因为一般一个像素在处理时,与其他像素处理与否不冲突。

简单例子:绘制一个Mandelbrot集合

通过使用绘制Mandelbrot集的示例来说明,如何轻松调整常规的连续执行代码,实现并行化计算。

理论

Mandelbrot集合的命名是数学家Adrien Douady向数学家Benoit Mandelbrot致敬。是图像分形表示的一个例子,表现为图像的自相似性,即整个图像的形状在不同图像的尺度重复出现。为更深入的了解可以看维基百科。这里,只是介绍如何绘制Mandelbrot集合的公式。

Mandelbrot集合是复数c在复平面,从初值0开始进行二次迭代,保持有界的值的集合:

复数

是Mandelbrot集合的一部分,若对于起始的
,重复迭代,无论n多大,
有界。以上可表达为:

伪代码

生成Mandelbrot集合中一个像素的算法成为“逃逸时间算法”。递归生成图像的某个像素过程中,测试复数是否有界或达到最大迭代次数(上面所述条件)。不属于Mandelbrot集合的像素会迅速逃逸,而假定这个像素在最大迭代次数后仍在集合中。迭代次数越多,生成的图像就越精细,计算时间相应增加。用能够逃逸的迭代次数作为图像中的像素值。

For each pixel (Px, Py) on the screen, do:
{
  x0 = scaled x coordinate of pixel (scaled to lie in the Mandelbrot X scale (-2, 1))
  y0 = scaled y coordinate of pixel (scaled to lie in the Mandelbrot Y scale (-1, 1))
  x = 0.0
  y = 0.0
  iteration = 0
  max_iteration = 1000
  while (x*x + y*y < 2*2  AND  iteration < max_iteration) {
    xtemp = x*x - y*y + x0
    y = 2*x*y + y0
    x = xtemp
    iteration = iteration + 1
  }
  color = palette[iteration]
  plot(Px, Py, color)
}

为了把伪代码和理论相联系,有:

c527b843acc02d080f6fe471531983fc.png

图中,虚数的实部在X轴,虚部在Y轴。如果放大指定位置,整个形状重复可见。

实现

逃逸时间算法实现

//[mandelbrot逃逸时间算法]

这里用到了std::complex模板类来表示复数。这个函数来测试像素是否在集合中,并返回逃逸的迭代次数。

连续Mandelbrot实现

//[连续mandelbrot实现]

在实现过程中,遍历图像的每个像素,分别迭代、尺度变换,得到最终的像素值。

另外,需要将像素坐标转换到Mandelbrot集合坐标空间:

//[获取mandelbrot变换所需的初值x1,y1,scaleX,scaleY]

最后,给像素赋值,用下面的规则:

  • 如果一个像素达到最大迭代次数,为黑色(认为像素在集合中)
  • 否则用逃逸迭代次数赋值,然后将迭代次数的数值转换到灰度尺度范围内
//[mandelbrot灰度尺度变换]

使用线性尺度变换不能精确反映出灰度变化。为克服该缺点,用平方根尺度变换来增强显示:

e8e3292b8ddbfd53960d7e01aff407cf.png

绿色是线性尺度变换,蓝色对应平方根尺度变换。可以看到左侧最低值的增强。

并行Mandelbrot实现

从上面的连续Mandelbrot实现可以看到,每个像素都是独立计算的。为了优化计算,利用多核架构的处理器,执行多个像素并行计算。用OpenCV的cv::parallel_for_框架很容易实现。

//[并行mandelbrot实现]

首先要做的是声明一个自定义类,继承于cv::ParalelLoopBoody,并且重载虚函数virtual void operator()(const cv::Range& range) const。

operator中的range重新表示被独立线程处理的像素子集。分解任务是自动完成的,并根据CPU核心的计算能力平均分配。将像素索引坐标转换为[row,col]坐标。另外,对mat图像保持引用(reference),以实现in-place修改。

并行执行用下面方式调用:

//[并行mandelbrot调用方式二:需要前面定义的类]

这里,range表示将要执行的运算次数,即图像的像素个数。为了设置线程数目,可以用cv::setNumThreads(2)或者设置parallel_for_的第三个参数nstrips=2。默认会使用所有可用处理器线程,但是任务会分为两个线程。

注意:C++11标准允许通过lambda表达式代替ParalleMandelbrot类,简化上述并行实现:

//[并行mandelbrot调用方式一:cxx11,不需要前面定义的类]

代码

//---parallel_for_实现并行计算-----------//

结果

并行运算的性能取决于CPU的类型。例如4核8线程CPU,可以提升约6.9倍速度。有很多因素可以解释为什么没有达到8倍的加速。主要原因是:

  • 创建与管理线程的时间
  • 后台有其它进程在并行运行
  • 4核每核两逻辑线程与8核的不同

本例运行结果生成的图像如下图。(可以修改代码使用更多的迭代次数,根据逃逸迭代次数的不同用调色板为像素配色)

917fe4375a9cb1855ad4de4c36c41586.png

984c69e3db6c01197d6669dc4a09abcf.png
图像(480, 540, CV_8U); x1 = -2.1f, x2 = 0.6f, y1 = -1.2f, y2 = 1.2f; 最大迭代次数500
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值