Python Pillow 將 8 張照片經過旋轉、裁切與加框後合成 4×2 拼貼圖的流程

Python 圖片合成教學:用 Pillow 將多張圖片拼成一張

如果你有多張圖片要放進同一張圖,不必每次都開修圖軟體手動排列。這篇保留原本「用 Pillow 把 8 張圖片排成 4 × 2 合成圖」的做法,並補上目前實作時容易遇到的 EXIF 旋轉、不同尺寸裁切、邊框、RGB/RGBA 與輸出格式問題。

完成後你會做到:讀取多張 JPG、修正手機照片方向、把每張圖裁成相同尺寸、依序貼到畫布,最後輸出一張合成圖;也能替每格加上白色邊框。

安裝 Pillow

Pillow 是 Python Imaging Library(PIL)的延伸版本。請在終端機執行:

python -m pip install --upgrade Pillow

如果電腦同時安裝多個 Python,請把 python 換成實際執行程式所用的命令,例如 python3 或 Windows 的 py。安裝後可以先測試匯入:

from PIL import Image, ImageOps

print(Image.__version__)

先用 Image.new 建立空白畫布

Image.new(mode, size, color) 會建立一張新圖片。size 的順序是「寬、高」,例如 (400, 300);沒有指定顏色時,預設會建立黑色畫布。

模式 用途 常見輸出
RGB 紅、綠、藍三個 8-bit 色彩通道,沒有透明度 JPG、一般不透明圖片
RGBA RGB 再加一個 Alpha 透明度通道 需要透明背景的 PNG

以下程式會建立一張 400 × 300 的紅色圖片,並存成 test.jpg

from PIL import Image

background = Image.new("RGB", (400, 300), "#ff0000")
background.save("test.jpg")
Pillow Image.new 建立的 400 乘 300 紅色圖片
原教學的第一個範例:用 Image.new() 建立紅色畫布。

不同尺寸的圖片要怎麼統一?

直接呼叫 resize() 會強制縮放到指定寬高;原圖比例不同時,人物或物件可能被拉長。實務上可以依需求選擇 fit()contain()pad()

方法 結果 適合情境
ImageOps.fit() 等比例縮放後裁切,輸出尺寸固定 照片牆、商品格、縮圖拼貼
ImageOps.contain() 完整保留圖片,限制在指定範圍內 不能裁掉任何內容
ImageOps.pad() 等比例縮放並用背景補滿固定尺寸 需要完整圖片又要統一格子大小

這篇要把每一格固定為 300 × 400,所以使用 ImageOps.fit()Image.Resampling.LANCZOS 適合高品質縮小圖片。

把 8 張圖片合成 4 × 2 拼貼圖

以下範例讀取 test1.jpgtest8.jpg,建立 1200 × 800 黑色畫布。程式會先用 exif_transpose() 修正手機照片的方向資訊,再裁成 300 × 400,最後依序貼到四欄兩列的位置。

from math import ceil
from pathlib import Path

from PIL import Image, ImageOps

files = [Path(f"test{i}.jpg") for i in range(1, 9)]
columns = 4
cell_size = (300, 400)
rows = ceil(len(files) / columns)

canvas = Image.new(
"RGB",
(columns * cell_size[0], rows * cell_size[1]),
"black",
)

for index, path in enumerate(files):
with Image.open(path) as source:
image = ImageOps.exif_transpose(source)
image = ImageOps.fit(
image,
cell_size,
method=Image.Resampling.LANCZOS,
)

if image.mode != "RGB":
image = image.convert("RGB")

