numpy中的三维数组

96 投票
6 回答
316722 浏览
提问于 2025-04-18 02:10

我刚开始学习Python和Numpy,想创建三维数组。我的问题是,维度的顺序和Matlab不一样,实际上这个顺序根本没有道理。

创建一个矩阵:

x = np.zeros((2,3,4))

在我看来,这应该是2行、3列和4个深度维度,应该呈现为:

[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]      0 0 0 0]

也就是说,变成了3行、4列和2个深度维度。也就是说,第一个维度是“深度”。更麻烦的是,当我用OpenCV导入一张图片时,颜色维度是最后一个维度,也就是说,我看到的颜色信息被当作深度维度。这让事情变得复杂得多,如果我只是想在一个已知的小三维数组上尝试一些东西。

我是不是搞错了什么?如果没有,为什么numpy使用这样不直观的方式来处理三维数组呢?

6 个回答

5

虽然很多人喜欢说“顺序无所谓,这只是个约定”,但在跨域接口时,这种说法就不太成立了,比如从C语言的顺序转到Fortran的顺序,或者其他的顺序方案。在这种情况下,数据的布局和在numpy中如何表示形状是非常重要的。

默认情况下,numpy使用C语言的顺序,这意味着在内存中相邻的元素是按存储的。你也可以使用FORTRAN顺序(“F”),这种方式是根据列来排序元素,索引相邻的元素。

numpy的形状还有自己的一套显示顺序。在numpy中,形状是先显示最大的跨度,也就是说,在一个三维向量中,最不连续的维度是Z,或者说是页,第三维等等。所以当你执行:

np.zeros((2,3,4)).shape

你会得到

(2,3,4)

这实际上是(帧, 行, 列)。如果你改成np.zeros((2,2,3,4)).shape,那么就意味着(元帧, 帧, 行, 列)。当你考虑在C语言等语言中创建多维数组时,这样的顺序就更容易理解了。对于C++来说,创建一个非连续定义的四维数组会得到array [ of arrays [ of arrays [ of elements ]]]。这就要求你先解引用第一个数组(也就是包含其他数组的第四维),然后依次向下解引用(第三维、第二维、第一维),最终的语法像这样:

double element = array4d[w][z][y][x];

而在Fortran中,这个索引顺序是反过来的(x变成了第一个array4d[x][y][z][w]),从最连续到最不连续。而在Matlab中,这一切就变得很奇怪了。

Matlab试图同时保留数学上的默认顺序(行、列),但在内部库中又使用列主序,并且不遵循C语言的维度顺序。在Matlab中,你的顺序是这样的:

double element = array4d[y][x][z][w];

这就打破了所有的约定,造成了奇怪的情况,有时候你像是按行顺序索引,有时候又像是按列顺序索引(比如在创建矩阵时)。

实际上,Matlab才是不直观的,而不是Numpy。

6

阅读这篇文章可以帮助你更好地理解:numpy:数组的形状和数组的重塑

注意:NumPy 在报告三维数组的形状时,顺序是 层,行,列

21

其实没必要搞得那么复杂,让我用最简单的方式来解释一下。我们在学校的数学课上都学过“集合”。你可以把3D的numpy数组想象成“集合”的一种表现形式。

x = np.zeros((2,3,4)) 

简单来说:

2 Sets, 3 Rows per Set, 4 Columns

举个例子:

输入

x = np.zeros((2,3,4))

输出

Set # 1 ---- [[[ 0.,  0.,  0.,  0.],  ---- Row 1
               [ 0.,  0.,  0.,  0.],  ---- Row 2
               [ 0.,  0.,  0.,  0.]], ---- Row 3 
    
Set # 2 ----  [[ 0.,  0.,  0.,  0.],  ---- Row 1
               [ 0.,  0.,  0.,  0.],  ---- Row 2
               [ 0.,  0.,  0.,  0.]]] ---- Row 3

解释: 看吧,我们有2个集合,每个集合有3行,4列。

注意:每当你看到一个用双括号包起来的“数字集合”,就把它当作一个“集合”来看。而3D和3D+数组总是基于这些“集合”来构建的。

23

你说得对,你正在创建一个有2行、3列和4个深度的矩阵。Numpy打印矩阵的方式和Matlab不一样:

Numpy:

>>> import numpy as np
>>> np.zeros((2,3,2))
 array([[[ 0.,  0.],
    [ 0.,  0.],
    [ 0.,  0.]],

   [[ 0.,  0.],
    [ 0.,  0.],
    [ 0.,  0.]]])

Matlab:

>> zeros(2, 3, 2)
 ans(:,:,1) =
     0     0     0
     0     0     0
 ans(:,:,2) =
     0     0     0
     0     0     0

不过你计算的矩阵是一样的。你可以看看这个链接:Numpy for Matlab users,它会指导你如何把Matlab的代码转换成Numpy的代码。


举个例子,如果你在使用OpenCV,你可以用numpy来构建一张图像,记得OpenCV使用的是BGR的颜色表示法:

import cv2
import numpy as np

a = np.zeros((100, 100,3))
a[:,:,0] = 255

b = np.zeros((100, 100,3))
b[:,:,1] = 255

c = np.zeros((100, 200,3)) 
c[:,:,2] = 255

img = np.vstack((c, np.hstack((a, b))))

cv2.imshow('image', img)
cv2.waitKey(0)

在这里输入图片描述

如果你看看矩阵c,你会发现它是一个100x200x3的矩阵,这正好和图像中显示的内容一致(红色部分是因为我们把R坐标设置为255,而其他两个坐标保持为0)。

68

你有一个被截断的数组表示。我们来看一个完整的例子:

>>> a = np.zeros((2, 3, 4))
>>> a
array([[[ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.]],

       [[ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.]]])

在NumPy中,数组的打印格式是先显示单词 array,然后是它的结构,这和嵌套的Python列表类似。我们来创建一个类似的列表:

>>> l = [[[ 0.,  0.,  0.,  0.],
          [ 0.,  0.,  0.,  0.],
          [ 0.,  0.,  0.,  0.]],

          [[ 0.,  0.,  0.,  0.],
          [ 0.,  0.,  0.,  0.],
          [ 0.,  0.,  0.,  0.]]]

>>> l
[[[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, 0.0, 0.0]]]

这个复合列表 l 的第一层正好有2个元素,就像数组 a 的第一维(行数)。每个元素本身又是一个有3个元素的列表,这和 a 的第二维(列数)相等。最后,最里面的列表每个都有4个元素,这和 a 的第三维(深度或颜色数)相同。

所以,你的结构(维度方面)和Matlab是完全一样的,只是打印的方式不同。

一些注意事项:

  1. Matlab是按列存储数据的(“Fortran顺序”),而NumPy默认是按行存储的(“C顺序”)。这对索引没有影响,但可能会影响性能。例如,在Matlab中,效率高的循环是针对列的(比如 for n = 1:10 a(:, n) end),而在NumPy中,更推荐按行循环(比如 for n in range(10): a[n, :] -- 注意 n 在第一个位置,而不是最后一个)。

  2. 如果你在OpenCV中处理彩色图像,记得:

    2.1. 它以BGR格式存储图像,而不是像大多数Python库那样使用RGB格式。

    2.2. 大多数函数是基于图像坐标(x, y)工作的,这和矩阵坐标(i, j)是相反的。

撰写回答