6

我有一个图像,存储在一个uint8形状为 s的 numpy 数组中(planes, rows, cols)。我需要将它与存储在掩码中的值进行比较,掩码也是uint8s,形状为(mask_rows, mask_cols)。虽然图像可能非常大,但遮罩通常很小,通常(256, 256)要平铺image。为了简化代码,让我们假设rows = 100 * mask_rowscols = 100 * mask_cols.

我目前处理这个阈值的方式是这样的:

out = image >= np.tile(mask, (image.shape[0], 100, 100))

在被 a扇耳光之前,我可以通过这种方式处理的最大数组MemoryError(3, 11100, 11100). 我想它的方式,以这种方式做事,我有多达三个巨大的数组共存于内存中:image, tiledmask和 my return out。但是平铺的蒙版是相同的小数组,被复制了 10,000 多次。因此,如果我可以节省内存,我将只使用 2/3 的内存,并且应该能够处理大 3/2 的图像,因此大小约为(3, 13600, 13600). 顺便说一句,如果我使用

np.greater_equal(image, (image.shape[0], 100, 100), out=image)

我(失败)尝试利用周期性特性mask来处理更大的数组是mask用周期性线性数组索引:

mask = mask[None, ...]
rows = np.tile(np.arange(mask.shape[1], (100,))).reshape(1, -1, 1)
cols = np.tile(np.arange(mask.shape[2], (100,))).reshape(1, 1, -1)
out = image >= mask[:, rows, cols]

对于小型阵列,它确实产生与另一个阵列相同的结果,尽管速度降低了 20 倍(!!!),但对于较大的尺寸,它非常无法执行。它最终不会MemoryError导致 python 崩溃,即使对于其他方法处理没有问题的值也是如此。

我认为正在发生的事情是 numpy 实际上是在构造(planes, rows, cols)要索引的数组mask,所以不仅没有节省内存,而且由于它是一个int32s 数组,它实际上需要四倍的空间来存储......

关于如何解决这个问题的任何想法?为了省去你的麻烦,在下面找到一些沙盒代码来玩:

import numpy as np

def halftone_1(image, mask) :
    return np.greater_equal(image, np.tile(mask, (image.shape[0], 100, 100)))

def halftone_2(image, mask) :
    mask = mask[None, ...]
    rows = np.tile(np.arange(mask.shape[1]),
                   (100,)).reshape(1, -1, 1)
    cols = np.tile(np.arange(mask.shape[2]),
                   (100,)).reshape(1, 1, -1)
    return np.greater_equal(image, mask[:, rows, cols])

rows, cols, planes = 6000, 6000, 3
image = np.random.randint(-2**31, 2**31 - 1, size=(planes * rows * cols // 4))
image = image.view(dtype='uint8').reshape(planes, rows, cols)
mask = np.random.randint(256,
                         size=(1, rows // 100, cols // 100)).astype('uint8')

#np.all(halftone_1(image, mask) == halftone_2(image, mask))
#halftone_1(image, mask)
#halftone_2(image, mask)

import timeit
print timeit.timeit('halftone_1(image, mask)',
                    'from __main__ import halftone_1, image, mask',
                    number=1)
print timeit.timeit('halftone_2(image, mask)',
                    'from __main__ import halftone_2, image, mask',
                    number=1)
4

1 回答 1

6

我几乎会向您指出滚动窗口类型的技巧,但是对于这个简单的非重叠事物,正常的重塑也可以。(这里的重塑是安全的,numpy永远不会为它们复制)

def halftone_reshape(image, mask):
    # you can make up a nicer reshape code maybe, it is a bit ugly. The
    # rolling window code can do this too (but much more general then reshape).
    new_shape = np.array(zip(image.shape, mask.shape))
    new_shape[:,0] /= new_shape[:,1]
    reshaped_image = image.reshape(new_shape.ravel())

    reshaped_mask = mask[None,:,None,:,None,:]

    # and now they just broadcast:
    result_funny_shaped = reshaped_image >= reshaped_mask

    # And you can just reshape it back:
    return result_funny_shaped.reshape(image.shape)

而且由于时间就是一切(不是真的但是......):

In [172]: %timeit halftone_reshape(image, mask)
1 loops, best of 3: 280 ms per loop

In [173]: %timeit halftone_1(image, mask)
1 loops, best of 3: 354 ms per loop

In [174]: %timeit halftone_2(image, mask)
1 loops, best of 3: 3.1 s per loop
于 2013-01-22T21:28:27.747 回答