2d numpy数组,使每个值都是以其为中心的3x3平方之和

时间:2019-07-06 00:00:33

标签: python numpy

我有一个正方形2D numpy数组A和一个形状相同的零数组B

对于(i, j)中的每个索引A,除了第一行和最后一行和最后一列外,我都想将B[i, j]的值分配给np.sum(A[i - 1:i + 2, j - 1:j + 2]

示例:

A =
array([[0, 0, 0, 0, 0],
       [0, 1, 0, 1, 0],
       [0, 1, 1, 0, 0],
       [0, 1, 0, 1, 0],
       [0, 0, 0, 0, 0])

B =
array([[0, 0, 0, 0, 0],
       [0, 3, 4, 2, 0],
       [0, 4, 6, 3, 0],
       [0, 3, 4, 2, 0],
       [0, 0, 0, 0, 0])

是否有一种有效的方法来做到这一点?还是应该简单地使用for循环?

3 个答案:

答案 0 :(得分:6)

np.lib.stride_tricks.as_strided有一种聪明的方法(请阅读“ borderline smartass”)。 as_strided允许您在缓冲区中创建视图,以通过向视图添加另一个维度来模拟窗口。例如,如果您有一个一维数组,例如

>>> x = np.arange(10)
>>> np.lib.stride_tricks.as_strided(x, shape=(3, x.shape[0] - 2), strides=x.strides * 2)
array([[0, 1, 2, 3, 4, 5, 6, 7],
       [1, 2, 3, 4, 5, 6, 7, 8],
       [2, 3, 4, 5, 6, 7, 8, 9]])

希望很明显,您可以沿axis=0求和以获得每个3窗口的总和。没有理由不能将其扩展到两个或多个维度。我以建议解决方案的方式编写了上一个示例的形状和索引:

A = np.array([[0, 0, 0, 0, 0],
              [0, 1, 0, 1, 0],
              [0, 1, 1, 0, 0],
              [0, 1, 0, 1, 0],
              [0, 0, 0, 0, 0]])
view = np.lib.stride_tricks.as_strided(A,
    shape=(3, 3, A.shape[0] - 2, A.shape[1] - 2),
    strides=A.strides * 2
)
B[1:-1, 1:-1] = view.sum(axis=(0, 1))

自v1.7.0起,np.sum支持同时沿多个轴进行求和。对于较旧的numpy版本,只需沿axis=0重复(两次)即可。

B边缘的填充操作留给读者练习(因为它实际上并不是问题的一部分)。

顺便说一句,如果您愿意的话,这里的解决方案是单线的。就我个人而言,我认为使用as_strided进行的任何操作已经足够难以辨认,并且不需要进一步混淆。我不确定for循环是否会严重影响性能,以至于无法证明该方法的正确性。

为便于将来参考,这是一个通用的窗口制作功能,可用于解决此类问题:

def window_view(a, window=3):
    """
    Create a (read-only) view into `a` that defines window dimensions.

    The first ``a.ndim`` dimensions of the returned view will be sized according to `window`.
    The remaining ``a.ndim`` dimensions will be the original dimensions of `a`, truncated by `window - 1`.

    The result can be post-precessed by reducing the leading dimensions. For example, a multi-dimensional moving average could look something like ::


         window_view(a, window).sum(axis=tuple(range(a.ndim))) / window**a.ndim

    If the window size were different for each dimension (`window` were a sequence rather than a scalar), the normalization would be ``np.prod(window)`` instead of ``window**a.ndim``.

    Parameters
    -----------
    a : array-like
        The array to window into. Due to numpy dimension constraints, can not have > 16 dims.
    window :
        Either a scalar indicating the window size for all dimensions, or a sequence of length `a.ndim` providing one size for each dimension.

    Return
    ------
    view : numpy.ndarray
         A read-only view into `a` whose leading dimensions represent the requested windows into `a`.
         ``view.ndim == 2 * a.ndim``.
    """
    a = np.array(a, copy=False, subok=True)
    window = np.array(window, copy=False, subok=False, dtype=np.int)
    if window.size == 1:
        window = np.full(a.ndim, window)
    elif window.size == a.ndim:
        window = window.ravel()
    else:
        raise ValueError('Number of window sizes must match number of array dimensions')

    shape = np.concatenate((window, a.shape))
    shape[a.ndim:] -= window - 1
    strides = a.strides * 2

    return np.lib.stride_tricks.as_strided(a, shake=shape, strides=strides)

答案 1 :(得分:2)

我发现没有“简单”的方法可以做到这一点。但是,有两种方法:


  • 仍然涉及一个for循环
# Basically, get the sum for each location and then pad the result with 0's
B = [[np.sum(A[j-1:j+2,i-1:i+2]) for i in range(1,len(A)-1)] for j in range(1,len(A[0])-1)]
B = np.pad(B, ((1,1)), "constant", constant_values=(0))

  • 更长,但没有for循环(在大型数组上这将效率更高):
# Roll basically slides the array in the desired direction
A_right = np.roll(A, -1, 1)
A_left = np.roll(A, 1, 1)
A_top = np.roll(A, 1, 0)
A_bottom = np.roll(A, -1, 0)
A_bot_right = np.roll(A_bottom, -1, 1)
A_bot_left = np.roll(A_bottom, 1, 1)
A_top_right = np.roll(A_top, -1, 1)
A_top_left = np.roll(A_top, 1, 1)

# After doing that, you can just add all those arrays and these operations
# are handled better directly by numpy compared to when you use for loops
B = A_right + A_left + A_top + A_bottom + A_top_left + A_top_right + A_bot_left + A_bot_right + A

# You can then return the edges to 0 or whatever you like
B[0:len(B),0] = 0
B[0:len(B),len(B[0])-1] = 0
B[0,0:len(B)] = 0
B[len(B[0])-1,0:len(B)] = 0

答案 2 :(得分:0)

您可以对构成一个块的9个数组求和,每个数组偏移1 w.r.t.任一维度中的前一个。使用切片符号可以一次对整个数组A进行此操作:

B = np.zeros_like(A)
B[1:-1, 1:-1] = sum(A[i:A.shape[0]-2+i, j:A.shape[1]-2+j]
                    for i in range(0, 3) for j in range(0, 3))

任意矩形窗口的常规版本

def sliding_window_sum(a, size):
    """Compute the sum of elements of a rectangular sliding window over the input array.

    Parameters
    ----------
    a : array_like
        Two-dimensional input array.
    size : int or tuple of int
        The size of the window in row and column dimension; if int then a quadratic window is used.

    Returns
    -------
    array
        Shape is ``(a.shape[0] - size[0] + 1, a.shape[1] - size[1] + 1)``.
    """
    if isinstance(size, int):
        size = (size, size)
    m = a.shape[0] - size[0] + 1
    n = a.shape[1] - size[1] + 1
    return sum(A[i:m+i, j:n+j] for i in range(0, size[0]) for j in range(0, size[1]))