Python numpy数组的包围盒

Python numpy数组的包围盒,python,arrays,numpy,transformation,Python,Arrays,Numpy,Transformation,假设您有一个2D numpy数组,其中包含一些随机值和周围的零 “倾斜矩形”示例: 现在我想找到所有非零数据的最小边界矩形。例如: a = np.where(img2 != 0) bbox = img2[np.min(a[0]):np.max(a[0])+1, np.min(a[1]):np.max(a[1])+1] 实现这一结果的最快方法是什么?我确信有更好的方法,因为如果我使用1000x1000个数据集,np.where函数需要相当长的时间 编辑:也应适用于3D…您可以使用np将执行时间大

假设您有一个2D numpy数组,其中包含一些随机值和周围的零

“倾斜矩形”示例:

现在我想找到所有非零数据的最小边界矩形。例如:

a = np.where(img2 != 0)
bbox = img2[np.min(a[0]):np.max(a[0])+1, np.min(a[1]):np.max(a[1])+1]
实现这一结果的最快方法是什么?我确信有更好的方法,因为如果我使用1000x1000个数据集,np.where函数需要相当长的时间


编辑:也应适用于3D…

您可以使用
np将执行时间大致减半。任何
都可以将包含非零值的行和列减少为1D向量,而不是使用
np查找所有非零值的索引。其中

def bbox1(img):
    a = np.where(img != 0)
    bbox = np.min(a[0]), np.max(a[0]), np.min(a[1]), np.max(a[1])
    return bbox

def bbox2(img):
    rows = np.any(img, axis=1)
    cols = np.any(img, axis=0)
    rmin, rmax = np.where(rows)[0][[0, -1]]
    cmin, cmax = np.where(cols)[0][[0, -1]]

    return rmin, rmax, cmin, cmax
一些基准:

%timeit bbox1(img2)
10000 loops, best of 3: 63.5 µs per loop

%timeit bbox2(img2)
10000 loops, best of 3: 37.1 µs per loop
将此方法扩展到3D情况仅涉及沿每对轴执行缩减:

def bbox2_3D(img):

    r = np.any(img, axis=(1, 2))
    c = np.any(img, axis=(0, 2))
    z = np.any(img, axis=(0, 1))

    rmin, rmax = np.where(r)[0][[0, -1]]
    cmin, cmax = np.where(c)[0][[0, -1]]
    zmin, zmax = np.where(z)[0][[0, -1]]

    return rmin, rmax, cmin, cmax, zmin, zmax
通过使用
itertools.combines
对每个轴的唯一组合进行迭代,从而对以下轴执行缩减,可以很容易地将其推广到N维:

import itertools

def bbox2_ND(img):
    N = img.ndim
    out = []
    for ax in itertools.combinations(reversed(range(N)), N - 1):
        nonzero = np.any(img, axis=ax)
        out.extend(np.where(nonzero)[0][[0, -1]])
    return tuple(out)

如果您知道原始边界框角点的坐标、旋转角度和旋转中心,则可以通过计算相应的边界框角点并使用输入坐标点对其进行点处理,直接获得变换边界框角点的坐标:

def bbox_rotate(bbox_in, angle, centre):

    rmin, rmax, cmin, cmax = bbox_in

    # bounding box corners in homogeneous coordinates
    xyz_in = np.array(([[cmin, cmin, cmax, cmax],
                        [rmin, rmax, rmin, rmax],
                        [   1,    1,    1,    1]]))

    # translate centre to origin
    cr, cc = centre
    cent2ori = np.eye(3)
    cent2ori[:2, 2] = -cr, -cc

    # rotate about the origin
    theta = np.deg2rad(angle)
    rmat = np.eye(3)
    rmat[:2, :2] = np.array([[ np.cos(theta),-np.sin(theta)],
                             [ np.sin(theta), np.cos(theta)]])

    # translate from origin back to centre
    ori2cent = np.eye(3)
    ori2cent[:2, 2] = cr, cc

    # combine transformations (rightmost matrix is applied first)
    xyz_out = ori2cent.dot(rmat).dot(cent2ori).dot(xyz_in)

    r, c = xyz_out[:2]

    rmin = int(r.min())
    rmax = int(r.max())
    cmin = int(c.min())
    cmax = int(c.max())

    return rmin, rmax, cmin, cmax
这比使用
np稍微快一点。对于您的小示例数组,任何

%timeit bbox_rotate([25, 75, 25, 75], 45, (50, 50))
10000 loops, best of 3: 33 µs per loop
但是,由于该方法的速度与输入数组的大小无关,因此对于较大的数组,它可以快得多

将变换方法扩展到3D稍微复杂一些,因为旋转现在有三个不同的组件(一个关于x轴,一个关于y轴,一个关于z轴),但基本方法是相同的:

