使用numpy进行9宫格图像拼接

 

本节内容要用到opencv-python模块,请先行安装。本例程中使用到的图片保存在pictures子目录下。本例的任务是要将9张JPG格式图片按三行三列拼接成下述九宫格。

版权声明

本文可以在互联网上自由转载,但必须:注明出处(作者:海洋饼干叔叔)并包含指向本页面的链接。

本文不可以以纸质出版为目的进行改编、摘抄。

本文配套在线视频: https://www.bilibili.com/video/av34409478/?p=49

微实践:九宫格图像拼接

本节内容要用到opencv-python模块,请先行安装。本例程中使用到的图片保存在pictures子目录下。本例的任务是要将9张JPG格式图片按三行三列拼接成下述九宫格。

1
2
3
4
5
6
7
8
9
10
11
12
13
#pictures9.py
import glob
import numpy as np
from cv2 import imread,imwrite #cv2来自OpenCV-Python扩展库

imgs,heights,widths = [],[],[]
for f in glob.glob("pictures/*.jpg"):
img = imread(f,-1)
print("original:",img.shape)
h,w = img.shape[:2]
heights.append(h)
widths.append(w)
imgs.append(img)

控制台输出:

1
2
3
4
5
original: (960, 1280, 3)
...
original: (793, 1280, 3)
...
original: (960, 1280, 3)

glob.glob(“pictures/*.jpg”)返回一个序列,该序列包括当前目录之pictures子目录下的所有扩展名为jpg的图片文件的文件名(含路径)。读者可以在上述循环中加上print(f)把这些文件名打出来看看。

imread()函数来自opencv-python扩展库,它读取一个图片,并返回一个多维数组。我们打印了这些多维数组的形状,典型如(960,1280,3):它表示对应图片高960个像素,宽1280个像素,每个像素由3个值构成,分别表示该像素红、绿、蓝三个通道的颜色值。请读者注意,本例中的9张图片的高度值并不相等。

img.shape[:2]取出了图像/多维数组的高度和宽度,分别赋值给h和w。所有的图片高、宽均存入heights、widths列表备用。所有的图片/多维数组均存入imgs列表备用。

显然,(960,1280)的图片尺寸对于九宫格拼接而言太大了,需要分别提取缩略图。

1
2
3
4
5
6
7
8
9
10
11
12
#pictures9.py
#制作缩略图,纵横向每3个像素抽一个
minHeight = min(heights)
minWidth = min(widths)
for i,x in enumerate(imgs):
imgs[i] = x[:minHeight:3,:minWidth:3,:]
print("thumbnail:",imgs[i].shape)

#横向沿轴1拼接
img = np.concatenate(imgs,1)
print("concatenated by axis1:",img.shape)
imwrite("concatenated_1.jpg",img)

控制台输出:

1
2
3
4
thumbnail: (265, 427, 3)
...
thumbnail: (265, 427, 3)
concatenated by axis1: (265, 3843, 3)

我们首先获得了全部图像的最小高度 - min(heights)和最小宽度 - min(widths)。然后,遍历并逐一使用多维数组的切片下标语法提取缩略图:横纵向都从每3个像素中抽1个像素。根据打印出来的值,我们可以看到,所有的缩略图都是265 x 427个像素。

接下来,我们使用np.concatenate()函数将imgs列表中的9张图沿1轴拼接(横向拼接)至img。根据打印的结果,横向拼接后的图片尺寸为265 x 3843。imwrite()则将img存至文件”concatenated_1.jpg”。读者可以在项目目录中找到这个照片,如下:

下述代码及其执行结果可以帮助读者理解concatenate()函数的作用:可以看到,对于二维数组而言,沿0轴拼接相当于纵向延长数组,沿1轴拼接则相当于横向延长数组。

1
2
3
4
5
6
7
8
9
10
11
12
#con.py
import numpy as np

a = np.array([[1,2],[3,4]])
b = np.array([[11,22],[33,44]])
print("a=\n",a)
print("b=\n",b)

c = np.concatenate([a,b],0) #沿0轴拼接,纵向延长
print("np.concatenate([a,b],0)=\n",c)
c = np.concatenate([a,b],1) #沿1轴拼接,横向延长
print("np.concatenate([a,b],1)=\n",c)

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a=
[[1 2]
[3 4]]
b=
[[11 22]
[33 44]]
np.concatenate([a,b],0)=
[[ 1 2]
[ 3 4]
[11 22]
[33 44]]
np.concatenate([a,b],1)=
[[ 1 2 11 22]
[ 3 4 33 44]]

借助于concatenate()函数,我们可以先将图片0-2横向拼接,再将图片3-5横向拼接,接着再将图片6-8横向拼接。最后再将拼接结果纵向拼接,即得3x3的九宫格:

1
2
3
4
5
6
7
8
#pictures9.py
#方法1
img0 = np.concatenate(imgs[:3],1) #沿1轴横向拼接
img1 = np.concatenate(imgs[3:6],1)
img2 = np.concatenate(imgs[6:],1)
img9 = np.concatenate([img0,img1,img2],0) #沿0轴纵向拼接
print("3x3_0, shape:",img9.shape)
imwrite("3x3_0.jpg",img9)

控制台输出:

1
3x3_0, shape: (795, 1281, 3)

上述3x3_0.jpg应该与本例开始处的图片相同,该文件也存入了当前项目目录,读者自行查证。除了上面这个方法外,下面这个方法也可以达到相同的效果,但真的很难理解。在下述代码中,img1.swapaxes(1,2)将img1的1轴与2轴进行了交换。

1
2
3
4
5
6
#方法2
img = np.concatenate(imgs,0) #将9图沿0轴纵向拼
img1 = img.reshape(3,3,265,427,3) #改变形状至5维数组
img9 = img1.swapaxes(1,2).reshape(795,1281,3) #1轴和2轴交换,再改变形状
print("3x3_1, shape:",img9.shape)
imwrite("3x3_1.jpg",img9)

控制台输出:

1
3x3_1, shape: (795, 1281, 3)

读者可以查证3x3_1.jpg的最终效果。

读者可以试着执行下述代码来观察轴交换的效果:

1
2
3
4
5
6
7
8
9
10
#swapaxes.py
import numpy as np
a = np.array([[1,2],[3,4]])
print("a=\n",a)
print("a.swapaxes(0,1):\n",a.swapaxes(0,1))

b = np.array([[1,2,3,4],[5,6,7,8]])
print("b=\n",b)
print("b.reshape(2,2,2)=\n",b.reshape(2,2,2))
print("b.reshape(2,2,2).swapaxes(1,2))=\n",b.reshape(2,2,2).swapaxes(1,2))

与多维数组操作有关的函数列表如下:

函数名 作用 函数名 作用
concatenate() 拼接多个数组 vstack/hstack() 沿0/1轴拼接数组
column_stack() 按列连接多个一维数组 split,array_split() 将数组分为多段
transpose() 重设轴的顺序 swapaxes() 交换两个轴的顺序

 评论