5.1 Pillow 库概述
5.1.1 Pillow 简介与特点
Pillow 是 Python 图像库(PIL)的现代分支和继承者。PIL 最初由 Fredrik Lundh 于 1995 年开发,曾是 Python 最早的图像处理库之一。由于 PIL 在 2009 年后停止维护,Alex Clark 等人创建了 Pillow 项目,在保持向后兼容的同时持续更新。如今,Pillow 已成为 Python 生态中最流行的图像处理库,每月下载量巨大。
Pillow 的设计理念是简洁易用,提供直观的 API 和丰富的功能。与 OpenCV 相比,Pillow 更专注于图像的基本操作和格式处理,而非复杂的计算机视觉算法。它特别适合以下场景:文件读写、格式转换、基本几何变换(裁剪、缩放、旋转)、增强(滤镜、色彩调整)、绘制(文字、图形)以及元数据处理。
Pillow 支持超过 30 种图像格式,包括常见的 JPEG、PNG、GIF、BMP、TIFF、WebP,以及一些专业格式如 ICO、PPM 等。它对各种格式的读写进行了优化,允许设置压缩参数和质量。对于 GIF 支持动画,PNG 支持透明通道,JPEG 支持渐进式编码。
5.1.2 Pillow 与 OpenCV 的比较
Pillow 和 OpenCV 是 Python 图像处理领域的两大支柱,各有侧重。下表对比了它们的核心特性:
| 特性 | Pillow | OpenCV |
|---|---|---|
| 主要用途 | 图像处理、格式转换 | 计算机视觉、复杂处理 |
| 核心语言 | Python/C | C++ |
| 图像表示 | PIL.Image 对象 | NumPy 数组 |
| 格式支持 | 30+ 种 | 常见格式 |
| 学习曲线 | 简单易学 | 相对复杂 |
| 性能 | 中等 | 高 |
| 深度学习 | 需转换 | 直接支持 |
实际项目中常结合使用:用 Pillow 做读取和格式转换,用 OpenCV 做复杂算法。两者转换也很方便,Pillow 的 Image 对象可直接转为 NumPy 数组。
5.2 图像读取与写入
5.2.1 基本读写操作
Pillow 提供了简洁的 API 进行图像的读取和写入。Image.open() 用于读取文件,返回 Image 对象;Image.save() 用于保存,可根据扩展名自动识别格式。下面是一个封装好的 IO 类示例,展示了如何批量处理和保存不同格式:
""" Pillow 图像读写操作详解 演示各种图像格式的读取和写入方法 """
from PIL import Image, ExifTags
import numpy as np
import os
import io
class PillowImageIO:
SUPPORTED_FORMATS = {
'read': ['JPEG', 'PNG', 'GIF', 'BMP', 'TIFF', 'WebP'],
'write': ['JPEG', 'PNG', 'GIF', 'BMP', 'TIFF', 'WebP']
}
def __init__(self):
pass
def open_image(self, file_path: str) -> Image.Image:
try:
return Image.open(file_path)
except Exception as e:
print(f"无法打开图像:{e}")
return None
def save_image(self, image: Image.Image, file_path: str, format=None, **kwargs) -> bool:
try:
output_dir = os.path.dirname(file_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
image.save(file_path, format=format, **kwargs)
return True
except Exception as e:
print(f"保存图像失败:{e}")
return False
def convert_format(self, input_path: str, output_path: str, output_format=None, **kwargs) -> bool:
image = self.open_image(input_path)
if image is None:
return False
return self.save_image(image, output_path, format=output_format, **kwargs)
def batch_convert(self, input_dir: str, output_dir: str, output_format='PNG') -> dict:
if not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
results = {'total': 0, 'success': 0, 'failed': 0}
for filename in os.listdir(input_dir):
if filename.lower().endswith(('.jpg', '.png', '.bmp')):
input_file = os.path.join(input_dir, filename)
stem = os.path.splitext(filename)[0]
output_file = os.path.join(output_dir, f'{stem}.{output_format.lower()}')
results['total'] += 1
if self.convert_format(input_file, output_file):
results['success'] += 1
else:
results['failed'] += 1
return results
5.2.2 图像模式与转换
理解图像模式至关重要,不同的模式对应不同的颜色空间和像素表示。Pillow 支持多种模式,从简单的二值图到复杂的 LAB 空间。
| 模式 | 说明 | 通道数 | 每像素位数 |
|---|---|---|---|
| 1 | 二值图像 | 1 | 1 |
| L | 灰度图像 | 1 | 8 |
| RGB | 真彩色 | 3 | 24 |
| RGBA | 带透明通道 | 4 | 32 |
| CMYK | 印刷四色 | 4 | 32 |
下面的代码展示了如何进行模式间的灵活转换,特别是处理透明度和灰度时的注意事项:
from PIL import Image
import numpy as np
class ImageModeConverter:
def __init__(self, image: Image.Image):
self.image = image
self.original_mode = image.mode
def to_grayscale(self, method='standard') -> Image.Image:
if method == 'standard':
return self.image.convert('L')
rgb = self.image.convert('RGB')
arr = np.array(rgb, dtype=np.float64)
# 使用亮度公式计算灰度
gray = 0.299 * arr[:,:,0] + 0.587 * arr[:,:,1] + 0.114 * arr[:,:,2]
return Image.fromarray(gray.astype(np.uint8), mode='L')
def to_rgba(self, background_color=(255, 255, 255)) -> Image.Image:
if self.image.mode == 'RGBA':
return self.image.copy()
# 注意:非 RGBA 转 RGBA 需要指定背景色填充透明通道
return self.image.convert('RGBA')
def to_binary(self, threshold=128) -> Image.Image:
gray = self.image.convert('L')
return gray.point(lambda x: 255 if x > threshold else 0, mode='1')
5.3 图像基本操作
5.3.1 几何变换
Pillow 提供了丰富的几何变换功能,包括裁剪、缩放、旋转、翻转等。这些操作默认返回新的 Image 对象,不会修改原始数据。在实际应用中,我们常需要根据目标尺寸自适应调整图片。
from PIL import Image, ImageOps
class PillowGeometry:
def __init__(self, image: Image.Image):
self.image = image
self.width, self.height = image.size
def resize_contain(self, size: tuple, background_color=(255, 255, 255)) -> Image.Image:
"""缩放并包含在指定尺寸内(保持宽高比,不足部分填充)"""
return ImageOps.fit(self.image, size, method=Image.Resampling.LANCZOS, bleed=0.0, centering=(0.5, 0.5))
def resize_cover(self, size: tuple) -> Image.Image:
"""缩放并覆盖指定尺寸(保持宽高比,可能裁剪)"""
ratio = max(size[0] / self.width, size[1] / self.height)
new_size = (int(self.width * ratio), int(self.height * ratio))
resized = self.image.resize(new_size, resample=Image.Resampling.LANCZOS)
left = (resized.width - size[0]) // 2
upper = (resized.height - size[1]) // 2
return resized.crop((left, upper, left + size[0], upper + size[1]))
def create_thumbnail(self, size: tuple) -> Image.Image:
img_copy = self.image.copy()
img_copy.thumbnail(size, resample=Image.Resampling.LANCZOS)
return img_copy
5.3.2 图像增强
通过 ImageEnhance 和 ImageFilter 模块,我们可以轻松调整图像的视觉效果。无论是提升清晰度还是添加艺术滤镜,Pillow 都能胜任。
from PIL import ImageEnhance, ImageFilter
class PillowEnhance:
def __init__(self, image: Image.Image):
self.image = image.convert('RGB')
def adjust_brightness(self, factor: float) -> Image.Image:
enhancer = ImageEnhance.Brightness(self.image)
return enhancer.enhance(factor)
def apply_filter(self, filter_type: str, **kwargs) -> Image.Image:
filters = {
'blur': ImageFilter.BLUR,
'sharpen': ImageFilter.SHARPEN,
'emboss': ImageFilter.EMBOSS,
'gaussian': ImageFilter.GaussianBlur(radius=kwargs.get('radius', 2))
}
if filter_type in filters:
return self.image.filter(filters[filter_type])
return self.image.copy()
5.4 图像绘制与文字处理
5.4.1 图像绘制
ImageDraw 模块允许我们在图像上绘制直线、矩形、圆形、多边形以及文字。这对于生成标注图、水印或简单图表非常有用。
from PIL import ImageDraw, ImageFont
class PillowDrawing:
def __init__(self, image=None, size=(640, 480), background='white'):
if image is not None:
self.image = image.copy()
else:
self.image = Image.new('RGB', size, background)
self.draw = ImageDraw.Draw(self.image)
self.width, self.height = self.image.size
def draw_text_centered(self, text: str, color='black', font=None):
bbox = self.draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (self.width - text_width) // 2
y = (self.height - text_height) // 2
self.draw.text((x, y), text, fill=color, font=font)
def draw_arrow(self, start: tuple, end: tuple, color='black', width=2):
self.draw.line([start, end], fill=color, width=width)
# 这里可以进一步计算箭头头部顶点绘制多边形
5.5 本章小结
本章详细介绍了 Pillow 库的核心用法,涵盖了从基础的文件读写、格式转换,到进阶的几何变换、色彩增强及绘图功能。Pillow 凭借其简洁的 API 和广泛的格式支持,成为 Python 图像处理的首选工具之一。
虽然 OpenCV 在计算机视觉领域更为强大,但 Pillow 在处理静态图像、UI 素材生成及格式转换方面具有不可替代的优势。两者结合使用往往能发挥最大效能。下一章我们将深入探讨色彩空间转换与通道操作的底层原理。

