我有一个2D数组,其中包含从.png
创建的灰度图像,如下所示:
import cv2
img = cv2.imread("./images/test.png")
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
我想要做的是提取一个仅包含包含数据的矩形的子阵列 - 忽略图片周围的全部零。
例如,如果输入为:
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 2 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 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 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
输出:
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
答案 0 :(得分:11)
注意,不是OpenCV
解决方案 - 这通常适用于n维NumPy
或SciPy
数组。
(基于Divakar的答案,扩展到n维)
def crop_new(arr):
mask = arr != 0
n = mask.ndim
dims = range(n)
slices = [None]*n
for i in dims:
mask_i = mask.any(tuple(dims[:i] + dims[i+1:]))
slices[i] = (mask_i.argmax(), len(mask_i) - mask_i[::-1].argmax())
return arr[[slice(*s) for s in slices]]
速度测试:
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]]
E.g。
>>> img = np.array([[ 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, 2, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0]], dtype='uint8')
>>> print crop(img)
[[175 0 0 0 71]
[ 0 12 8 54 0]
[ 0 0 255 0 0]
[ 0 2 0 0 0]]
答案 1 :(得分:7)
我们可以使用argmax
来获取开始,停止行和列索引,正如this post
中详细讨论的那样。我们还打算使用布尔数组/掩码进行有效处理。因此,使用这些工具/想法,我们将有一个矢量化解决方案,如此 -
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
答案 2 :(得分:5)
<强>更新强> 使用opencv函数的这种更简单的方法实际上更快,并且可能比其他答案中提供的其他方法更快。
def crop_fastest(arr):
return cv2.boundingRect(cv2.findNonZero(arr))
返回边界框的x,y,宽度和高度。在我的桌面电脑上查找旧代码1000 loops, best of 3: 562 µs per loop
,同时使用此新代码10000 loops, best of 3: 179 µs per loop
。
又一次更新
正如Chupo_cro指出的那样,简单地调用cv2.boundingRect(arr)
会返回相同的结果,而这似乎是由于the code in this function内部进行了转换。
上一个回答
可能有更快的方法。这个更简单的功能稍快一些。
from scipy import ndimage
def crop_fast(arr):
slice_x, slice_y = ndimage.find_objects(arr>0)[0]
return arr[slice_x, slice_y]
比较droooze代码和此代码的速度,
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 loops, best of 3: 1.62 ms per loop
,%timeit crop_fast(arr)
在我的笔记本电脑上返回1000 loops, best of 3: 979 µs per loop
。也就是说,crop_fast()
需要crop()
大约60%的时间。