概括
简而言之,只需在切片之前移动视图。
代替:
ar2 = zeros((1000,2000),dtype=uint16)
ar2 = ar2[:,1000:]
ar2 = ar2.view(dtype=uint8)
做:
ar2 = zeros((1000,2000),dtype=uint16)
ar2 = ar2.view(dtype=uint8) # ar2 is now a 1000x4000 array...
ar2 = ar2[:,2000:] # Note the 2000 instead of 1000!
发生的事情是切片数组不是连续的(正如@Craig 指出的那样)并且view
在保守方面犯了错误,并且不会尝试重新解释非连续的内存缓冲区。(在这种确切的情况下恰好是可能的,但在某些情况下,它会导致一个非均匀跨度的数组,这是 numpy 不允许的。)
如果您不是很熟悉numpy
,则可能是您误解了view
,而实际上是您想要的astype
。
做什么view
?
首先,让我们详细了解一下view
它的作用。在这种情况下,如果可能,它将 numpy 数组的内存缓冲区重新解释为新的数据类型。这意味着当您使用视图时,数组中的元素数量经常会发生变化。(您也可以使用它来将数组视为 的不同子类ndarray
,但我们现在将跳过该部分。)
您可能已经知道以下内容(您的问题有点微妙),但如果没有,这里有一个解释。
举个例子:
In [1]: import numpy as np
In [2]: x = np.zeros(2, dtype=np.uint16)
In [3]: x
Out[3]: array([0, 0], dtype=uint16)
In [4]: x.view(np.uint8)
Out[4]: array([0, 0, 0, 0], dtype=uint8)
In [5]: x.view(np.uint32)
Out[5]: array([0], dtype=uint32)
如果您想使用新数据类型制作数组的副本,请使用astype
:
In [6]: x
Out[6]: array([0, 0], dtype=uint16)
In [7]: x.astype(np.uint8)
Out[7]: array([0, 0], dtype=uint8)
In [8]: x.astype(np.uint32)
Out[8]: array([0, 0], dtype=uint32)
现在让我们看看查看二维数组时会发生什么。
In [9]: y = np.arange(4, dtype=np.uint16).reshape(2, 2)
In [10]: y
Out[10]:
array([[0, 1],
[2, 3]], dtype=uint16)
In [11]: y.view(np.uint8)
Out[12]:
array([[0, 0, 1, 0],
[2, 0, 3, 0]], dtype=uint8)
请注意,数组的形状发生了变化,并且沿最后一个轴发生了变化(在这种情况下,添加了额外的列)。
乍一看,似乎添加了额外的零。并不是要插入额外的零,而是 的表示uint16
相当于2
两个uint8
s,一个的值为 ,2
一个的值为0
。因此,任何uint16
低于 255 的值都会产生一个值和一个零,而任何超过 255 的值都会产生两个较小uint8
的 s。举个例子:
In [13]: y * 100
Out[14]:
array([[ 0, 100],
[200, 300]], dtype=uint16)
In [15]: (y * 100).view(np.uint8)
Out[15]:
array([[ 0, 0, 100, 0],
[200, 0, 44, 1]], dtype=uint8)
幕后发生了什么
Numpy 数组由一个“原始”内存缓冲区组成,它通过一个形状、一个数据类型和一个跨步(以及一个偏移量,但现在让我们忽略它)来解释。有关更多详细信息,有几个很好的概述:官方文档、numpy 书或scipy-lectures。
这使得 numpy 的内存效率非常高,并且可以以许多不同的方式对底层内存缓冲区进行“切片和切块”,而无需进行复制。
步幅告诉 numpy 要在内存缓冲区中跳转多少字节才能沿特定轴增加一个增量。
例如:
In [17]: y
Out[17]:
array([[0, 1],
[2, 3]], dtype=uint16)
In [18]: y.strides
Out[18]: (4, 2)
因此,要在数组中更深一层,numpy 需要在内存缓冲区中前移 4 个字节,而要在数组中更远一列,numpy 需要在内存缓冲区中前移 2 个字节。转置数组仅相当于反转步幅(和形状,但在本例中y
为 2x2):
In [19]: y.T.strides
Out[19]: (2, 4)
当我们将数组视为uint8
时,步幅会发生变化。我们仍然每行前进 4 个字节,但每列只有一个字节:
In [20]: y.view(np.uint8).strides
Out[20]: (4, 1)
但是,numpy 数组的每个维度必须具有一个步长。这就是“匀速”的意思。换句话说,确实向前移动一行/列/无论如何,numpy 需要能够每次通过底层内存缓冲区步进相同的数量。换句话说,没有办法告诉 numpy 为每行/列/任何内容步进不同的数量。
为此,view
采取了非常保守的路线。如果数组不连续,并且视图会改变数组的形状和步幅,它不会尝试处理它。正如@Craig 指出的那样,这是因为切片y
不连续view
而不起作用。
在很多情况下(你的情况就是这样),结果数组是有效的,但该view
方法并没有试图对此过于聪明。
要真正尝试可能的事情,您可以使用numpy.lib.stride_tricks.as_strided
或直接使用__array_interface__
. 这是一个很好的学习工具来试验它,但你必须真正了解你在做什么才能有效地使用它。
无论如何,希望这会有所帮助!对不起,冗长的回答!