【问题标题】:Fill Bounding Boxes in 2D array填充二维数组中的边界框
【发布时间】:2019-07-05 10:26:15
【问题描述】:

我有一个 2D numpy 数组,看起来像

array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0.]]) `

我想在上面显示的 1 上创建像蒙版一样的边界框。例如它应该是这样的

array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], 
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0.]])

我怎样才能轻松做到这一点?另外,如果存在其他编号(例如 2,3 等)但我想忽略它们并且组大多是 2,我该怎么办。

【问题讨论】:

  • 搜索“连接组件标签”以了解如何在“背景”值的字段中识别单独的对象。然后找到每个唯一标签的最小和最大行/列。

标签: python arrays numpy bounding-box numpy-ndarray


【解决方案1】:

我们有skimage.measure 让您在组件标签方面变得轻松。我们可以使用skimage.measure.label标记数组中的不同组件,使用skimage.measure.regionprops获取对应的切片,在这种情况下我们可以使用它们将值设置为1

def fill_bounding_boxes(x):
    l = label(x)
    for s in regionprops(l):
        x[s.slice] = 1
    return x

如果我们尝试使用建议的示例:

from skimage.measure import label, regionprops

a = np.array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0.]])

我们得到:

fill_bounding_boxes(x)

array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0.]])

【讨论】:

    【解决方案2】:

    虽然之前的回复都很好,但您可以使用scipy.ndimage 来做到这一点:

    import numpy as np
    from scipy import ndimage
    
    def fill_bboxes(x):
        x_components, _ = ndimage.measurements.label(x, np.ones((3, 3)))
        bboxes = ndimage.measurements.find_objects(x_components)
    
        for bbox in bboxes:
            x[bbox] = 1
    
        return x
    

    ndimage.measurements.label 使用定义邻域的 3x3-“ones”矩阵进行连接组件标记。 find_objects 然后确定每个组件的边界框,然后您可以使用它来将所有内容设置为 1。

    【讨论】:

    • 你认为你可以将它扩展到非正交边界框吗?
    • 您可以确定每个对象的最小封闭矩形(即每个bbox in bboxes,然后填充该矩形下的区域而不是bbox下的区域。据我所知,scipy 没有为此的预制函数。因此,您要么自己编写它,要么使用 OpenCVs cv.minAreaRect 之类的东西,但无论哪种情况,您都必须决定在填充时如何离散化。
    【解决方案3】:

    有一个解决方案,但它有点hacky,我不会为你编程。

    OpenCV - 图像处理库,具有查找矩形轮廓的算法 -> 直线或旋转。您可能想要做的是将数组转换为 2D 灰度图像,找到轮廓并在轮廓内写入 1。

    检查这张图片 - 它来自 Opencv DOC - 7.a - https://docs.opencv.org/3.4/dd/d49/tutorial_py_contour_features.html

    您会对绿线内的所有内容感兴趣。


    说实话,在我看来,这比为边界框编程一些算法要容易得多

    注意

    当然你真的不需要做图像的东西,但我认为使用opencv的算法来处理边界框(countours)就足够了

    【讨论】:

      【解决方案4】:

      这是一个有趣的问题。 2D 卷积是一种自然的方法。但是,如果输入矩阵是稀疏的(如您的示例中所示),这可能会很昂贵。对于稀疏矩阵,另一种方法是使用聚类算法。这仅从输入框 a (您的示例中的数组)中提取非零像素,并运行层次聚类。聚类基于一个特殊的距离矩阵(一个元组)。如果框在任一方向上最多分隔 1 个像素,则会发生合并。您还可以在初始化步骤中为您需要的任何数字应用过滤器(比如只为 a[row, col]==1 执行并跳过任何其他数字,或任何您想要的。

      from collections import namedtuple 
      
      Point = namedtuple("Point",["x","y"]) # a pixel on the matrix
      Box = namedtuple("Box",["tl","br"]) # a box defined by top-lef/bottom-right
      
      def initialize(a):
          """ create a separate bounding box at each non-zero pixel. """
          boxes = []
          rows, cols = a.shape
          for row in range(rows):
              for col in range(cols):
                  if a[row, col] != 0:
                      boxes.append(Box(Point(row, col),Point(row, col)))
          return boxes
      
      def dist(box1, box2):
          """ dist between boxes is from top-left to bottom-right, or reverse. """
          x = min(abs(box1.br.x - box2.tl.x), abs(box1.tl.x - box2.br.x))
          y = min(abs(box1.br.y - box2.tl.y), abs(box1.tl.y - box2.br.y))
          return x, y
      
      def merge(boxes, i, j):
          """ pop the boxes at the indices, merge and put back at the end. """
          if i == j:
              return
      
          if i >= len(boxes) or j >= len(boxes):
              return
      
          ii = min(i, j)
          jj = max(i, j)
          box_i = boxes[ii]
          box_j = boxes[jj]
          x, y = dist(box_i, box_j)
      
          if x < 2 or y < 2:
              tl = Point(min(box_i.tl.x, box_j.tl.x),min(box_i.tl.y, box_j.tl.y))
              br = Point(max(box_i.br.x, box_j.br.x),max(box_i.br.y, box_j.br.y))
              del boxes[ii]
              del boxes[jj-1]
              boxes.append(Box(tl, br))
      
      
      def cluster(a, max_iter=100):
          """ 
              initialize the cluster. then loop through the length and merge 
              boxes. break if `max_iter` reached or no change in length.
          """
          boxes = initialize(a)
          n = len(boxes)
          k = 0
      
          while k < max_iter:
              for i in range(n):
                  for j in range(n):
                      merge(boxes, i, j)
              if n == len(boxes):
                  break
              n = len(boxes)
              k = k+1
      
          return boxes
      
      cluster(a)
      # output: [Box(tl=Point(x=2, y=2), br=Point(x=5, y=4)),Box(tl=Point(x=11, y=9), br=Point(x=14, y=11))]
      
      # performance 275 µs ± 887 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
      # compares to 637 µs ± 9.36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) for 
      #the method based on 2D convolution
      

      这将返回由角点(左上角和右下角)定义的框列表。这里 x 是行号,y 是列号。初始化循环遍历整个矩阵。但在那之后,我们只处理一个非常小的点子集。通过更改 dist 函数,您可以自定义框定义(重叠、非重叠等)。性能可以进一步优化(例如,如果 i 或 j 大于 for 循环中框的长度,则中断,而不是简单地从合并函数返回并继续)。

      【讨论】:

        猜你喜欢
        • 2020-08-25
        • 1970-01-01
        • 1970-01-01
        • 2016-05-27
        • 1970-01-01
        • 1970-01-01
        • 2010-11-23
        • 1970-01-01
        • 2018-02-05
        相关资源
        最近更新 更多