Python 在二维数组中填充边界框

Python 在二维数组中填充边界框,python,arrays,numpy,bounding-box,numpy-ndarray,Python,Arrays,Numpy,Bounding Box,Numpy Ndarray,我有一个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.,

我有一个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等其他no.s,但我想忽略它们,而且组中大多数是2,我该怎么做。

有一个解决方案,但它有点黑客,我不会为您编程

OpenCV-图像处理库,有一个算法用于查找矩形轮廓->直线或旋转。你可能想做的是将你的阵列转换成2D灰度图像,找到轮廓,然后在轮廓中写下你的1

检查此图像-它来自Opencv文档-7.a-

你会对绿线内的一切都感兴趣


老实说,我认为对我来说,似乎比为边界框编写一些算法要容易得多

注意


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

我们有
略读。测量
可以让组件标记变得简单。我们可以使用来标记数组中的不同组件,并获得相应的切片,在这种情况下,我们可以使用这些切片将值设置为
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.]])

这是一个有趣的问题。二维卷积是一种自然的方法。但是,如果输入矩阵是稀疏的(如您的示例中所示),那么这可能代价高昂。对于稀疏矩阵,另一种方法是使用聚类算法。这将仅从输入框a(示例中的数组)中提取非零像素,并运行分层聚类。聚类基于一个特殊的距离矩阵(元组)。如果框在任一方向上最多分隔1个像素,则会发生合并。您还可以对初始化步骤中需要的任何数字应用过滤器(例如,仅对[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
从集合导入namedtuple
点=命名的倍数(“点”、[“x”、“y”])#矩阵上的一个像素
Box=名称双(“Box”、[“tl”、“br”])#由左上/右下定义的框
def初始化(a):
“”“在每个非零像素处创建一个单独的边界框。”“”
框=[]
行,cols=a.shape
对于范围内的行(行):
对于范围内的列(列):
如果[行,列]!=0:
追加(框(点(行,列),点(行,列)))
返回框
def dist(第1框、第2框):
“”“框之间的距离是从左上到右下,或相反。”“”
x=min(abs(box1.br.x-box2.tl.x),abs(box1.tl.x-box2.br.x))
y=最小值(abs(box1.br.y-box2.tl.y),abs(box1.tl.y-box2.br.y))
返回x,y
def合并(框,i,j):
“”“在索引处弹出框,合并并在末尾放回。”“”
如果i==j:
返回
如果i>=len(框)或j>=len(框):
返回
ii=最小值(i,j)
jj=最大值(i,j)
方框i=方框[ii]
box_j=box[jj]
x、 y=距离(方框i、方框j)
如果x<2或y<2:
tl=点(最小值(box_i.tl.x,box_j.tl.x),最小值(box_i.tl.y,box_j.tl.y))
br=点(最大值(box_i.br.x,box_j.br.x),最大值(box_i.br.y,box_j.br.y))
del box[ii]
删除框[jj-1]
框。追加(框(tl,br))
def集群(a,最大值=100):
""" 
初始化集群。然后循环长度并合并
盒子。如果达到“最大值”或长度没有变化,则断开。
"""
框=初始化(a)
n=长度(框)
k=0
当k

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

虽然前面的响应非常好,但下面介绍了如何使用
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“一”矩阵对连接的组件进行标记,该矩阵定义了邻域。
find_objects
然后确定每个组件的边界框,您可以使用该框将其中的所有内容设置为1。

搜索“连接组件标记”了解如何在具有“背景”值的字段中识别单独的对象。然后找到每个唯一标签的最小和最大行/列。您认为可以将其扩展为非正交边框吗?您可以确定每个对象的最小封闭矩形(即,对于bboxes
中的每个
bbox,然后填充该矩形下的区域,而不是bbox下的区域。据我所知,scipy没有用于此的预制函数。因此,要么您自己编写,要么使用类似OpenCVs
cv.MinareRect
,但在任何情况下,您都必须决定在填充时如何离散化。