【问题标题】:How to use Python and OpenCV with multiprocessing?如何使用 Python 和 OpenCV 进行多处理?
【发布时间】:2015-12-22 21:28:58
【问题描述】:

我正在使用 Python 3.4.3 和 OpenCV 3.0.0 来处理(应用各种过滤器)内存中的一个非常大的图像(80,000 x 60,000),我想使用多个 CPU 内核来提高性能。经过一番阅读,我得出了两种可能的方法:1)使用python的multiprocessing模块,让每个进程处理一个大图像的切片并在处理完成后加入结果(这可能应该在POSIX系统上执行? ) 2) 由于 NumPy 支持 OpenMP 而 OpenCV 使用 NumPy,我可以将多处理留给 NumPy 吗?

所以我的问题是:

哪一个会是更好的解决方案? (如果它们看起来不合理,可能的方法是什么?)

如果选项 2 不错,我应该使用 OpenMP 同时构建 NumPy 和 OpenCV 吗?我将如何真正实现多处理? (我真的找不到有用的说明..)

【问题讨论】:

  • 如果 numpy 是用 BLAS 的多核实现构建的,那么常规操作(向量/矩阵加法和向量/矩阵乘法)在多核中工作。但是,没有一种简单的方法可以使用标准操作执行图像卷积。(因为它需要一个滑动窗口)。最简单的方法是选项 1,但我建议另一种方法:结合选项 1 使用 hdf5 来节省内存。
  • 如果 openCV 是用多线程(TBB 和/或 OpenMP)编译的,一些操作是多线程执行的,而另一些则不是。 IPP 异步。也可能有帮助......

标签: python opencv image-processing parallel-processing openmp


【解决方案1】:

在阅读了一些 SO 帖子后,我想出了一种在 Python3 中使用 OpenCVmultiprocessing 的方法。我建议在 linux 上执行此操作,因为根据this post,只要内容未更改,生成的进程就会与其父进程共享内存。这是一个最小的例子:

import cv2
import multiprocessing as mp
import numpy as np
import psutil

img = cv2.imread('test.tiff', cv2.IMREAD_ANYDEPTH) # here I'm using a indexed 16-bit tiff as an example.
num_processes = 4
kernel_size = 11
tile_size = img.shape[0]/num_processes  # Assuming img.shape[0] is divisible by 4 in this case

output = mp.Queue()

def mp_filter(x, output):
    print(psutil.virtual_memory())  # monitor memory usage
    output.put(x, cv2.GaussianBlur(img[img.shape[0]/num_processes*x:img.shape[0]/num_processes*(x+1), :], 
               (kernel_size, kernel_size), kernel_size/5))
    # note that you actually have to process a slightly larger block and leave out the border.

if __name__ == 'main':
    processes = [mp.Process(target=mp_filter, args=(x, output)) for x in range(num_processes)]

    for p in processes:
        p.start()

    result = []
    for ii in range(num_processes):
        result.append(output.get(True))

    for p in processes:
        p.join()

不使用Queue,另一种收集进程结果的方法是通过multiprocessing 模块创建一个共享数组。 (必须导入ctypes

result = mp.Array(ctypes.c_uint16, img.shape[0]*img.shape[1], lock = False)

然后假设没有重叠,每个进程可以写入数组的不同部分。然而,创建一个大的mp.Array 非常慢。这实际上违背了加快操作的目的。因此,仅当与总计算时间相比增加的时间不多时才使用它。这个数组可以通过以下方式变成一个numpy数组:

result_np = np.frombuffer(result, dtypye=ctypes.c_uint16)

【讨论】:

  • GaussianBlur 不是 GaussianBlue。由于 StackOverflow-s 限制为 6 个字符,无法建议编辑。
  • @Eilyre 到处都是小提琴。完成。
【解决方案2】:

我不知道你需要什么类型的过滤器,但如果它相当简单,你可以考虑libvips。这是一个用于处理非常大图像(大于您拥有的内存量)的图像处理系统。它来自一系列欧盟资助的科学艺术成像项目,因此重点在于图像捕获和比较所需的操作类型:卷积、秩、形态学、算术、颜色分析、重采样、直方图等.

这是fast (faster than OpenCV, on some benchmarks at least), needs little memory,还有a high-level Python binding。它适用于 Linux、OS X 和 Windows。它会自动为您处理所有的多处理。

【讨论】:

  • 感谢您的意见。滤波方面,我对图像进行了高斯的高斯和拉普拉斯算子,相信VIPS会表现得非常好。我没有使用VIPS,因为我还对图像进行了其他操作,例如图像之间的逻辑操作。 NumPy 使这更容易,这就是我选择 OpenCV 而不是 VIPS 的原因。
  • 逻辑运算是指像素级和/或/eor吗? libvips 有,例如。 a = (a << 8) & b ^ c
  • 是的。感谢您的评论。我实际上尝试过VIPS。但是,我只能使图像卷积起作用。它确实表现得更好。我在这里提出了一个新问题:stackoverflow.com/questions/33195055/… 另外,我设法使用 openCV 进行多处理,我会发布答案。
  • 您好,我在您的新问题上发布了一些内容,希望对您有所帮助。
【解决方案3】:

这可以使用Ray 干净地完成,这是一个用于并行和分布式 Python 的库。 Ray 解释“任务”而不是使用分叉连接模型,这提供了一些额外的灵活性(例如,即使在分叉工作进程之后,您也可以将值放入共享内存中),相同的代码在多台机器上运行,您可以将任务组合在一起等。

import cv2
import numpy as np
import ray

num_tasks = 4
kernel_size = 11


@ray.remote
def mp_filter(image, i):
    lower = image.shape[0] // num_tasks * i
    upper = image.shape[0] // num_tasks * (i + 1)
    return cv2.GaussianBlur(image[lower:upper, :],
                            (kernel_size, kernel_size), kernel_size // 5)


if __name__ == '__main__':
    ray.init()

    # Load the image and store it once in shared memory.
    image = np.random.normal(size=(1000, 1000))
    image_id = ray.put(image)

    result_ids = [mp_filter.remote(image_id, i) for i in range(num_tasks)]
    results = ray.get(result_ids)

请注意,您可以在共享内存中存储更多的 numpy 数组,如果您有包含 numpy 数组的 Python 对象(例如包含 numpy 数组的字典),您也可以从中受益。在后台,这使用了Plasma shared-memory object storeApache Arrow data layout

您可以在Ray documentation 中阅读更多内容。请注意,我是 Ray 开发人员之一。

【讨论】:

  • 对 python (3.x) 默认的multiprocessing 的任何基准测试?如果是这样..分享一个链接。 (对 Pong 不感兴趣)。
  • 我没有链接,但最大的区别在于大型数值数据。例如,如果 x = np.zeros(10**8)f 是身份功能(或 Ray 中的身份远程功能),那么在我的笔记本电脑上,%time pool.apply_async(f, args=(x, )).get() 需要 15 秒,%time ray.get(f.remote(x)) 需要 1.5 秒。
  • @RobertNishihara Out of left field:你知道有人在 Flask 中呼叫 Ray 吗?
猜你喜欢
  • 2014-04-01
  • 2022-06-08
  • 2020-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多