Pillow 图像几何变换与仿射操作

Pillow 图像几何变换与仿射操作

几何变换是图像处理中最基础且最常用的一类操作,在数据预处理、样本增强、图像对齐以及视觉建模等任务中均扮演核心角色。该类操作通过对像素坐标进行系统性映射,改变图像在空间中的结构表达,而不直接修改像素的语义内容。

在 Pillow 中,几何变换主要通过裁剪、翻转、旋转、缩放以及更一般形式的仿射变换来实现。本章围绕这些操作,从坐标模型、实现方式与可视化结果三个层面,对其原理与实践进行说明。


1. 几何变换的坐标表示

一幅二维数字图像可以表示为定义在规则网格上的函数:
I:(x,y)→v,v∈Rc I: (x, y) \rightarrow \mathbf{v}, \quad \mathbf{v} \in \mathbb{R}^c I:(x,y)→v,v∈Rc

其中 (x,y)(x, y)(x,y) 为像素坐标,ccc 为通道数。

几何变换通过构造坐标映射函数 TTT,在新的坐标系下重建图像:
I′(x′,y′)=I(T−1(x′,y′)) I'(x', y') = I(T^{-1}(x', y')) I′(x′,y′)=I(T−1(x′,y′))

Pillow 在实现几何变换时采用反向映射策略,即在输出图像坐标系中查找原图像的对应位置,从而避免像素空洞问题。


2. 图像裁剪

2.1 原理:区域裁剪与坐标范围

裁剪通过限定坐标范围提取原图像的局部区域。该操作不涉及插值计算,其结果直接来自原始像素数据。

2.2 Pillow 接口:Image.crop()

python 复制代码
from PIL import Image                 # Pillow 的核心图像对象与处理接口
from skimage import data              # scikit-image 自带示例数据集(便于复现实验)
from IPython.display import display   # 在 Notebook / Jupyter 环境中显示图像

img = Image.fromarray(data.astronaut())  # 将 skimage 返回的 NumPy 数组转换为 PIL.Image 对象
box = (100, 100, 400, 400)              # 裁剪区域 (left, upper, right, lower),右/下边界为"开区间"语义
cropped = img.crop(box)                 # 按 box 指定的矩形区域裁剪,返回新的 Image 对象(不修改原图)

display(cropped)                        # 在 Notebook 中直接渲染裁剪后的结果

2.3 可视化:裁剪前后对比

python 复制代码
import matplotlib.pyplot as plt  # Matplotlib:绘图与可视化
import seaborn as sns            # Seaborn:设置更美观的默认主题(这里主要用于风格)

sns.set_theme(style="whitegrid", font="SimHei", rc={"axes.unicode_minus": False})
# style="whitegrid":白底网格风格(对比更清晰)
# font="SimHei":中文字体,避免标题中文乱码
# axes.unicode_minus=False:避免负号显示为方块(某些中文字体环境常见问题)

fig, axes = plt.subplots(1, 2, figsize=(10, 4))  # 创建 1 行 2 列子图,画布大小 10×4 英寸
axes[0].imshow(img)                              # 左图显示原图(PIL.Image 可被 imshow 直接接受)
axes[0].set_title("原始图像")                    # 设置左图标题
axes[1].imshow(cropped)                          # 右图显示裁剪结果
axes[1].set_title("裁剪后图像")                  # 设置右图标题

for ax in axes:
    ax.axis("off")                               # 关闭坐标轴刻度与边框,让图像展示更干净

plt.tight_layout()                               # 自动调整子图间距,避免标题/图像重叠
plt.show()                                       # 显示整张图

裁剪操作仅改变图像的空间范围,不引入数值近似误差。

2.4 NumPy 视角:数组切片裁剪

python 复制代码
import numpy as np              # NumPy:数组表示与切片操作

arr = np.array(img)             # 将 PIL.Image 转为 NumPy 数组,形状通常为 (H, W, C)
cropped_np = arr[100:400, 100:400]  # NumPy 切片裁剪:先行(y/H)后列(x/W),等价于 box=(100,100,400,400)

从数组角度看,裁剪等价于二维切片操作,具有较低的计算成本。


3. 图像翻转

3.1 原理:坐标反射模型

翻转属于坐标反射变换,不涉及连续插值。

  • 水平翻转(左右翻转):

x′=W−1−x,y′=y x' = W - 1 - x,\quad y' = y x′=W−1−x,y′=y

  • 垂直翻转(上下翻转):

x′=x,y′=H−1−y x' = x,\quad y' = H - 1 - y x′=x,y′=H−1−y

其中 WWW、HHH 分别表示图像宽度与高度。

3.2 Pillow 接口:Image.transpose()

python 复制代码
flip_lr = img.transpose(Image.FLIP_LEFT_RIGHT)  # 左右翻转(沿 y 轴镜像),像素值不变,仅重排位置
flip_tb = img.transpose(Image.FLIP_TOP_BOTTOM)  # 上下翻转(沿 x 轴镜像),同样不做插值

3.3 可视化:水平/垂直翻转对比

