NumPy中的三维数组

92

刚学习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]
那就是三行,四列和两个深度维度。也就是说,第一个维度是“深度”。进一步增加这个问题,使用OpenCV导入图像时,颜色维度是最后一个维度,也就是说,我将颜色信息视为深度维度。如果我只想在已知较小的三维数组上尝试一些东西,这会极大地复杂化问题。我是否理解错了什么?如果没有,那么为什么numpy要使用如此不直观的方式来处理三维数组呢?

3
Python 遵循行优先索引。请参考此帖子:https://dev59.com/mKrka4cB1Zd3GeqPk-dD?rq=1 - richa shah
6个回答

67
您有一个截断的数组表示。让我们看一个完整的例子:
>>> 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中的数组打印输出格式类似于Python嵌套列表,使用单词 array 跟随结构体进行展示。让我们创建一个相似的列表:

>>> 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)相反。

是的,我从jabaldonedo和一些测试中理解了这一点。尽管后端逻辑使其更易于理解,但它的显示方式并不合乎逻辑。我一定会考虑订单存储,这是个好建议! - Vejto
3
并不是不合逻辑,只是不同而已。在二维屏幕上无法真实地表示三维数组,因此不同的环境采用不同的方法。Matlab和NumPy都有他们打印数组的理由。 - ffriend
9
阅读并理解这个回答后,我觉得把numpy数组想象成“行、列、堆叠”的顺序不太合适。更好的方式是想象成“第一个容器包含第二个容器,第二个容器又包含第三个容器”。 - Monica Heddneck
3
显示不合逻辑,看起来像是两个数组,每个数组有3行4列,但通过这里的答案可以知道实际上是4个堆叠的数组,每个数组有2行和列数。 - seanysull
1
如果你把数据结构看作是一个长宽高分别为2、3和4的盒子,那么唯一的区别就在于将你的参考框架旋转90度。这并不是不合逻辑,只是不同的观点而已。 - John
@John 哈哈,看来我是那个缺乏逻辑的人。 - seanysull

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)。


1
好的,所以如果一切计算正确,我可以期望一个算法对于较小的矩阵和大图像都是相同的吗?将第三个维度视为“深度”。这是有道理的,因为数学上对待所有维度都是一样的。但是有没有办法让Numpy正确地呈现它呢?恐怕链接在这方面没有太多帮助 :( - Vejto
Matlab和Numpy在处理图像时的方式不同,如果您正在使用OpenCV,则它将图像表示为BGR而不是RGB,然后Matlab使用Fortran读取矩阵。因此,在从Matlab移植代码到Numpy时,您必须考虑所有这些事情。 - jabaldonedo
很好,我尝试了一些东西,当然和你说的一样。我知道BGR的表示方式(它让我感到困扰,但不像这个问题那么严重)。我只是希望有一种“正确”的方法来呈现它。目前,我将只查看每个维度,因为(对于一个3D矩阵e)e[:,:,1]被正确显示,而e[:,:,:-1]则显示不合逻辑。谢谢! - Vejto
1
这是不正确的,“depth”是显示的第一个维度。在C排序中,显示的第一个维度是具有“最大步幅”的维度。实际顺序是深度、行、列。在Fortran排序中,情况相反,因此是列、行和深度。 - Krupip

20

不需要深入技术,并让自己爆炸。 让我用最简单的方式解释一下。 我们在学校数学课上都学过“集合”。只需将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 +数组始终建立在这些“集合”上。


5
尽管人们常说“顺序不重要,这只是惯例”,但在进入跨域界面时(例如从C排序转换为Fortran排序或其他排序方案),该说法会失效。在这种情况下,数据的布局方式以及numpy中形状的表示方式非常重要。
默认情况下,numpy使用C排序,这意味着内存中连续的元素是按行存储的。您还可以使用Fortran排序("F"),它根据列排序元素,并索引相邻的元素。
Numpy的形状进一步有其自己的顺序来显示形状。在numpy中,最大步长优先,即在3D向量中,最不连续的维度Z或页面、第三维等是最前面的。因此,在执行以下操作时:
np.zeros((2,3,4)).shape 您将得到
(2,3,4)
这实际上是(frames, rows, columns)。而使用np.zeros((2,2,3,4)).shape则意味着(metaframs, frames, rows, columns),这在像C++这样的语言中创建多维数组时更有意义。对于C ++,创建一个非连续定义的4D数组会导致一个数组(元素的数组)的数组,这迫使您取消引用保存所有其他数组(第4个维度)的第一个数组,然后同样处理到最后(第3个、第2个、第1个),从而产生以下语法:
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。

5

1

我在 NumPy 刚开始也感到困惑。当你说:

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

它的解释是: 生成一个3D矩阵,其中包含2个每个都有3行的矩阵。每行必须包含4个元素; Numpy总是从最外层开始分配维度,然后向内移动 拇指规则:2D数组是矩阵。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接