在Python中加快图像上距离转换的计算

时间:2018-12-08 00:51:39

标签: python numpy computer-vision

我想找到一种不使用scipy包distance_trnsform_edt()的最快方式来查找二进制图像的距离变换。图像是256 x256。我不想使用scipy的原因是因为在tensorflow中使用它很困难。我要使用这个软件包的时间很长,我需要开始一个新的会话,这需要很多时间。因此,我想制作一个仅使用numpy的自定义函数。

我的方法如下:找到图像中所有1和所有0的坐标。找到每个零像素(a)和一个像素(b)之间的欧几里得距离,然后每个(a)位置的值是到(b)像素的最小距离。我为每个0像素执行此操作。生成的图像具有与原始二进制图相同的尺寸。我的尝试如下所示。

我尝试不使用循环而仅使用向量化来尽可能快地执行此操作。但是我的功能仍然不能像scipy包一样快。当我为代码计时时,看起来分配给变量“ a”的时间最长。但是我不知道是否有办法加快这一步。

如果有人对解决距离转换问题的其他算法有其他建议,或者可以将我引导到python中的其他实现,

def get_dst_transform_img(og): #og is a numpy array of original image
    ones_loc = np.where(og == 1)
    ones = np.asarray(ones_loc).T # coords of all ones in og
    zeros_loc = np.where(og == 0)
    zeros = np.asarray(zeros_loc).T # coords of all zeros in og

    a = -2 * np.dot(zeros, ones.T) 
    b = np.sum(np.square(ones), axis=1) 
    c = np.sum(np.square(zeros), axis=1)[:,np.newaxis]
    dists = a + b + c
    dists = np.sqrt(dists.min(axis=1)) # min dist of each zero pixel to one pixel
    x = og.shape[0]
    y = og.shape[1]
    dist_transform = np.zeros((x,y))
    dist_transform[zeros[:,0], zeros[:,1]] = dists 

    plt.figure()
    plt.imshow(dist_transform)

1 个答案:

答案 0 :(得分:1)

OP中的实现是一种用于距离转换的蛮力方法。该算法为O(n 2 ),因为它计算了每个背景像素到每个前景像素的距离。此外,由于将其向量化的方式,它需要大量内存。在我的计算机上,如果没有抖动,就无法计算256x256图像的距离变换。文献中还描述了许多其他算法,下面我将讨论两种O(n)算法。

注意:通常,距离变换是针对对象像素(值1)到最接近的背景像素(值0)计算的。 OP中的代码是相反的,因此我下面粘贴的代码遵循OP的约定,而不是更常见的约定。


最容易实现的IMO是倒角距离算法。这是一种递归算法,对图像执行两次遍历:一个从左到右,从上到下,从右到左,从下到上。在每个遍中,将传播为先前像素计算的距离。可以使用邻居之间的整数距离或浮点距离来实现此算法。当然,后者产生的误差较小。但是在这两种情况下,都可以通过增加在此传播中查询的邻居数量来大大减少错误。该算法较旧,但是G. Borgefors对其进行了分析,并提出了合适的邻居距离(G. Borgefors, Distance Transformations in Digital Images, Computer Vision, Graphics, and Image Processing 34:344-371, 1986)。

这是一个使用3-4距离的实现(到边缘连接的邻居的距离是3,到顶点连接的邻居的距离是4):

def chamfer_distance(img):
   w, h = img.shape
   dt = np.zeros((w,h), np.uint32)
   # Forward pass
   x = 0
   y = 0
   if img[x,y] == 0:
      dt[x,y] = 65535 # some large value
   for x in range(1, w):
      if img[x,y] == 0:
         dt[x,y] = 3 + dt[x-1,y]
   for y in range(1, h):
      x = 0
      if img[x,y] == 0:
         dt[x,y] = min(3 + dt[x,y-1], 4 + dt[x+1,y-1])
      for x in range(1, w-1):
         if img[x,y] == 0:
            dt[x,y] = min(4 + dt[x-1,y-1], 3 + dt[x,y-1], 4 + dt[x+1,y-1], 3 + dt[x-1,y])
      x = w-1
      if img[x,y] == 0:
         dt[x,y] = min(4 + dt[x-1,y-1], 3 + dt[x,y-1], 3 + dt[x-1,y])
   # Backward pass
   for x in range(w-2, -1, -1):
      y = h-1
      if img[x,y] == 0:
         dt[x,y] = min(dt[x,y], 3 + dt[x+1,y])
   for y in range(h-2, -1, -1):
      x = w-1
      if img[x,y] == 0:
         dt[x,y] = min(dt[x,y], 3 + dt[x,y+1], 4 + dt[x-1,y+1])
      for x in range(1, w-1):
         if img[x,y] == 0:
            dt[x,y] = min(dt[x,y], 4 + dt[x+1,y+1], 3 + dt[x,y+1], 4 + dt[x-1,y+1], 3 + dt[x+1,y])
      x = 0
      if img[x,y] == 0:
         dt[x,y] = min(dt[x,y], 4 + dt[x+1,y+1], 3 + dt[x,y+1], 3 + dt[x+1,y])
   return dt

请注意,此处的许多复杂之处是避免索引超出范围,但仍要一直计算到图像边缘的距离。如果我们只是跳过图像边框周围的像素,则代码将变得更加简单。

由于它是递归算法,因此无法向量化其实现。 Python代码不会很有效。但是用C或类似语言编程将产生一个非常快的算法,该算法可以很好地近似欧几里得距离。

OpenCV's cv.distanceTransform实现了此算法。


另一种非常有效的算法可以计算距离变换的平方。平方距离是可分开的(即可以为每个轴独立计算并相加)。这导致易于并行化的算法。对于每个图像行,该算法都会进行正向和反向传递。然后,对于结果中的每一列,算法都会进行另一次正向和反向传递。这个过程导致了精确的欧几里德距离变换。

该算法最早由R. van den Boomgaard在his Ph.D. thesis in 1992中提出。不幸的是,这没有引起注意。然后由A.Meijster,J.B.T.M.罗尔丁克和W.H. Hesselink(A General Algorithm for Computing Distance Transforms in Linear Time, Mathematical Morphology and its Applications to Image and Signal Processing, pp 331-340, 2002),以及P. Felzenszwalb和D. Huttenlocher(Distance transforms of sampled functions, Technical report, Cornell University, 2004)。

这是已知的最有效的算法,部分原因是它是唯一可以轻松高效地并行化的算法(每个图像行上的计算,然后每个图像列上的计算独立于其他行/列)。 / p>

不幸的是,我没有任何可共享的Python代码,但是您可以在线找到实现。例如,OpenCV's cv.distanceTransform实现了此算法,而PyDIP's dip.EuclideanDistanceTransform也实现了此算法。