python 复制代码
fig, axes = plt.subplots(1, 3, figsize=(12, 4))  # 创建 1×3 子图:原图/水平翻转/垂直翻转

axes[0].imshow(img)                               # 原图
axes[0].set_title("原始图像")

axes[1].imshow(flip_lr)                           # 水平翻转结果
axes[1].set_title("水平翻转")

axes[2].imshow(flip_tb)                           # 垂直翻转结果
axes[2].set_title("垂直翻转")

for ax in axes:
    ax.axis("off")                                # 隐藏坐标轴,突出图像内容

plt.tight_layout()                                # 自动布局
plt.show()                                        # 显示图像对比

翻转操作保持像素值不变,仅改变像素在空间中的排列顺序。

3.4 NumPy 视角:步进切片翻转

python 复制代码
flip_lr_np = arr[:, ::-1, :]  # 水平翻转:对宽度维 W 反向步进(::-1),行(y)与通道(c)不变
flip_tb_np = arr[::-1, :, :]  # 垂直翻转:对高度维 H 反向步进(::-1),列(x)与通道(c)不变

4. 图像旋转

4.1 原理:二维旋转与插值

二维旋转可以表示为线性变换:

x′y′\]\[cos⁡θ−sin⁡θsin⁡θcos⁡θ\]\[xy\] \\begin{bmatrix} x' \\\\ y' \\end{bmatrix} \\begin{bmatrix} \\cos\\theta \& -\\sin\\theta \\\\ \\sin\\theta \& \\cos\\theta \\end{bmatrix} \\begin{bmatrix} x \\\\ y \\end{bmatrix} \[x′y′\]\[cosθsinθ−sinθcosθ\]\[xy

旋转后像素坐标通常不再位于整数网格上,因此需要通过插值方法计算新像素值。

4.2 Pillow 接口:Image.rotate()

python 复制代码
rotated = img.rotate(45)                 # 旋转 45°,默认以图像中心为旋转中心;输出尺寸默认不变(可能裁切边缘)
rotated_expand = img.rotate(45, expand=True)  # expand=True:自动扩大画布以容纳完整旋转后的图像(常见于数据增强)

4.3 可视化:expand=True 的画布差异

python 复制代码
fig, axes = plt.subplots(1, 3, figsize=(12, 4))  # 创建 1×3 子图:原图/旋转不扩展/旋转并扩展

axes[0].imshow(img)                               # 原始图像
axes[0].set_title("原始图像")

axes[1].imshow(rotated)                           # rotate(45):画布不变,边缘可能被裁掉
axes[1].set_title("rotate(45)")

axes[2].imshow(rotated_expand)                    # rotate(45, expand=True):画布变大,内容尽量保留
axes[2].set_title("rotate(45, expand=True)")

for ax in axes:
    ax.axis("off")                                # 去掉坐标轴

plt.tight_layout()                                # 自动布局
plt.show()                                        # 展示对比结果

当启用 expand=True 时,画布尺寸会随旋转角度自动调整,以完整保留图像内容。


5. 图像缩放

5.1 原理:尺度变换与重采样

缩放通过对坐标轴进行线性变换实现:

x′=sxx,y′=syy x' = s_x x,\quad y' = s_y y x′=sxx,y′=syy

当缩放比例不为整数时,新像素值由插值算法估计。

5.2 Pillow 接口:Image.resize()

python 复制代码
methods = {
    "NEAREST": Image.NEAREST,    # 最近邻:最快、锯齿明显,适合标签/掩码(mask)这类离散值图像
    "BILINEAR": Image.BILINEAR,  # 双线性:速度与平滑度折中,常用于一般图像缩放
    "BICUBIC": Image.BICUBIC     # 双三次:更平滑、细节更好但更慢,常用于高质量缩放
}

5.3 可视化:缩放-插值方法对比

python 复制代码
fig, axes = plt.subplots(1, 3, figsize=(12, 4))   # 创建 1×3 子图用于对比不同插值结果

for ax, (name, method) in zip(axes, methods.items()):  # 同步遍历子图与插值方法
    resized = img.resize((256, 256), resample=method)  # resize:指定目标尺寸 (W, H),resample 控制插值策略
    ax.imshow(resized)                                 # 显示缩放结果
    ax.set_title(name)                                 # 标注插值方法名称
    ax.axis("off")                                     # 关闭坐标轴

plt.tight_layout()                                     # 自动布局
plt.show()                                             # 展示对比

不同插值方式在平滑性与边缘保留方面表现不同,应根据应用需求进行选择。


6. 仿射变换

6.1 原理:齐次坐标与仿射矩阵

仿射变换使用齐次坐标表示为:

x′y′1\]\[abcdef001\]\[xy1\] \\begin{bmatrix} x' \\\\ y' \\\\ 1 \\end{bmatrix} \\begin{bmatrix} a \& b \& c \\\\ d \& e \& f \\\\ 0 \& 0 \& 1 \\end{bmatrix} \\begin{bmatrix} x \\\\ y \\\\ 1 \\end{bmatrix} x′y′1 ad0be0cf1 xy1 该模型可以统一描述平移、旋转、缩放与剪切等几何操作。 #### 6.2 Pillow 接口:Image.transform(mode=AFFINE) ```python matrix = (1, 0.3, 0, 0.2, 1, 0) # Pillow 的 AFFINE 使用 6 参数矩阵 (a, b, c, d, e, f),对应: # x' = a*x + b*y + c # y' = d*x + e*y + f # 这里 b=0.3、d=0.2 引入剪切(shear),c=f=0 表示不平移 affine = img.transform( img.size, # 输出图像大小(这里保持与原图一致) Image.AFFINE, # 指定变换类型为仿射变换 matrix, # 6 参数仿射矩阵 resample=Image.BICUBIC # 重采样方式:双三次插值,减弱锯齿并提升观感 ) ``` #### 6.3 可视化:仿射变换效果对比 ```python fig, axes = plt.subplots(1, 2, figsize=(10, 4)) # 创建 1×2 子图:原图 vs 仿射结果 axes[0].imshow(img) # 原图 axes[0].set_title("原始图像") axes[1].imshow(affine) # 仿射变换后的图像 axes[1].set_title("仿射变换结果") for ax in axes: ax.axis("off") # 隐藏坐标轴 plt.tight_layout() # 自动布局 plt.show() # 展示结果 ``` ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/099ad283124447a98168c01dd876e104.png) 仿射变换在保持直线和平行关系的同时,对图像整体结构进行线性调整。 *** ** * ** *** ### 7. 批量几何变换:数据增强示例 ```python def geometric_augmentations(img): # 将多种几何变换打包返回,便于统一管理与可视化对比 return { "original": img, # 原始图像(基准) "rotated": img.rotate(30), # 旋转 30°(默认中心旋转,可能发生裁切) "flipped": img.transpose(Image.FLIP_LEFT_RIGHT), # 水平翻转(左右镜像) "scaled": img.resize((224, 224)) # 缩放到固定输入尺寸(常用于深度学习模型输入) } augmented = geometric_augmentations(img) # 获取增强结果字典:name -> Image fig, axes = plt.subplots(1, 4, figsize=(16, 4)) # 创建 1×4 子图用于展示四种版本 for ax, (name, im) in zip(axes, augmented.items()): # 同步遍历子图与增强图像 ax.imshow(im) # 显示当前增强结果 ax.set_title(name) # 用 key 作为标题 ax.axis("off") # 关闭坐标轴 plt.tight_layout() # 自动布局 plt.show() # 展示增强对比 ``` ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/eb1cf5fea0c3493980409638484b5e13.png) *** ** * ** *** ### 8. NumPy 表示:维度变化与数组形状 ```python arr = np.array(img) # 原图转为 NumPy 数组,通常形状为 (H, W, C) arr_rotated = np.array(rotated_expand) # 旋转并扩展后的图像转数组(H/W 可能变化) print(arr.shape) # 打印原图数组形状:验证高度、宽度、通道数 print(arr_rotated.shape) # 打印旋转后数组形状:expand=True 往往导致 H/W 变大 ``` > (512, 512, 3) > > (726, 726, 3) 几何变换主要影响数组的空间维度,通道维度保持不变。 *** ** * ** *** ### 9. 总结 本章围绕 Pillow 提供的几何变换接口,对裁剪、翻转、旋转、缩放以及仿射变换的实现方式与数学背景进行了系统说明。通过结合 NumPy 表示与可视化结果,可以直观理解几何变换对图像空间结构的影响。这些操作构成了图像预处理与数据增强的重要基础。

