使用省略号交换 numpy 数组的维度?

Swapping the dimensions of a numpy array using Ellipsis?

此代码是 交换 RBG 图像的第一个和最后一个通道,该图像被加载到 Numpy 数组中:

img = imread('image1.jpg')

# Convert from RGB -> BGR
img = img[..., [2, 1, 0]]

虽然我理解使用 Ellipsis 在 Numpy 数组中进行切片,但我无法理解这里使用 Ellipsis。谁能解释一下这里究竟发生了什么?

tl;博士

img[..., [2, 1, 0]] 产生的结果与为索引数组 [2, 1, 0] 中的每个 i 获取切片 img[:, :, i],然后沿着img。也就是说:

img[..., [2,1,0]]

将产生与以下相同的输出:

np.stack([img[:,:,2], img[:,:,1], img[:,:,0]], axis=2)

省略号...是一个占位符,它告诉numpy将索引数组应用到哪个轴。如果没有 ...,索引数组将应用于 img 的第一个轴而不是最后一个。因此,没有 ...,索引语句:

img[[2,1,0]]

将产生与以下相同的输出:

np.stack([img[2,:,:], img[1,:,:], img[0,:,:]], axis=0)

文档怎么说

这是 docs call "Combining advanced and basic indexing":

的例子

When there is at least one slice (:), ellipsis (...) or np.newaxis in the index (or the array has more dimensions than there are advanced indexes), then the behaviour can be more complicated. It is like concatenating the indexing result for each advanced index element.

它继续描述这个

case, the dimensions from the advanced indexing operations [in your example [2, 1, 0]] are inserted into the result array at the same spot as they were in the initial array (the latter logic is what makes simple advanced indexing behave just like slicing).

二维案例

文档不是最容易理解的,但在这种情况下,区分起来并不难。从一个更简单的 2D 案例开始:

arr = np.arange(12).reshape(4,3)

array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])

使用具有单个索引值的同类高级索引产生:

arr[:, [1]]

array([[ 1],
       [ 4],
       [ 7],
       [10]])

这是 arr 的第一列。换句话说,这就像您在固定最后一个轴的索引的同时从 arr 产生了所有可能的值。正如@hpaulj 在他的评论中所说,省略号在那里充当占位符。它有效地告诉 numpy 在所有轴上自由迭代,除了应用索引数组的最后一个轴。

您也可以使用此索引语法随意排列 arr 的列:

arr[..., [1,0,2]]

array([[ 1,  0,  2],
       [ 4,  3,  5],
       [ 7,  6,  8],
       [10,  9, 11]])

这与您的示例中的操作基本相同,但在 2D 数组而不是 3D 数组上。

您可以通过将其分解为更简单的索引操作来解释 arr[..., [1,0,2]] 发生了什么。这有点像你首先取 arr[..., [1]] 的 return 值:

array([[ 1],
       [ 4],
       [ 7],
       [10]])

然后arr[..., [0]]的return值:

array([[0],
       [3],
       [6],
       [9]])

然后arr[..., [1]]的return值:

array([[ 2],
       [ 5],
       [ 8],
       [11]])

然后最终将所有这些结果连接成一个形状为 (*arr.shape[:-1], len(ix)) 的数组,其中 ix = [2, 0, 1] 是索引数组。最后一个轴上的数据根据​​它们在 ix.

中的顺序进行排序

准确理解省略号的一个好方法是在没有省略号的情况下执行相同的操作:

arr[[1,0,2]]

array([[6, 7, 8],
       [0, 1, 2],
       [3, 4, 5]])

在这种情况下,索引数组应用于 arr 的第一个轴,因此输出是一个包含 arr[1,0,2] 行的数组。在索引数组之前添加一个 ... 告诉 numpy 将索引数组应用于 arr 的最后一个轴。

您的 3D 案例

您询问的案例是上面 2D arr[..., [1,0,2]] 示例的 3D 等价物。假设 img.shape(480, 640, 3)。您可以将 img[..., [2, 1, 0]] 视为循环遍历 ix=[2, 1, 0] 中的每个值 i。对于每个 i,索引操作将收集位于 img 最后一个轴的第 i 个索引处的形状 (480, 640, 1) 的平板。收集完所有三个 slab 后,最终结果将等同于沿它们的最后一个轴(并按照它们被发现的顺序)连接。

笔记

  • arr[..., [1]]arr[:,1]之间的唯一区别是arr[..., [1]]保留了原始数组中数据的形状。

  • 对于二维数组,arr[:, [1]]等价于arr[..., [1]]: 就像 ... 一样充当占位符,但仅用于单个维度。