欢迎光临
我们一直在努力

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

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

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

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


1. 几何变换的坐标表示

一幅二维数字图像可以表示为定义在规则网格上的函数:

I

:

(

x

,

y

)

v

,

v

R

c

I: (x, y) \\rightarrow \\mathbf{v}, \\quad \\mathbf{v} \\in \\mathbb{R}^c

I:(x,y)v,vRc

其中

(

x

,

y

)

(x, y)

(x,y) 为像素坐标,

c

c

c 为通道数。 几何变换通过构造坐标映射函数

T

T

T,在新的坐标系下重建图像:

I

(

x

,

y

)

=

I

(

T

1

(

x

,

y

)

)

I'(x', y') = I(T^{-1}(x', y'))

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


2. 图像裁剪

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

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

2.2 Pillow 接口:Image.crop()

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 可视化:裁剪前后对比

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 视角:数组切片裁剪

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=W1x,y=y

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

x

=

x

,

y

=

H

1

y

x' = x,\\quad y' = H – 1 – y

x=x,y=H1y

其中

W

W

W

H

H

H 分别表示图像宽度与高度。

3.2 Pillow 接口:Image.transpose()

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

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

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 视角:步进切片翻转

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

θ

]

[

x

y

]

\\begin{bmatrix} x' \\\\ y' \\end{bmatrix} \\begin{bmatrix} \\cos\\theta & -\\sin\\theta \\\\ \\sin\\theta & \\cos\\theta \\end{bmatrix} \\begin{bmatrix} x \\\\ y \\end{bmatrix}

[xy][cosθsinθsinθcosθ][xy]

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

4.2 Pillow 接口:Image.rotate()

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

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

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

=

s

x

x

,

y

=

s

y

y

x' = s_x x,\\quad y' = s_y y

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

5.2 Pillow 接口:Image.resize()

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

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

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

]

[

a

b

c

d

e

f

0

0

1

]

[

x

y

1

]

\\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}

xy1

ad0be0cf1

xy1

该模型可以统一描述平移、旋转、缩放与剪切等几何操作。

6.2 Pillow 接口:Image.transform(mode=AFFINE)

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 可视化:仿射变换效果对比

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() # 展示结果

在这里插入图片描述

仿射变换在保持直线和平行关系的同时,对图像整体结构进行线性调整。


7. 批量几何变换:数据增强示例

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() # 展示增强对比

在这里插入图片描述


8. NumPy 表示:维度变化与数组形状

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 表示与可视化结果,可以直观理解几何变换对图像空间结构的影响。这些操作构成了图像预处理与数据增强的重要基础。

赞(0)
未经允许不得转载:171主机测评 » Pillow 图像几何变换与仿射操作
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址