2

在 libtiff 保存文件的上下文中遇到了这个问题,但现在我很困惑。谁能告诉我为什么这两个不相等?

ar1 = zeros((1000,1000),dtype=uint16)
ar1 = ar1.view(dtype=uint8) # works

ar2 = zeros((1000,2000),dtype=uint16)
ar2 = ar2[:,1000:]
ar2 = ar2.view(dtype=uint8) # ValueError: new type not compatible with array.

编辑:所以这也有效?

ar2 = zeros((1000,2000),dtype=uint16)
ar2 = array(ar2[:,1000:])
ar2 = ar2.view(dtype=uint8)
4

2 回答 2

3

概括

简而言之,只需在切片之前移动视图。

代替:

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两个uint8s,一个的值为 ,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__. 这是一个很好的学习工具来试验它,但你必须真正了解你在做什么才能有效地使用它。

无论如何,希望这会有所帮助!对不起,冗长的回答!

于 2013-10-22T17:09:19.023 回答
1

这不是一个完整的答案,但可能会指出我所缺少的细节。当您制作一个数组的切片时,您不再拥有连续的数据并且您不拥有这些数据。要查看这一点,请查看数组的标志:

ar2 = zeros((1000,2000),dtype=uint16)
ar2.flags

  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

ar2 = ar2[:,1000:]
ar2.flags

  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

我不知道其中哪一个导致了实际问题。正如您在编辑中指出的那样,如果您制作切片数组的新副本,那么一切都很好。您可以使用array()as you note 或类似ar2=ar2[:,1000:].copy().

于 2013-10-22T15:18:19.253 回答