[原创]使用 Python + Pillow 完成图片墙拼图 - 青鸟晴空

时间:2024-03-03 11:50:33

[原创]使用 Python + Pillow 完成图片墙拼图

因为脑子里的一些想法,需要将一些照片拼接在一起,首先想到了使用APP直接操作,结果下载了许多应用后发现最多只能支持九张照片的拼接。然后又找了些美图秀秀之类,都无法满足我的需求,甚至我都想到使用PS去进行操作,但是如果使用PS那可就变成了一项耗时间的活了呢。于是继续的查找解决方案,在一个小角落里找到了使用Pillow搭建照片墙的例子,心想这就是我想要的,细细查找发现果不其然,一下子明朗了许多。在此对使用 Python + Pillow 完成的拼图实现进行记录。

安装 Python与 Pillow

可参考之前的博文,Python 及其库的安装

流程及思路

需求:将一个文件夹中按名称排序的方式进行拼图操作,逐行或逐列操作。

预计流程

  • 创建临时文件夹缓存可能生成或后续需要使用的图片
  • 读取图片,进行预处理后将处理后图片按照一定命名规范保存至缓存文件夹
  • 切换路径至临时文件夹
  • 依次打开图片,进行图像合并
  • 合并完成后保存图片
  • 删除临时文件夹
  • 展示合并图片

其中图片预处理可以为拉伸、旋转、裁剪等变换,因为我后续需要拼图时所有照片都应该为正方形,因此我需要对图片进行一个裁剪操作使图片比例为 1:1,为了保证裁剪区域在图像正中,需要进行判断长短边操作。

进行合并图片时需要空余区域尽可能少,且合并图片比例不能太畸形。例如 30 张图片可以分为 5 × 6 排布,31 张照片可以分布为 4 × 8 排布。最理想状态是脚本自动识别图片个数并合理分配,这块功能暂时没有写入 DEMO 中,行与列目前需要手动分配。

DEMO

在 Python 脚本中引用 Pillow 的方法也可以参见 DEMO 程序。其中,

bol_auto_place 暂时为可选项,置为 True 表示将自动分配合并后画布大小,目前只有根据图片多少开平方,然后合并为一个大正方形图片,手动设置合并排布时需要将其置为 False

row 为合并图片分布行参数,bol_auto_place == False 时有效。

col 为合并图片分布列参数,bol_auto_place == False 时有效。

nw 为缓存图片宽度设定,nh 为缓存图片高度设定。合并文件的大小由排布及缓存图片大小自动设定。

DEMO 脚本中所使用到的一些 function 有不懂的可百度或谷歌,查看各自的详细描述。脚本在使用时与图片放在一起,然后点击运行,运行期间将会显示当前处理图片,处理完成后将会展示合并图片。合并完成后图片以 PNG 格式存储于同路径下splicing_picture.png文件。DEMO 程序的源代码及几个参考文件可点此进行下载

#####################################################
# Notice !                                          #
# This script file should be placed in the same     #
# folder as the image.                              #
#####################################################

import sys, os, shutil, math
from PIL import Image

#####################################################
# parameter setting                                 #
#####################################################
bol_auto_place = False                     # auto place the image as a squared image, if \'True\', ignore var \'row\' and \'col\' below
row            = 4                         # row number which means col number images per row
col            = 8                         # col number which means row number images per col
nw             = 400                       # sub image size, nw x nh
nh             = 400

path = os.getcwd();          # acquire current folder path

if os.path.exists(\'tmp\'):    # ensure the \'tmp\' folder is empty
   shutil.rmtree(\'tmp\')
os.makedirs(\'tmp\')

file_ls = os.listdir()       # list all files in this folder

i = 0                        # a counter for images
for file in file_ls:
	name, extension = os.path.splitext(file);    # get file info[name, extension]
	if (extension == \'.png\' or extension == \'.jpg\' or extension == \'.jpeg\') and name != \'splicing_picture\':    # select the image
		i += 1                               # image counter++
		print(\'%s...%s%s\' % (i, name, extension))
		os.chdir(path)                       # ensure the image folder in every loop
		im = Image.open(file)                # open the image
		w, h = im.size                       # get image info
		#print(\'Original image size: %sx%s\' % (w, h))
		if nw == nh:                         # if image should be 1:1 size
			if w >= h:
				box = ((w - h) // 2, 0, (w + h) // 2, h)
			else:
				box = (0, (h - w) // 2, w, (h + w) // 2)
			region = im.crop(box)            # crop the image to 1:1 and keep center region
		else:
			region = im                      # do nothing
		sname = \'%s%s\' % (str(i), \'.png\')    # rename \'x.png\', x is a number from 1 to N
		os.chdir(\'tmp\')                      # get into the folder \'tmp\'
		region.save(sname, \'png\')            # save the square image

os.chdir(path)        # ensure the path
os.chdir(\'tmp\')

if bol_auto_place:    # auto place a big 1:1 square image 
	row = math.ceil(i ** 0.5)
	col = math.ceil(i ** 0.5)

dest_im = Image.new(\'RGBA\', (col * nw, row * nh), (255, 255, 255))    # the image size of splicing image, background color is white

for x in range(1, col + 1):          # loop place the sub image
	for y in range(1,row + 1):
		try:
			src_im = Image.open("%s.png" % str( x + ( y - 1 ) * col))  # open files in order
			resize_im = src_im.resize((nw, nh), Image.ANTIALIAS)       # resize again
			dest_im.paste(resize_im, ((x-1) * nw, (y-1) * nh))         # paste to dest_im
		except IOError:
			pass

os.chdir(path)        # ensure the path
shutil.rmtree(\'tmp\')  # delete the \'tmp\'

dest_im.save(\'splicing_picture.png\', \'png\')
dest_im.show()        # finish

运行效果图

30 张照片按照 4 × 8 的排布方式,图片拼合后效果图如下所示。个人对这样的结果还是相当满意的,也可以调整成 5 × 6 的排布方式,只需更改 rowcol 的参数设定后重新运行即可。

30 张照片拼图