x = (index % columns) * cell_size[0]
y = (index // columns) * cell_size[1]
canvas.paste(image, (x, y))

canvas.save("collage.jpg", quality=90, optimize=True)
8 張圖片用 Pillow 排成四欄兩列的合成圖
原教學的 4 × 2 拼貼結果;每格位置由欄數與索引自動計算。

座標為什麼這樣算?

index % columns 會得到目前是第幾欄,index // columns 則會得到第幾列。再乘上單格寬高,就是 paste() 所需的左上角座標。這樣不必為每張圖片手動寫八次位置。

替每張圖片加上白色邊框

如果每格仍是 300 × 400,又想在四周加入 20 像素白框,先把圖片裁成 260 × 360,再使用 ImageOps.expand() 向四邊各增加 20 像素。

from PIL import Image, ImageOps

cell_size = (300, 400)
inner_size = (260, 360)
border = 20

with Image.open("test1.jpg") as source:
image = ImageOps.exif_transpose(source)
image = ImageOps.fit(
image,
inner_size,
method=Image.Resampling.LANCZOS,
)
image = ImageOps.expand(image, border=border, fill="white")

if image.mode != "RGB":
image = image.convert("RGB")

canvas = Image.new("RGB", cell_size, "black")
canvas.paste(image, (0, 0))
canvas.save("photo-with-border.jpg", quality=90)

要套用到前一段的 8 張圖片,只要把迴圈中的 fit() 目標改成 inner_size,接著呼叫 expand(),座標算法不需改動。

Pillow 合成圖片加入白色邊框後的效果
原教學的邊框效果:先縮小內容,再以 ImageOps.expand() 補上白框。

透明背景與 JPG 輸出的差異

如果合成圖需要透明背景,畫布要使用 RGBA 並輸出成 PNG:

from PIL import Image

canvas = Image.new("RGBA", (1200, 800), (0, 0, 0, 0))
canvas.save("collage.png")

JPG 不支援透明度。如果來源或畫布是 RGBA,存成 JPG 前要先決定背景色並轉成 RGB,否則可能出現錯誤或不符合預期的背景。

background = Image.new("RGB", canvas.size, "white")
background.paste(canvas, mask=canvas.getchannel("A"))
background.save("collage.jpg", quality=90)

實作時常見問題

手機照片為什麼會轉 90 度?

部分手機不會直接旋轉像素,而是把方向寫在 EXIF。讀取後先使用 ImageOps.exif_transpose(),就能依方向資訊修正圖片,並移除已套用的方向標記。

為什麼圖片被拉長或壓扁?

通常是直接把不同比例的原圖 resize() 成固定寬高。允許裁切時改用 fit();不能裁切時用 contain()pad()

輸出的合成圖為什麼看起來模糊?

不要先把小圖放大再縮小,也不要反覆讀取與儲存 JPG。縮放時使用 Image.Resampling.LANCZOS,並讓畫布尺寸符合實際輸出需求。

Image.open 後需要關閉檔案嗎?

Image.open() 是延遲讀取,而且檔案可能在載入期間保持開啟。範例使用 with Image.open(...) as source,離開區塊後會自動關閉檔案,批次處理大量圖片時比較安全。

圖片順序為什麼變成 1、10、2?

一般字串排序不等於數字排序。檔名若有編號,可以像本篇直接用 range() 建立順序;從資料夾抓檔時,則先解析數字或採用固定補零格式,例如 photo_01.jpg

大量圖片提醒:超高解析度圖片會占用大量記憶體。Pillow 對異常巨大的圖片也有解壓縮炸彈保護警告;若來源不可信,請不要為了讓程式繼續執行而直接關閉保護。

延伸應用

如果圖片來自多層資料夾,可以搭配 Python 檔案操作教學,用 pathlib 搜尋與整理檔案;需要從遠端主機取得素材時,也可參考 Python SFTP 上傳與下載教學

結論

圖片合成的核心就是「建立畫布、統一單格尺寸、計算座標、貼上圖片、儲存結果」。原本固定 8 張圖片的程式已經能解決基本需求;加入 exif_transpose()fit()、內容模式轉換與動態列數後,就能更穩定地處理不同來源與尺寸的照片。

官方參考:Pillow 安裝Image 模組ImageOps 模組圖片模式

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *