Python 从表示图像的数组中排除周围零的最快方法是什么?

Python 从表示图像的数组中排除周围零的最快方法是什么?,python,arrays,numpy,image-processing,Python,Arrays,Numpy,Image Processing,我有一个二维数组,其中包含从.png创建的灰度图像,如下所示: 导入cv2 img=cv2.imread(“./images/test.png”) img=cv2.cvt颜色(img,cv2.COLOR\u bgr2灰色) 我想做的是提取一个子数组,该子数组只包含包含数据的矩形-忽略图片周围的所有零 例如,如果输入为: 0 0 0 0 0 0 0 0 0 0 0 175 0 0 0 71 0 0 0 0 12 8 54 0

我有一个二维数组,其中包含从
.png
创建的灰度图像,如下所示:

导入cv2
img=cv2.imread(“./images/test.png”)
img=cv2.cvt颜色(img,cv2.COLOR\u bgr2灰色)
我想做的是提取一个子数组,该子数组只包含包含数据的矩形-忽略图片周围的所有零

例如,如果输入为:

0
0   0   0   0   0   0   0   0
0   0 175   0   0   0  71   0
0   0   0  12   8  54   0   0
0   0   0   0 255   0   0   0
0   0   0   2   0   0   0   0
0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0
那么输出应该是:

175071
0  12   8  54   0
0   0 255   0   0
0   2   0   0   0
我可以向前迭代行以找到第一个非零行,然后向后迭代行以找到最后一个非零行,然后对列重复相同的操作,然后使用该数据提取子数组,但我确信有更合适的方法来执行相同或相同的操作甚至可能有一个NumPy函数是为这样的目的而设计的

如果让我在最短代码和最快执行之间进行选择,我会对最快的代码执行更感兴趣

编辑:
我没有包括最好的例子,因为中间可能有零行/列,如:

输入:

0
0   0   0   0   0   0   0   0
0   0 175   0   0   0  71   0
0   0   0  12   8  54   0   0
0   0   0   0 255   0   0   0
0   0   0   0   0   0   0   0
0   0   0   2   0   0   0   0
0   0   0   0   0   0   0   0
输出:

175071
0  12   8  54   0
0   0 255   0   0
0   0   0   0   0
0   2   0   0   0
注意,不是
OpenCV
解决方案-这通常适用于n维
NumPy
SciPy
数组

(基于Divakar的回答,扩展到n维)

速度测试:

In [42]: np.random.seed(0)

In [43]: a = np.zeros((30, 30, 30, 20),dtype=np.uint8)

In [44]: a[2:-2, 2:-2, 2:-2, 2:-2] = np.random.randint(0,255,(26,26,26,16),dtype
=np.uint8)

In [45]: timeit crop(a) # Old solution
1 loop, best of 3: 181 ms per loop

In [46]: timeit crop_fast(a) # modified fireant's solution for n-dimensions
100 loops, best of 3: 5 ms per loop

In [48]: timeit crop_new(a) # modified Divakar's solution for n-dimensions
100 loops, best of 3: 1.91 ms per loop

旧解决方案

您可以使用
np.nonzero
获取数组的索引。该数组的边界框完全包含在索引的最大值和最小值中

def _get_slice_bbox(arr):
    nonzero = np.nonzero(arr)
    return [(min(a), max(a)+1) for a in nonzero]

def crop(arr):
    slice_bbox = _get_slice_bbox(arr)
    return arr[[slice(*a) for a in slice_bbox]]
例如

更新 这个使用opencv函数的简单方法实际上更快,而且可能比这里其他答案中给出的其他方法更快

def crop_fastest(arr):
    return cv2.boundingRect(cv2.findNonZero(arr))
这将返回边界框的x、y、宽度和高度。在我的台式电脑上,对于旧代码
1000个循环,每个循环的最佳值为3:562µs
,而对于新代码
10000个循环,每个循环的最佳值为3:179µs

又一次更新

In [105]: # Setup for 1000x1000 2D image and 100 offsetted boundaries all across
     ...: np.random.seed(0)
     ...: a = np.zeros((1000,1000),dtype=np.uint8)
     ...: a[100:-100,100:-100] = np.random.randint(0,255,(800,800),dtype=np.uint8)

In [106]: %timeit crop_fast(a) # @fireant's soln
     ...: %timeit crop(a)      # @droooze's soln
     ...: %timeit remove_black_border(a) # from this post
100 loops, best of 3: 4.58 ms per loop
10 loops, best of 3: 127 ms per loop
10000 loops, best of 3: 155 µs per loop
正如Chupo_cro指出的,简单地调用
cv2.boundingRect(arr)
会返回相同的结果,这似乎是因为它在内部进行转换

先前的答案

可能有更快的方法来实现这一点。这个简单的函数稍微快一点

from scipy import ndimage
def crop_fast(arr):
    slice_x, slice_y = ndimage.find_objects(arr>0)[0]
    return arr[slice_x, slice_y]
要比较Drooze的代码和这个代码的速度

