因為腦子里的一些想法,需要將一些照片拼接在一起,首先想到了使用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 的排布方式,只需更改 row
與 col
的參數設定后重新運行即可。