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")

Image.new() 建立紅色畫布。不同尺寸的圖片要怎麼統一?
直接呼叫 resize() 會強制縮放到指定寬高;原圖比例不同時,人物或物件可能被拉長。實務上可以依需求選擇 fit()、contain() 或 pad()。
| 方法 | 結果 | 適合情境 |
|---|---|---|
ImageOps.fit() |
等比例縮放後裁切,輸出尺寸固定 | 照片牆、商品格、縮圖拼貼 |
ImageOps.contain() |
完整保留圖片,限制在指定範圍內 | 不能裁掉任何內容 |
ImageOps.pad() |
等比例縮放並用背景補滿固定尺寸 | 需要完整圖片又要統一格子大小 |
這篇要把每一格固定為 300 × 400,所以使用 ImageOps.fit()。Image.Resampling.LANCZOS 適合高品質縮小圖片。
把 8 張圖片合成 4 × 2 拼貼圖
以下範例讀取 test1.jpg 到 test8.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)

座標為什麼這樣算?
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(),座標算法不需改動。

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 模組、圖片模式。















