python如何用零填充numpy数组

Posted

技术标签:

【中文标题】python如何用零填充numpy数组【英文标题】:python how to pad numpy array with zeros 【发布时间】:2016-06-15 13:07:48 【问题描述】:

我想知道如何使用带有 numpy 版本 1.5.0 的 python 2.6.6 用零填充 2D numpy 数组。但这些都是我的局限。因此我不能使用np.pad。例如,我想用零填充a,使其形状与b 匹配。我想这样做的原因是我可以这样做:

b-a

这样

>>> a
array([[ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.]])
>>> b
array([[ 3.,  3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.,  3.]])
>>> c
array([[1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 0],
       [0, 0, 0, 0, 0, 0]])

我能想到的唯一方法是追加,但这看起来很丑陋。是否有更清洁的解决方案可能使用b.shape

编辑, 感谢 MSeiferts 的回答。我不得不把它清理一下,这就是我得到的:

def pad(array, reference_shape, offsets):
    """
    array: Array to be padded
    reference_shape: tuple of size of ndarray to create
    offsets: list of offsets (number of elements must be equal to the dimension of the array)
    will throw a ValueError if offsets is too big and the reference_shape cannot handle the offsets
    """

    # Create an array of zeros with the reference shape
    result = np.zeros(reference_shape)
    # Create a list of slices from offset to offset + shape in each dimension
    insertHere = [slice(offsets[dim], offsets[dim] + array.shape[dim]) for dim in range(array.ndim)]
    # Insert the array in the result at the specified offsets
    result[insertHere] = array
    return result

【问题讨论】:

【参考方案1】:

NumPy 1.7.0(添加 numpy.pad 时)现在已经很老了(它于 2013 年发布)所以即使问题要求一种方法不使用该功能我认为它可以了解如何使用numpy.pad 来实现这一点很有用。

其实很简单:

>>> import numpy as np
>>> a = np.array([[ 1.,  1.,  1.,  1.,  1.],
...               [ 1.,  1.,  1.,  1.,  1.],
...               [ 1.,  1.,  1.,  1.,  1.]])
>>> np.pad(a, [(0, 1), (0, 1)], mode='constant')
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

在这种情况下,我使用 0mode='constant' 的默认值。但也可以通过显式传递来指定:

>>> np.pad(a, [(0, 1), (0, 1)], mode='constant', constant_values=0)
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

以防第二个参数 ([(0, 1), (0, 1)]) 看起来令人困惑:每个列表项(在本例中为元组)对应一个维度,其中的项表示填充 before(第一个元素)和 after(第二个元素)。所以:

[(0, 1), (0, 1)]
         ^^^^^^------ padding for second dimension
 ^^^^^^-------------- padding for first dimension

  ^------------------ no padding at the beginning of the first axis
     ^--------------- pad with one "value" at the end of the first axis.

在这种情况下,第一个轴和第二个轴的填充是相同的,所以也可以只传入 2 元组:

>>> np.pad(a, (0, 1), mode='constant')
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

如果之前和之后的填充相同,甚至可以省略元组(但在这种情况下不适用):

>>> np.pad(a, 1, mode='constant')
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.]])

或者如果轴的前后填充相同但不同,您也可以省略内部元组中的第二个参数:

>>> np.pad(a, [(1, ), (2, )], mode='constant')
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]])

但是我倾向于总是使用显式的,因为它很容易出错(当 NumPys 的期望与你的意图不同时):

>>> np.pad(a, [1, 2], mode='constant')
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]])

这里 NumPy 认为您想在每个轴之前用 1 个元素和在每个轴之后用 2 个元素填充所有轴!即使您打算在轴 1 中填充 1 个元素,在轴 2 中填充 2 个元素。

我使用元组列表作为填充,注意这只是“我的惯例”,您也可以使用列表列表或元组元组,甚至数组元组。 NumPy 只检查参数的长度(或者如果它没有长度)和每个项目的长度(或者如果它有长度)!

【讨论】:

这真的很好解释。比原始文档好得多。谢谢。 mode='constant' 是合理的默认值,因此无需任何可选关键字即可实现用零填充,从而使代码更具可读性。 我怎样才能只向 3D numpy 数组的第三维添加填充? @RamshaSiddiqui 您可以将 0 用于不应填充的尺寸。【参考方案2】:

非常简单,您可以使用参考形状创建一个包含零的数组:

result = np.zeros(b.shape)
# actually you can also use result = np.zeros_like(b) 
# but that also copies the dtype not only the shape

然后在需要的地方插入数组:

result[:a.shape[0],:a.shape[1]] = a

瞧,你已经填充了它:

print(result)
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

如果你定义左上角元素应该插入的位置,你也可以让它更通用

result = np.zeros_like(b)
x_offset = 1  # 0 would be what you wanted
y_offset = 1  # 0 in your case
result[x_offset:a.shape[0]+x_offset,y_offset:a.shape[1]+y_offset] = a
result

array([[ 0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.],
       [ 0.,  1.,  1.,  1.,  1.,  1.],
       [ 0.,  1.,  1.,  1.,  1.,  1.]])

但请注意不要有大于允许的偏移量。以x_offset = 2 为例,这将失败。


如果您有任意数量的维度,您可以定义一个切片列表来插入原始数组。我发现玩一下很有趣,并创建了一个填充函数,只要数组和引用具有相同的维数并且偏移量不太大,就可以填充(带偏移量)任意形状的数组。