相关推荐
liuyunshengsir3 分钟前
PyTorch 动态量化(Dynamic Quantization)
人工智能·pytorch·python
电子云与长程纠缠12 分钟前
UE5制作六边形包裹球体效果
开发语言·python·ue5
DFT计算杂谈21 分钟前
KPROJ编译教程
java·前端·python·算法·conda
念恒123061 小时前
Python(循环中断)
开发语言·python
tsfy20031 小时前
Python 处理中文文件名的3个坑(附 Flask 上传解决函数)
开发语言·python·flask·文件上传·中文编码
AI技术控1 小时前
KV Cache 缓存机制的原理和应用:从 Transformer 推理到大模型服务优化
人工智能·python·深度学习·缓存·自然语言处理·transformer
vx-程序开发2 小时前
基于机器学习的动漫可视化系统的设计与实现-计算机毕业设计源码08339
java·c++·spring boot·python·spring·django·php
爱睡懒觉的焦糖玛奇朵2 小时前
【从视频到数据集:焦糖玛奇朵的魔法工具Video To YOLO Dataset】
人工智能·python·学习·yolo·音视频
石榴树下的七彩鱼2 小时前
医疗票据 OCR 识别 API 多场景落地指南:医保结算 + 商保理赔 + 医疗信息化(附 Python/Java 完整示例)
java·python·ocr·石榴智能·医疗票据ocr·医保结算·ocrapi
idingzhi3 小时前
A股量化策略日报(2026年05月22日)
android·开发语言·python·kotlin