Numpy - 将重叠的3D子阵列创建为具有内存效率的向量

时间:2012-03-01 14:43:36

标签: python memory-management image-processing tree numpy

我正在尝试从更大的3D阵列(用于基于补丁的分割)创建所有相同大小的重叠子阵列的列表,其中每个子阵列需要被展平(作为1D向量),因此我可以使用sklearn.neighbours.BallTree中的球树。

例如,给定100x100x100图像,如果我将其分解为5x5x5重叠补丁(子阵列),我将得到96x96x96 = 884,736。

但是,如果没有numpy为每个flattened / vectorized子阵列分配更多内存,我还没有找到任何方法。这似乎是因为每个子阵列在内存中都不是连续的。

e.g。对于100x100x100图像,如果我想要每个5x5x5补丁作为1D向量(长度为125),numpy决定在内存中为所有884,736个分配一个全新的阵列,然后变得相当大,特别是如果我想要使用超过单个100x100x100图像!

我欢迎任何解决方案来克服python / numpy中的内存挑战。我正在考虑创建numpy.ndarray对象的子类,该对象在较大的图像中存储指向补丁位置的指针,但仅在调用时将数据作为1D numpy数组返回(然后在未使用时再次删除)但我没有看到有关子类化ndarray对象的足够细节。如果唯一的解决方案是用C / C ++实现所有内容,我会非常失望。感谢您提供的任何帮助,谢谢!

1 个答案:

答案 0 :(得分:1)

根据您的问题,您可能已经知道所有这些。但是,我发布这个“答案”更像是对问题的讨论,因为许多人可能没有意识到这些问题....

但是,如果您不是,则可以从96x96x96x5x5x5图像中创建100x100x100数组,该图像充当5x5x5移动窗口,而不会分配任何额外的内存。

但是,因为每个维度只能有一个步幅,所以无法在不制作副本的情况下将其重新整形为96x96x96x125数组。

无论如何,这是一个例子(基本上是straight from one of my previous answers):

import numpy as np

def rolling_window_lastaxis(a, window):
    """Directly taken from Erik Rigtorp's post to numpy-discussion.
    <http://www.mail-archive.com/numpy-discussion@scipy.org/msg29450.html>"""
    if window < 1:
       raise ValueError, "`window` must be at least 1."
    if window > a.shape[-1]:
       raise ValueError, "`window` is too long."
    shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
    strides = a.strides + (a.strides[-1],)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

def rolling_window(a, window):
    """Takes a numpy array *a* and a sequence of (or single) *window* lengths
    and returns a view of *a* that represents a moving window."""
    if not hasattr(window, '__iter__'):
        return rolling_window_lastaxis(a, window)
    for i, win in enumerate(window):
        if win > 1:
            a = a.swapaxes(i, -1)
            a = rolling_window_lastaxis(a, win)
            a = a.swapaxes(-2, i)
    return a

x = np.zeros((100,100,100), dtype=np.uint8)
y = rolling_window(x, (5,5,5))
print 'Now *y* will be a 96x96x96x5x5x5 array...'
print y.shape
print 'Representing a "rolling window" into *x*...'
y[0,0,0,...] = 1
y[1,1,0,...] = 2
print x[:10,:10,0] # Note that *x* and *y* share the same memory!

这会产生:

Now *y* will be a 96x96x96x5x5x5 array...
(96, 96, 96, 5, 5, 5)
Representing a "rolling window" into *x*...
[[1 1 1 1 1 0 0 0 0 0]
 [1 2 2 2 2 2 0 0 0 0]
 [1 2 2 2 2 2 0 0 0 0]
 [1 2 2 2 2 2 0 0 0 0]
 [1 2 2 2 2 2 0 0 0 0]
 [0 2 2 2 2 2 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 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]]

但是,正如您已经注意到的那样,我们无法在不创建副本的情况下将其重新整理为96x96x96x125y.shape = (96,96,96,-1)会引发错误,z = y.reshape((96,96,96,-1))会有效,但会返回副本。

(相关文档位于numpy.reshape,如果这看起来很混乱。基本上reshape会尽可能避免复制,如果不是则会返回副本,而设置shape属性则会无法复制时引发错误。)

但是,即使您构建了一个更高效的数组容器,sklearn.neighbors.BallTree几乎肯定会制作临时中间副本。

您提到您正在进行图像分割。为什么不考虑一个比你似乎尝试的相当“蛮力”更有效的算法呢? (或者,如果这不可行,请向我们提供一些有关原因的详细信息......也许有人会有更好的想法?)