Numpy视图重塑无需复制(二维移动/滑动窗口,步幅,遮罩内存结构)

8 投票
1 回答
2888 浏览
提问于 2025-04-18 13:47

我有一张图像,它被存储为一个二维的numpy数组(可能是多维的)。

我可以在这个数组上创建一个视图,显示一个二维的滑动窗口,但当我把它重新调整形状,让每一行变成一个扁平的窗口(行是窗口,列是窗口中的一个像素)时,Python会做一个完整的复制。这是因为我使用了常见的步幅技巧,而新的形状在内存中并不是连续的。

我需要这样做,因为我将整个大图像传递给一个sklearn分类器,它接受二维矩阵,而没有批量或部分拟合的过程,完整的扩展副本对于内存来说太大了。

我的问题是:有没有办法在不做完整复制的情况下实现这个视图?

我认为答案可能是(1)关于步幅或numpy内存管理的某些我忽略的内容,或者(2)某种可以模拟numpy数组的Python掩码内存结构,即使是对于像sklearn这样包含cython的外部包。

在内存中对二维图像进行移动窗口训练的任务是很常见的,但我知道的唯一直接处理补丁的尝试是Vigra项目(http://ukoethe.github.io/vigra/)。

谢谢你的帮助。

>>> A=np.arange(9).reshape(3,3)
>>> print A
[[0 1 2]
 [3 4 5]
 [6 7 8]]
>>> xstep=1;ystep=1; xsize=2; ysize=2
>>> window_view = np.lib.stride_tricks.as_strided(A, ((A.shape[0] - xsize + 1) / xstep, (A.shape[1] - ysize + 1) / ystep, xsize, ysize),
...       (A.strides[0] * xstep, A.strides[1] * ystep, A.strides[0], A.strides[1]))
>>> print window_view 
[[[[0 1]
   [3 4]]

  [[1 2]
   [4 5]]]


 [[[3 4]
   [6 7]]

  [[4 5]
   [7 8]]]]
>>> 
>>> np.may_share_memory(A,window_view)
True
>>> B=window_view.reshape(-1,xsize*ysize)
>>> np.may_share_memory(A,B)
False

1 个回答

4

你这个任务仅仅用步幅是做不到的,但NumPy确实支持一种可以完成这个工作的数组。通过步幅和masked_array,你可以创建一个你想要的数据视图。不过,并不是所有的NumPy函数都支持masked_array的操作,所以scikit-learn可能也不太适合用这些。

首先,我们来重新审视一下我们想要做的事情。考虑一下你例子中的输入数据。从根本上说,这些数据其实就是内存中的一个一维数组,如果我们从这个角度看步幅,会简单很多。这个数组看起来是二维的,其实是因为我们定义了它的形状。使用步幅,形状可以这样定义:

from numpy.lib.stride_tricks import as_strided

base = np.arange(9)
isize = base.itemsize
A = as_strided(base, shape=(3, 3), strides=(3 * isize, isize))

现在我们的目标是设置这样的步幅给base,让数字的顺序和最终数组B一样。换句话说,我们在寻找整数ab,使得

>>> as_strided(base, shape=(4, 4), strides=(a, b))
array([[0, 1, 3, 4],
       [1, 2, 4, 5],
       [3, 4, 6, 7],
       [4, 5, 7, 8]])

但这显然是不可能的。我们能做到的最接近的视图是对base进行滑动窗口处理:

>>> C = as_strided(base, shape=(5, 5), strides=(isize, isize))
>>> C
array([[0, 1, 2, 3, 4],
       [1, 2, 3, 4, 5],
       [2, 3, 4, 5, 6],
       [3, 4, 5, 6, 7],
       [4, 5, 6, 7, 8]])

但这里的不同之处在于,我们有多余的列和行,而我们希望去掉这些。所以,实际上我们是在寻找一个不连续的滑动窗口,并且在固定间隔内跳跃。以这个例子为例,我们想要每隔三个项目就排除一个,并且在两行之后跳过一个项目。

我们可以把这个描述为一个masked_array

>>> mask = np.zeros((5, 5), dtype=bool)
>>> mask[2, :] = True
>>> mask[:, 2] = True
>>> D = np.ma.masked_array(C, mask=mask)

这个数组正好包含了我们想要的数据,而且它只是原始数据的一个视图。我们可以确认这些数据是相等的:

>>> D.data[~D.mask].reshape(4, 4)
array([[0, 1, 3, 4],
       [1, 2, 4, 5],
       [3, 4, 6, 7],
       [4, 5, 7, 8]])

但正如我一开始所说的,scikit-learn很可能不理解masked arrays。如果它只是把这个转换成一个数组,数据就会出错:

>>> np.array(D)
array([[0, 1, 2, 3, 4],
       [1, 2, 3, 4, 5],
       [2, 3, 4, 5, 6],
       [3, 4, 5, 6, 7],
       [4, 5, 6, 7, 8]])

撰写回答