numpy中的三维数组
我刚开始学习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 个回答
虽然很多人喜欢说“顺序无所谓,这只是个约定”,但在跨域接口时,这种说法就不太成立了,比如从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。
阅读这篇文章可以帮助你更好地理解:numpy:数组的形状和数组的重塑
注意:NumPy
在报告三维数组的形状时,顺序是 层,行,列
。
其实没必要搞得那么复杂,让我用最简单的方式来解释一下。我们在学校的数学课上都学过“集合”。你可以把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+数组总是基于这些“集合”来构建的。
你说得对,你正在创建一个有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)。
你有一个被截断的数组表示。我们来看一个完整的例子:
>>> 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是完全一样的,只是打印的方式不同。
一些注意事项:
Matlab是按列存储数据的(“Fortran顺序”),而NumPy默认是按行存储的(“C顺序”)。这对索引没有影响,但可能会影响性能。例如,在Matlab中,效率高的循环是针对列的(比如
for n = 1:10 a(:, n) end
),而在NumPy中,更推荐按行循环(比如for n in range(10): a[n, :]
-- 注意n
在第一个位置,而不是最后一个)。如果你在OpenCV中处理彩色图像,记得:
2.1. 它以BGR格式存储图像,而不是像大多数Python库那样使用RGB格式。
2.2. 大多数函数是基于图像坐标(
x, y
)工作的,这和矩阵坐标(i, j
)是相反的。