从NumPy数组中提取块或补丁

23

我有一个2维的numpy数组,如下所示:

a = np.array([[1,5,9,13],
              [2,6,10,14],
              [3,7,11,15],
              [4,8,12,16]]
我想将它提取为2x2大小的块,不重复元素。 答案应完全相同。可以是3D数组或列表,具有与下面相同的元素顺序:
[[[1,5],
 [2,6]],   

 [[3,7],
 [4,8]],

 [[9,13],
 [10,14]],

 [[11,15],
 [12,16]]]

如何轻松地完成这个任务?

在我的实际问题中,a的大小为(36, 72)。我不能逐个完成它。我想以编程的方式完成它。


@WarrenWeckesser,您能否在此向我展示如何提取补丁,就像我在我的问题中手动提取的那样? - Borys
@WarrenWeckesser 我的意思是我希望将这些补丁作为一个数组的子指标,作为这个问题的答案。 - Borys
啊,所以如果a是你的输入,你可以直接使用a[2*j:2*j+2, 2*k:2*k+2]。这是你的意思吗? - Warren Weckesser
@WarrenWeckesser 是的,我已经编辑了我的问题,描述了元素的结构和顺序是重要的。 - Borys
@TrekkieByDay 是的,这是我完成MBA唯一剩下的问题,哈哈! - Borys
显示剩余10条评论
3个回答

28

使用scikit-image:

import numpy as np
from skimage.util import view_as_blocks

a = np.array([[1,5,9,13],
              [2,6,10,14],
              [3,7,11,15],
              [4,8,12,16]])

print(view_as_blocks(a, (2, 2)))

13
你可以通过结合np.reshapenp.swapaxes来实现,像这样 -
def extract_blocks(a, blocksize, keep_as_view=False):
    M,N = a.shape
    b0, b1 = blocksize
    if keep_as_view==0:
        return a.reshape(M//b0,b0,N//b1,b1).swapaxes(1,2).reshape(-1,b0,b1)
    else:
        return a.reshape(M//b0,b0,N//b1,b1).swapaxes(1,2)

可以看到,使用它有两种方式 - 开启(默认)或关闭 keep_as_view 标志。使用 keep_as_view = False,我们将交换轴进行重塑,并得到最终输出为3D的形状; 而使用 keep_as_view = True,我们将保持其为4D的形状,并且这将是输入数组的视图,因此在运行时几乎免费。我们稍后会用一个样例案例运行来验证。

样例案例

让我们使用一个样例输入数组,如下所示 -

In [94]: a
Out[94]: 
array([[2, 2, 6, 1, 3, 6],
       [1, 0, 1, 0, 0, 3],
       [4, 0, 0, 4, 1, 7],
       [3, 2, 4, 7, 2, 4],
       [8, 0, 7, 3, 4, 6],
       [1, 5, 6, 2, 1, 8]])

现在让我们使用一些块大小进行测试。让我们使用一个块大小为(2,3),并分别打开和关闭视图标志 -

In [95]: extract_blocks(a, (2,3)) # Blocksize : (2,3)
Out[95]: 
array([[[2, 2, 6],
        [1, 0, 1]],

       [[1, 3, 6],
        [0, 0, 3]],

       [[4, 0, 0],
        [3, 2, 4]],

       [[4, 1, 7],
        [7, 2, 4]],

       [[8, 0, 7],
        [1, 5, 6]],

       [[3, 4, 6],
        [2, 1, 8]]])

In [48]: extract_blocks(a, (2,3), keep_as_view=True)
Out[48]: 
array([[[[2, 2, 6],
         [1, 0, 1]],

        [[1, 3, 6],
         [0, 0, 3]]],


       [[[4, 0, 0],
         [3, 2, 4]],

        [[4, 1, 7],
         [7, 2, 4]]],


       [[[8, 0, 7],
         [1, 5, 6]],

        [[3, 4, 6],
         [2, 1, 8]]]])

使用keep_as_view=True验证view

In [20]: np.shares_memory(a, extract_blocks(a, (2,3), keep_as_view=True))
Out[20]: True

让我们来看看在一个大数组上的表现,并验证之前讨论的几乎免费的运行时间声明 -

In [42]: a = np.random.rand(2000,3000)

In [43]: %timeit extract_blocks(a, (2,3), keep_as_view=True)
1000000 loops, best of 3: 801 ns per loop

In [44]: %timeit extract_blocks(a, (2,3), keep_as_view=False)
10 loops, best of 3: 29.1 ms per loop

7

这是一个有点难懂的numpy一行代码,用于生成名为result1的三维数组:

In [60]: x
Out[60]: 
array([[2, 1, 2, 2, 0, 2, 2, 1, 3, 2],
       [3, 1, 2, 1, 0, 1, 2, 3, 1, 0],
       [2, 0, 3, 1, 3, 2, 1, 0, 0, 0],
       [0, 1, 3, 3, 2, 0, 3, 2, 0, 3],
       [0, 1, 0, 3, 1, 3, 0, 0, 0, 2],
       [1, 1, 2, 2, 3, 2, 1, 0, 0, 3],
       [2, 1, 0, 3, 2, 2, 2, 2, 1, 2],
       [0, 3, 3, 3, 1, 0, 2, 0, 2, 1]])

In [61]: result1 = x.reshape(x.shape[0]//2, 2, x.shape[1]//2, 2).swapaxes(1, 2).reshape(-1, 2, 2)

result1 就像一个由 2 维数组组成的一维数组:

In [68]: result1.shape
Out[68]: (20, 2, 2)

In [69]: result1[0]
Out[69]: 
array([[2, 1],
       [3, 1]])

In [70]: result1[1]
Out[70]: 
array([[2, 2],
       [2, 1]])

In [71]: result1[5]
Out[71]: 
array([[2, 0],
       [0, 1]])

In [72]: result1[-1]
Out[72]: 
array([[1, 2],
       [2, 1]])

抱歉,我暂时没有时间详细介绍它的工作原理。也许稍后会有...

这里有一个不那么神秘的版本,它使用了嵌套列表推导式。在这种情况下,result2是一个包含2维numpy数组的Python列表:

In [73]: result2 = [x[2*j:2*j+2, 2*k:2*k+2] for j in range(x.shape[0]//2) for k in range(x.shape[1]//2)]

In [74]: result2[5]
Out[74]: 
array([[2, 0],
       [0, 1]])

In [75]: result2[-1]
Out[75]: 
array([[1, 2],
       [2, 1]])

谢谢,这正是我在寻找的! - Borys
@Warren,你应该喝一杯啤酒。 - Eliethesaiyan
你如何反转这个操作? - Fabian Fritz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接