arr = np.zeros(shape=(50000,6), dtype=np.uint8)
arr[2] = [9,8,0,0,1,1]
arr[1] = [0,3,0,0,1,1]
然后
%timeit-crop(arr)
在我的笔记本电脑上返回
1000个循环,每个循环最好3:1.62毫秒
%timeit-crop\u-fast(arr)
在我的笔记本电脑上返回
1000个循环,每个循环最好3:979µs。也就是说,
crop\u fast()
大约花费了
crop()

我们可以用来获取开始、停止行和列索引的60%的时间,如中的一些详细讨论。我们还打算使用布尔数组/掩码进行高效处理。因此,使用这些工具/想法,我们将得到一个矢量化的解决方案,如下所示-

def remove_black_border(a): 
    # Mask of non-zeros
    mask = a!=0 # Use a >tolerance for a tolerance defining black border

    # Mask of non-zero rows and columns
    mask_row = mask.any(1)
    mask_col = mask.any(0)

    # First, last indices among the non-zero rows
    sr0,sr1 = mask_row.argmax(), len(mask_row) - mask_row[::-1].argmax()

    # First, last indices among the non-zero columns
    sc0,sc1 = mask_col.argmax(), len(mask_col) - mask_col[::-1].argmax()

    # Finally slice along the rows & cols with the start and stop indices to get 
    # cropped image. Slicing helps for an efficient operation.
    return a[sr0:sr1, sc0:sc1]
样本运行-

In [56]: a # Input image array
Out[56]: 
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, 175,   0,   0,   0,  71],
       [  0,   0,   0,   0,  12,   8,  54,   0],
       [  0,   0,   0,   0,   0, 255,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   2,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0]])

In [57]: out = remove_black_border(a)

In [58]: out
Out[58]: 
array([[175,   0,   0,   0,  71],
       [  0,  12,   8,  54,   0],
       [  0,   0, 255,   0,   0],
       [  0,   0,   0,   0,   0],
       [  0,   2,   0,   0,   0]])
内存效率:

输出是输入数组的视图,因此不需要额外的内存或复制,这有助于提高内存效率。让我们验证视图部分-

In [59]: np.shares_memory(a, out)
Out[59]: True
在更大的图像上使用所有建议方法的计时

In [105]: # Setup for 1000x1000 2D image and 100 offsetted boundaries all across
     ...: np.random.seed(0)
     ...: a = np.zeros((1000,1000),dtype=np.uint8)
     ...: a[100:-100,100:-100] = np.random.randint(0,255,(800,800),dtype=np.uint8)

In [106]: %timeit crop_fast(a) # @fireant's soln
     ...: %timeit crop(a)      # @droooze's soln
     ...: %timeit remove_black_border(a) # from this post
100 loops, best of 3: 4.58 ms per loop
10 loops, best of 3: 127 ms per loop
10000 loops, best of 3: 155 µs per loop

如果我要在最短代码和最快执行之间进行选择,我会对最快的代码更感兴趣。
所说的
最快代码
你指的是最快的执行,对吗?是的,我刚刚更正了措辞。你可以不迭代地完成这项工作,首先屏蔽
>0
,然后在每个轴上请求最小和最大屏蔽索引,然后在上面切片。我不认为在这一点上它会更快或者更容易阅读,但是……可能值得一试。@abarnet:你为什么不写下答案,这样我就可以接受它呢?看起来Drooze确实写了一个正确的答案,但随后他删除了它:-/@Chupo_cro抱歉,我想更改一些内容,使其更通用。注意,如果要扩展此功能以支持彩色图像,则必须先确定颜色通道的位置,然后进行适当的切片。到目前为止,此解决方案不完全支持彩色图像。这正是我需要的,谢谢!事实上,我甚至可以使用每像素1位的图像。我已经使用opencv方法用一个更简单的函数更新了我的答案,这可能和基于numpy的方法一样快或更快。谢谢你指出了一个更快的方法。我根据您的方法添加了一个处理n维的新解决方案,它应该能够处理图像堆栈。当我上次看到答案时,fireant的答案是最快的,我只是想将其标记为解决方案,但现在看来您的解决方案是最快的,drooze根据您的答案用解决方案更新了他的答案。现在我再也不确定该把哪个答案标记为解决方案:-/此外,我现在才意识到,如果有
有关提取子阵列坐标的信息,我的程序可能会受益。我不确定是否要发布一个带有附加要求的新问题(提取子数组
,并返回其在原始数组中的坐标
):-/@Chupo_cro我想发布一个
In [105]: # Setup for 1000x1000 2D image and 100 offsetted boundaries all across
     ...: np.random.seed(0)
     ...: a = np.zeros((1000,1000),dtype=np.uint8)
     ...: a[100:-100,100:-100] = np.random.randint(0,255,(800,800),dtype=np.uint8)

In [106]: %timeit crop_fast(a) # @fireant's soln
     ...: %timeit crop(a)      # @droooze's soln
     ...: %timeit remove_black_border(a) # from this post
100 loops, best of 3: 4.58 ms per loop
10 loops, best of 3: 127 ms per loop
10000 loops, best of 3: 155 µs per loop