在验证码识别、图片预处理等场景中,经常需要从一张大图中按固定坐标裁出多张小图,再重新拼成一张新图。本文以抖音九宫格验证码为例,完整演示裁剪与拼接的实现。


一、需求拆解

原始图是一张九宫格大图,每张小图之间有间隙。目标:

  1. 按坐标裁出 9 张小图
  2. 每张缩放到 110×110
  3. 拼成一张 330×330 的新图

二、核心代码

from PIL import Image

img = Image.open(r"微信图片_20260528094122_13_1856.png")

# 九宫格坐标:3行 × 3列
rect_list = [
    [(1,0,166,165),  (181,0,346,165),  (361,0,525,165)],     # 第一行
    [(1,180,166,345),(181,180,346,345),(361,180,525,345)],   # 第二行
    [(1,360,166,525),(181,360,346,525),(361,360,525,525)],   # 第三行
]

# 创建 330×330 空白图(白色背景)
new_img = Image.new("RGB", (330, 330), (255, 255, 255))

for y in range(3):
    for x in range(3):
        # 1. 裁剪
        s_img = img.crop(rect_list[y][x])
        # 2. 高质量缩放(关键:加 LANCZOS)
        s_img = s_img.resize((110, 110), resample=Image.LANCZOS)
        # 3. 粘贴到新图(每张 110px,按网格排列)
        new_img.paste(s_img, (x * 110, y * 110))

new_img.save("result.png")

三、关键细节

1. crop() 裁剪

crop() 接收一个四元组 (left, top, right, bottom)

s_img = img.crop((1, 0, 166, 165))  # 左上(1,0) 到 右下(166,165)

坐标从大图的 rect_list 中逐行读取。

2. resize() 高质量缩放

必须指定 resample=Image.LANCZOS,否则默认用 BILINEAR,缩放后模糊。

滤波器 质量 速度
NEAREST 最快
BILINEAR ⭐⭐
BICUBIC ⭐⭐⭐ 中等
LANCZOS ⭐⭐⭐⭐⭐ 较慢

PIL 10.0+ 写法:

s_img.resize((110, 110), resample=Image.Resampling.LANCZOS)

3. paste() 拼接

新图 330×330,每张小图 110×110,共 3×3 排列。粘贴坐标按网格计算:

paste_x = x * 110
paste_y = y * 110
小图 x y 粘贴位置
(0,0) 0 0 (0, 0)
(0,1) 1 0 (110, 0)
(1,0) 0 1 (0, 110)
(2,2) 2 2 (220, 220)

四、常见坑

问题 原因 解决
s_img.size() 报错 size 是属性不是方法 去掉括号:s_img.size
拼接后有白边 小图尺寸不一致 确保 resize 统一为 110×110
缩放后模糊 没加 LANCZOS resize(..., resample=Image.LANCZOS)
保存后质量差 JPEG 默认 quality=75 save(..., quality=95)

五、透明背景版本

如果需要透明底:

new_img = Image.new("RGBA", (330, 330), (0, 0, 0, 0))  # 透明背景
new_img.save("result.png")  # 必须存 PNG

总结:crop 裁 → LANCZOS 缩 → paste 拼,三步搞定九宫格重排。

更多推荐