Python科学计算(第2版)
上QQ阅读APP看书,第一时间看更新

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)