def pad(array, reference, offsets):
    """
    array: Array to be padded
    reference: Reference array with the desired shape
    offsets: list of offsets (number of elements must be equal to the dimension of the array)
    """
    # Create an array of zeros with the reference shape
    result = np.zeros(reference.shape)
    # Create a list of slices from offset to offset + shape in each dimension
    insertHere = [slice(offset[dim], offset[dim] + array.shape[dim]) for dim in range(a.ndim)]
    # Insert the array in the result at the specified offsets
    result[insertHere] = a
    return result

还有一些测试用例:

import numpy as np

# 1 Dimension
a = np.ones(2)
b = np.ones(5)
offset = [3]
pad(a, b, offset)

# 3 Dimensions

a = np.ones((3,3,3))
b = np.ones((5,4,3))
offset = [1,0,0]
pad(a, b, offset)

【讨论】:

只是总结一下我需要的情况:如果在原点插入,任意尺寸:padded = np.zeros(b.shape)padded[tuple(slice(0,n) for n in a.shape)] = a【参考方案3】:

我了解您的主要问题是您需要计算 d=b-a 但您的数组大小不同。不需要中间填充的c

你可以不用填充来解决这个问题:

import numpy as np

a = np.array([[ 1.,  1.,  1.,  1.,  1.],
              [ 1.,  1.,  1.,  1.,  1.],
              [ 1.,  1.,  1.,  1.,  1.]])

b = np.array([[ 3.,  3.,  3.,  3.,  3.,  3.],
              [ 3.,  3.,  3.,  3.,  3.,  3.],
              [ 3.,  3.,  3.,  3.,  3.,  3.],
              [ 3.,  3.,  3.,  3.,  3.,  3.]])

d = b.copy()
d[:a.shape[0],:a.shape[1]] -=  a

print d

输出:

[[ 2.  2.  2.  2.  2.  3.]
 [ 2.  2.  2.  2.  2.  3.]
 [ 2.  2.  2.  2.  2.  3.]
 [ 3.  3.  3.  3.  3.  3.]]

【讨论】:

没错,对于他的具体情况,他不一定需要填充,但这是填充和您的方法等价的极少数算术运算之一。不过很好的答案! 不仅如此。这也可能比零填充更节省内存。【参考方案4】:

如果您需要将 1 的栅栏添加到数组中:

>>> mat = np.zeros((4,4), np.int32)
>>> mat
array([[0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0]])
>>> mat[0,:] = mat[:,0] = mat[:,-1] =  mat[-1,:] = 1
>>> mat
array([[1, 1, 1, 1],
       [1, 0, 0, 1],
       [1, 0, 0, 1],
       [1, 1, 1, 1]])

【讨论】:

【参考方案5】:

我知道我对此有点晚了,但如果您想执行相对填充(也称为边缘填充),您可以通过以下方式实现它。请注意,赋值的第一个实例会导致零填充,因此您可以将其用于零填充和相对填充(这是您将原始数组的边缘值复制到填充数组的位置)。

def replicate_padding(arr):
    """Perform replicate padding on a numpy array."""
    new_pad_shape = tuple(np.array(arr.shape) + 2) # 2 indicates the width + height to change, a (512, 512) image --> (514, 514) padded image.
    padded_array = np.zeros(new_pad_shape) #create an array of zeros with new dimensions
    
    # perform replication
    padded_array[1:-1,1:-1] = arr        # result will be zero-pad
    padded_array[0,1:-1] = arr[0]        # perform edge pad for top row
    padded_array[-1, 1:-1] = arr[-1]     # edge pad for bottom row
    padded_array.T[0, 1:-1] = arr.T[0]   # edge pad for first column
    padded_array.T[-1, 1:-1] = arr.T[-1] # edge pad for last column
    
    #at this point, all values except for the 4 corners should have been replicated
    padded_array[0][0] = arr[0][0]     # top left corner
    padded_array[-1][0] = arr[-1][0]   # bottom left corner
    padded_array[0][-1] = arr[0][-1]   # top right corner 
    padded_array[-1][-1] = arr[-1][-1] # bottom right corner

    return padded_array

复杂性分析:

对此的最佳解决方案是 numpy 的 pad 方法。 平均运行 5 次后,具有相对填充的 np.pad 仅比上面定义的函数好 8%。这表明这是相对和零填充填充的最佳方法。


#My method, replicate_padding
start = time.time()
padded = replicate_padding(input_image)
end = time.time()
delta0 = end - start

#np.pad with edge padding
start = time.time()
padded = np.pad(input_image, 1, mode='edge')
end = time.time()
delta = end - start


print(delta0) # np Output: 0.0008790493011474609 
print(delta)  # My Output: 0.0008130073547363281
print(100*((delta0-delta)/delta)) # Percent difference: 8.12316715542522%

【讨论】:

【参考方案6】:

Tensorflow 还实现了调整图像大小/填充图像的功能tf.image.padtf.pad。

padded_image = tf.image.pad_to_bounding_box(image, top_padding, left_padding, target_height, target_width)

padded_image = tf.pad(image, paddings, "CONSTANT")

这些函数的工作方式与 tensorflow 的其他输入管道特性一样,并且在机器学习应用程序中工作得更好。

【讨论】:

以上是关于python如何用零填充numpy数组的主要内容,如果未能解决你的问题,请参考以下文章

如何用另一个数组创建或填充一个 numpy 数组?

如何用前导零填充数组?

如何用零填充数组?

零填充numpy数组

如何从填充零的 3 维 numpy 数组创建 4 维 numpy 数组?

2 如何用Python进行数据计算