
2.4.6 操作多维数组
与本节内容对应的Notebook为:02-numpy/numpy-430-functions-array-op.ipynb。
本节介绍的函数如表2-8所示。
表2-8 本节要介绍的函数

concatenate( )是连接多个数组的最基本的函数,其他函数都是它的快捷版本。它的第一个参数是包含多个数组的序列,它将沿着axis参数指定的轴(默认为第0轴)连接数组。所有这些数组的形状除了第axis轴之外都相同。
vstack( )沿着第0轴连接数组,当被连接的数组是长度为N的一维数组时,将其形状改为(1, N)。
hstack( )沿着第1轴连接数组。当所有数组都是一维时,沿着第0轴连接数组,因此结果数组仍然为一维的。
column_stack( )和hstack( )类似,沿着第1轴连接数组,但是当数组为一维时,将其形状改为(N, 1),经常用于按列连接多个一维数组。

此外,c_[ ]对象也可以用于按列连接数组:
np.c_[a, b, a+b] array([[ 0, 10, 10], [ 1, 11, 12], [ 2, 12, 14]])
split( )和array_split( )的用法基本相同,将一个数组沿着指定轴分成多个数组,可以直接指定切分轴上的切分点下标。下面的代码把随机数组a切分为多个数组,保证每个数组中的元素都是升序排列的。注意通过diff( )和nonzero( )获得的下标是每个升序片段中最后一个元素的下标,而切分点为每个片段第一个元素的下标,因此需要+1。

当第二个参数为整数时,表示分组个数。split( )只能平均分组,而array_split( )能尽量平均分组:
np.split(a, 6) np.array_split(a, 5) --------------- -------------------- [array([6, 3]), [array([6, 3, 7]), array([7, 4]), array([4, 6, 9]), array([6, 9]), array([2, 6]), array([2, 6]), array([7, 4]), array([7, 4]), array([3, 7])] array([3, 7])]
transpose( )和swapaxes( )用于修改轴的顺序,它们得到的是原数组的视图。transpose( )通过其第二个参数axes指定轴的顺序,默认时表示将整个形状翻转。而swapaxes( )通过两个整数指定调换顺序的轴。在下面的例子中:
●transpose( )的结果数组的形状为(3, 4, 2, 5),它们分别位于原数组形状(2, 3, 4, 5)的(1, 2, 0, 3)下标位置处。
●swapaxes( )的结果数组的形状为(2, 4, 3, 5),它是通过将原数组形状的中间两个轴对调得到的。
a = np.random.randint(0, 10, (2, 3, 4, 5)) print u"原数组形状:", a.shape print u"transpose:", np.transpose(a, (1, 2, 0, 3)).shape print u"swapaxes:", np.swapaxes(a, 1, 2).shape 原数组形状: (2, 3, 4, 5) transpose: (3, 4, 2, 5) swapaxes: (2, 4, 3, 5)
下面以将多个缩略图拼成一幅大图为例,帮助读者理解多维数组中变换轴的顺序。在data/thumbnails目录之下有30个160×90像素的PNG图标图像,需要将这些图像拼成一幅6行5列的大图像。首先调用glob和cv2模块中的函数,获得一个数组列表imgs。cv2库将在第9章介绍OpenCV时进行详细介绍。
import glob
import numpy as np
from cv2 import imread, imwrite
imgs = [ ]
for fn in glob.glob("thumbnails/*
.png"):
imgs.append(imread(fn, -1))
print imgs[0].shape
(90, 160, 3)
imgs中每个元素都是一个多维数组,它的形状为(90, 160, 3),其中第0轴的长度为图像的高度,第1轴的长度为图像的宽度,第2轴为图像的通道数,彩色图像包含红、绿、蓝三个通道,所以第2轴的长度为3。
调用concatenate( )将这些数组沿第0轴拼成一个大数组,结果img是一个宽为160像素、高为2700像素的图像:
img = np.concatenate(imgs, 0) img.shape (2700, 160, 3)
由于我们的最终目标是把它们拼成一幅如图2-9(左)所示的6行5列的缩略图,因此需要将img的第0轴分解为3个轴,长度分别为(6, 5, 90)。下面使用reshape( )完成这个工作。使用img1[i, j]可以获取第i行、第j列上的图像:

图2-9 使用操作多维数组的函数拼接多幅缩略图
img1 = img.reshape(6, 5, 90, 160, 3) img1[0, 1].shape (90, 160, 3)
根据目标图像的大小,可以算出目标数组的形状为(540, 800, 3),即(6* 90, 5* 160, 3),也可以把它看作形状为(6, 90, 5, 160, 3)的多维数组。与img1的形状相比,可以看出需要交换img1的第1轴和第2轴。这个操作可以通过img1.swapaxes( )或img1.transpose( )完成。然后再通过reshape( )将数组的形状改为(540, 800, 3)。
img2 = img1.swapaxes(1, 2).reshape(540, 800, 3)
请读者思考下面的img3会得到怎样的图像:
img = np.concatenate(imgs, 0) img3 = img.reshape(5, 6, 90, 160, 3) \ .transpose(1, 2, 0, 3, 4) \ .reshape(540, 800, 3)
下面的程序将每幅缩略图的边沿上的两个像素填充为白色,效果如图2-9(右)所示。❶这里使用一个形状与img1的前4个轴相同的mask布尔数组,该数组的初始值为True。❷通过切片将mask中除去边框的部分设置为False。❸将img1中与mask为True的对应像素填充为白色。
img = np.concatenate(imgs, 0) img1 = img.reshape(6, 5, 90, 160, 3) mask = np.ones(img1.shape[:-1], dtype=bool) ❶ mask[:, :, 2:-2, 2:-2] = False ❷ img1[mask] = 230 ❸ img4 = img1.swapaxes(1, 2).reshape(540, 800, 3)