在二维数组中填充边界框
Fill Bounding Boxes in 2D array
我有一个 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.]])
我怎样才能轻松做到?另外,如果存在其他 no.s(例如 2,3 等)但我想忽略它们并且这些组大多是 2,我该怎么做。
有一个解决方案,但它有点老套,我不会为你编程。
OpenCV - 图像处理库,具有查找矩形轮廓的算法 -> 直线或旋转。你可能想要做的是将你的数组转换为二维灰度图像,找到轮廓并在轮廓内写入你的 1s。
检查这张图片 - 它来自 Opencv DOC - 7.a - https://docs.opencv.org/3.4/dd/d49/tutorial_py_contour_features.html
您会对绿线内的所有内容感兴趣。
老实说,我觉得比为边界框编写一些算法要容易得多
备注
当然你真的不需要做图像的东西,但我认为使用opencv的边界框算法就足够了(countours)
我们 skimage.measure
可以让组件标签变得更轻松。我们可以使用 skimage.measure.label
to label the different components in the array, and 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.]])
这是一个有趣的问题。二维卷积是一种自然的方法。但是,如果输入矩阵是稀疏的(如您的示例所示),这可能会很昂贵。对于稀疏矩阵,另一种方法是使用聚类算法。这仅从输入框 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
此 returns 由角点(左上角和右下角)定义的框列表。这里 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-"ones" 矩阵进行连通分量标记。 find_objects
然后确定每个组件的边界框,然后您可以使用它来将所有内容设置为 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.]])
我怎样才能轻松做到?另外,如果存在其他 no.s(例如 2,3 等)但我想忽略它们并且这些组大多是 2,我该怎么做。
有一个解决方案,但它有点老套,我不会为你编程。
OpenCV - 图像处理库,具有查找矩形轮廓的算法 -> 直线或旋转。你可能想要做的是将你的数组转换为二维灰度图像,找到轮廓并在轮廓内写入你的 1s。
检查这张图片 - 它来自 Opencv DOC - 7.a - https://docs.opencv.org/3.4/dd/d49/tutorial_py_contour_features.html
您会对绿线内的所有内容感兴趣。
老实说,我觉得比为边界框编写一些算法要容易得多
备注
当然你真的不需要做图像的东西,但我认为使用opencv的边界框算法就足够了(countours)
我们 skimage.measure
可以让组件标签变得更轻松。我们可以使用 skimage.measure.label
to label the different components in the array, and 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.]])
这是一个有趣的问题。二维卷积是一种自然的方法。但是,如果输入矩阵是稀疏的(如您的示例所示),这可能会很昂贵。对于稀疏矩阵,另一种方法是使用聚类算法。这仅从输入框 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
此 returns 由角点(左上角和右下角)定义的框列表。这里 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-"ones" 矩阵进行连通分量标记。 find_objects
然后确定每个组件的边界框,然后您可以使用它来将所有内容设置为 1。