def bbox_rotate_3d(bbox_in, angle_x, angle_y, angle_z, centre):

    rmin, rmax, cmin, cmax, zmin, zmax = bbox_in

    # bounding box corners in homogeneous coordinates
    xyzu_in = np.array(([[cmin, cmin, cmin, cmin, cmax, cmax, cmax, cmax],
                         [rmin, rmin, rmax, rmax, rmin, rmin, rmax, rmax],
                         [zmin, zmax, zmin, zmax, zmin, zmax, zmin, zmax],
                         [   1,    1,    1,    1,    1,    1,    1,    1]]))

    # translate centre to origin
    cr, cc, cz = centre
    cent2ori = np.eye(4)
    cent2ori[:3, 3] = -cr, -cc -cz

    # rotation about the x-axis
    theta = np.deg2rad(angle_x)
    rmat_x = np.eye(4)
    rmat_x[1:3, 1:3] = np.array([[ np.cos(theta),-np.sin(theta)],
                                 [ np.sin(theta), np.cos(theta)]])

    # rotation about the y-axis
    theta = np.deg2rad(angle_y)
    rmat_y = np.eye(4)
    rmat_y[[0, 0, 2, 2], [0, 2, 0, 2]] = (
        np.cos(theta), np.sin(theta), -np.sin(theta), np.cos(theta))

    # rotation about the z-axis
    theta = np.deg2rad(angle_z)
    rmat_z = np.eye(4)
    rmat_z[:2, :2] = np.array([[ np.cos(theta),-np.sin(theta)],
                               [ np.sin(theta), np.cos(theta)]])

    # translate from origin back to centre
    ori2cent = np.eye(4)
    ori2cent[:3, 3] = cr, cc, cz

    # combine transformations (rightmost matrix is applied first)
    tform = ori2cent.dot(rmat_z).dot(rmat_y).dot(rmat_x).dot(cent2ori)
    xyzu_out = tform.dot(xyzu_in)

    r, c, z = xyzu_out[:3]

    rmin = int(r.min())
    rmax = int(r.max())
    cmin = int(c.min())
    cmax = int(c.max())
    zmin = int(z.min())
    zmax = int(z.max())

    return rmin, rmax, cmin, cmax, zmin, zmax

基本上,我刚刚使用旋转矩阵表达式修改了上面的函数-我还没有时间编写测试用例,所以请谨慎使用。

下面是一个计算N维数组边界框的算法

def get_bounding_box(x):
    """ Calculates the bounding box of a ndarray"""
    mask = x == 0
    bbox = []
    all_axis = np.arange(x.ndim)
    for kdim in all_axis:
        nk_dim = np.delete(all_axis, kdim)
        mask_i = mask.all(axis=tuple(nk_dim))
        dmask_i = np.diff(mask_i)
        idx_i = np.nonzero(dmask_i)[0]
        if len(idx_i) != 2:
            raise ValueError('Algorithm failed, {} does not have 2 elements!'.format(idx_i))
        bbox.append(slice(idx_i[0]+1, idx_i[1]+1))
    return bbox
可与2D、3D等阵列一起使用,如下所示:

In [1]: print((img2!=0).astype(int))
   ...: bbox = get_bounding_box(img2)
   ...: print((img2[bbox]!=0).astype(int))
   ...: 
[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 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 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0]
 [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0]
 [0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0]
 [0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 1 1 1 1 0 0 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 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 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 0 0 0 0 0 0]
 [0 0 0 0 0 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 1 1 1 1 1 1 1 1 0 0 0]
 [0 0 1 1 1 1 1 1 1 1 1 1 0 0]
 [0 1 1 1 1 1 1 1 1 1 1 1 1 0]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [0 1 1 1 1 1 1 1 1 1 1 1 1 0]
 [0 0 1 1 1 1 1 1 1 1 1 1 0 0]
 [0 0 0 1 1 1 1 1 1 1 1 0 0 0]
 [0 0 0 0 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 0 0 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 0 0 1 1 0 0 0 0 0 0]]

虽然将
np.diff
np.nonzero
调用替换为一个
np.where
可能更好。

通过将
np.where
替换为
np.argmax
并使用布尔掩码,我能够挤出一点性能

def bbox(img): img = (img > 0) rows = np.any(img, axis=1) cols = np.any(img, axis=0) rmin, rmax = np.argmax(rows), img.shape[0] - 1 - np.argmax(np.flipud(rows)) cmin, cmax = np.argmax(cols), img.shape[1] - 1 - np.argmax(np.flipud(cols)) return rmin, rmax, cmin, cmax def bbox(img): img=(img>0) 行=np.any(img,轴=1) cols=np.any(img,轴=0) rmin,rmax=np.argmax(行),img.shape[0]-1-np.argmax(np.flipud(行)) cmin,cmax=np.argmax(cols),img.shape[1]-1-np.argmax(np.flipud(cols)) 返回rmin、rmax、cmin、cmax
在相同的基准测试中,这比上面的bbox2解决方案快了约10µs。还应该有一种方法,只使用argmax的结果来查找非零的行和列,避免使用
np.any
进行额外的搜索,但这可能需要一些复杂的索引,我无法使用简单的矢量化代码有效地进行索引。

Nice!如何将其扩展到3D案例?我还能用np.any吗?@ali_m:
bbox2
是一个很好的解决方案,特别是如果有大量空行/列,大约比:,快一个数量级,但我猜想,在没有非零行/列的极端情况下,性能会相似或更差。@Benjamin如果该解决方案能够击败
bbox2
,即使对于非常大的全密度阵列,我也会感到惊讶。在该解决方案中,
np.argwhere
的输入和输出数组随着数组的大小二次增加,而
bbox2中
np.where
的输入和输出数组仅线性增加。一种可能使它更快的方法是使用
np.argmax(rows)
rows.size-1-np.argmax(rows[:-1])
而不是
np。其中
获取
行中的第一个和最后一个非零值
cols
。我在这段代码中发现了一个可能的错误。xmin、ymin和zmin应加-1,xmax、ymax和zmax应加+1。我认为ND解决方案需要一些反转,因为itertools。组合确实会产生所需轴顺序的反转。这比ali_m的方法慢,但非常普遍,我喜欢!对我来说效率稍低,因为许多行/列都是零。 def bbox(img): img = (img > 0) rows = np.any(img, axis=1) cols = np.any(img, axis=0) rmin, rmax = np.argmax(rows), img.shape[0] - 1 - np.argmax(np.flipud(rows)) cmin, cmax = np.argmax(cols), img.shape[1] - 1 - np.argmax(np.flipud(cols)) return rmin, rmax, cmin, cmax