【问题标题】:Fast method to retrieve contour mask from a binary mask in Python从 Python 中的二进制掩码中检索轮廓掩码的快速方法
【发布时间】:2022-04-22 22:06:25
【问题描述】:

我想制作一个实时应用程序,其中涉及查找二进制掩码的边缘。我需要一些快速的东西,如果可能的话,没有 GPU,它希望每个图像的运行时间低于 0.0005 秒,大小为 (1000,1000)。我将使用以下二进制图像示例,大小为 (1000,1000)。

(要复制的代码:)

import numpy as np
im=np.zeros((1000,1000),dtype=np.uint8)
im[400:600,400:600]=255

Image

第一个快速做事的合乎逻辑的方法是使用 OpenCV 库:

import cv2
timeit.timeit(lambda:cv2.Laplacian(im,cv2.CV_8U),number=100)/100
0.0011617112159729003

正如预期的那样导致: laplacian

我发现这种方式非常耗时。在此之后我尝试了 findContours:

 def usingcontours(im):
    points=np.transpose(cv2.findContours(im,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)[1][0])
    tmp=np.zeros_like(im)
    tmp[tuple(points)]=255
    return tmp 
timeit.timeit(lambda:usingcontours(im),number=100)/100
0.0009052801132202148

它给出了与上面相同的结果。 这更好,但仍然没有我想要的那么好。作为最后的手段,我继续使用 numpy 来近似拉普拉斯算子,尽管我知道它会更糟:

def usinggradient(im):
    tmp=np.gradient(im)
    return ((tmp[0]+tmp[1])>0).astype(np.uint8)
timeit.timeit(lambda:usinggradient(im),number=100)/100
0.018681130409240722

那么,有人对我如何加速算法有任何进一步的想法吗?我强调我希望这个算法用于二值图像,所以我想一定有更好的实现。

【问题讨论】:

  • 您可以通过一些 NumPy 和 Scipy 形态侵蚀以及 NumPy 的按位运算来做到这一点。查看scipy.ndimage.morphology.binary_dilationnp.logical_* 函数。
  • 1000 x 1000 / 0.0005s = 2 x 10^9 pixels/second -- 每个像素需要 1-2 个时钟周期,即使是矢量化和并行化也没什么用处。
  • @DanMašek 2 x 10^9 像素/秒意味着按位图像 2x10^9 位/秒。假设一个好的程序使用所有内核,我相信这是可能的,在一个 8 核 2GHz CPU 中,假设每个内核有 4 个线程,我每个时钟周期可以处理 32 个像素(位),所以 64*10^9像素/秒 = 1000 x 1000/ 0.00016 秒。所以我要求任何复杂度低于 O(3*n) 的现成实现,我相信这在二进制图像中是可能的。
  • timeit.timeit(lambda:im+1,number=100)/100 给出 8.358001708984375e-05 秒,所以比我想象的要好,我需要一个小于 O(4*n) 的算法) 复杂性

标签: python opencv numpy binary edges


【解决方案1】:

我用cv2.findContours 选择了最快的一个来加快速度。在其中,我们可以用简单的slicing 替换那些昂贵的transpose 并转换为元组部分,就像这样 -

idx = cv2.findContours(im,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)[1][0]
out = np.zeros_like(im)
out[idx[:,0,0],idx[:,0,1]] = 255

运行时测试-

In [114]: # Inputs
     ...: im=np.zeros((1000,1000),dtype=np.uint8)
     ...: im[400:600,400:600]=255
     ...: idx = cv2.findContours(im,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)[1][0]
     ...: 

In [115]: def original_app(im, idx):
     ...:     points=np.transpose(idx)
     ...:     tmp=np.zeros_like(im)
     ...:     tmp[tuple(points)]=255
     ...:     return tmp
     ...: 
     ...: def proposed_app(im, idx):
     ...:     out = np.zeros_like(im)
     ...:     out[idx[:,0,0],idx[:,0,1]] = 255
     ...:     return out
     ...: 

In [120]: %timeit original_app(im, idx)
10000 loops, best of 3: 108 µs per loop

In [121]: %timeit proposed_app(im, idx)
10000 loops, best of 3: 101 µs per loop

In [122]: %timeit cv2.findContours(im,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
1000 loops, best of 3: 1.55 ms per loop

因此,所提出的方法有一些边际改进,但与轮廓查找本身相比,这似乎可以忽略不计。

我查看了scikit-image's version 并进行了快速测试,似乎它比 OpenCV 版本慢得多。

【讨论】:

  • 感谢您的回答。好的,所以切片确实加速了算法。正如您在上面提到的,findContours 是一个耗时的。问题是 findContours 也有不同的计算时间,具体取决于二进制形状的复杂性,其中 Ω(findContours) 比其他方法大得多,甚至大 10 倍(不幸的是,我无法找到一个好的代表性图像来描绘这个事实)。
  • 我想通过使用 cv2.CHAIN_APPROX_SIMPLE 会使 findContours 更快,但不好的是我不能使用二进制 numpy 数组作为输入,在我看来,这会加速很多功能总的来说,通过将访问和检索内存所需的时间减少到 1/8,并为我提供了一种更快的运行时间。无论如何,再次感谢您的宝贵时间。
猜你喜欢
  • 2017-05-26
  • 2022-01-20
  • 1970-01-01
  • 1970-01-01
  • 2015-11-30
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多