【Python图像处理】5 Pillow图像处理与格式转换

摘要 :本文详细介绍Pillow库的使用方法,包括图像的读取、写入、格式转换、基本操作和高级功能。Pillow是Python中最流行的图像处理库之一,提供了丰富的图像操作功能和广泛的格式支持。文章通过大量综合性代码示例,演示Pillow的各种应用场景,并介绍如何使用GPT-5.4辅助编写Pillow代码。由于国内无法访问OpenAI官网,因此使用国内镜像站可以注册使用GPT-5.4最新模型。注册入口:AIGCBAR镜像站。请广大读者遵守法律法规,切勿翻墙访问境外网站,使用国内合法镜像站即可满足学习需求。

5.1 Pillow库概述

5.1.1 Pillow简介与特点

Pillow是Python图像库(PIL,Python Imaging Library)的现代分支和继承者。PIL最初由Fredrik Lundh于1995年开发,是Python最早的图像处理库之一。由于PIL的开发在2009年后停滞,Alex Clark等人创建了Pillow项目,在保持向后兼容的同时持续维护和开发新功能。如今,Pillow已经成为Python生态系统中最流行的图像处理库之一,每月下载量超过数千万次。

Pillow的设计理念是简洁易用,它提供了直观的API和丰富的图像处理功能。与OpenCV相比,Pillow更专注于图像的基本操作和格式处理,而不是计算机视觉算法。Pillow特别适合以下场景:图像文件的读取和写入、图像格式转换、基本图像操作(裁剪、缩放、旋转)、图像增强(滤镜、色彩调整)、图像绘制(添加文字、图形)、图像元数据处理(EXIF信息)。

Pillow支持超过30种图像格式,包括常见的JPEG、PNG、GIF、BMP、TIFF、WebP,以及一些专业格式如ICO、PPM、SGI等。Pillow对各种格式的读写进行了优化,可以根据需要选择不同的压缩参数和质量设置。对于GIF格式,Pillow支持动画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()方法用于将Image对象保存为文件,可以根据文件扩展名自动识别格式。以下代码展示了Pillow的基本读写操作。

python 复制代码
"""
Pillow图像读写操作详解
演示各种图像格式的读取和写入方法
兼容Python 3.13
"""

from PIL import Image, ImageOps, ExifTags
import numpy as np
from typing import Optional, Tuple, List, Dict, Any
import os
import io


class PillowImageIO:
    """
    Pillow图像输入输出操作类
    提供图像读取、写入、格式转换等功能
    """

    SUPPORTED_FORMATS = {
        'read': ['JPEG', 'PNG', 'GIF', 'BMP', 'TIFF', 'WebP', 'PPM',
                 'PNG', 'ICO', 'SGI', 'TGA', 'Palm', 'PCX', 'XBM'],
        'write': ['JPEG', 'PNG', 'GIF', 'BMP', 'TIFF', 'WebP', 'PPM',
                  'ICO', 'TGA', 'PCX']
    }

    def __init__(self):
        """初始化图像IO操作器"""
        pass

    def open_image(self, file_path: str) -> Optional[Image.Image]:
        """
        打开图像文件

        参数:
            file_path: 图像文件路径

        返回:
            Image对象,失败返回None
        """
        try:
            image = Image.open(file_path)
            return image
        except Exception as e:
            print(f"无法打开图像: {e}")
            return None

    def open_from_bytes(self, data: bytes) -> Optional[Image.Image]:
        """
        从字节数据打开图像

        参数:
            data: 图像字节数据

        返回:
            Image对象
        """
        try:
            return Image.open(io.BytesIO(data))
        except Exception as e:
            print(f"无法解析图像数据: {e}")
            return None

    def save_image(self,
                   image: Image.Image,
                   file_path: str,
                   format: Optional[str] = None,
                   **kwargs) -> bool:
        """
        保存图像文件

        参数:
            image: Image对象
            file_path: 输出路径
            format: 输出格式(自动检测如果为None)
            **kwargs: 格式特定参数

        返回:
            是否保存成功
        """
        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 save_jpeg(self,
                  image: Image.Image,
                  file_path: str,
                  quality: int = 95,
                  progressive: bool = False,
                  optimize: bool = False) -> bool:
        """
        保存为JPEG格式

        参数:
            image: Image对象
            file_path: 输出路径
            quality: 质量(1-100)
            progressive: 是否渐进式
            optimize: 是否优化

        返回:
            是否保存成功
        """
        # 转换为RGB模式(JPEG不支持透明通道)
        if image.mode in ('RGBA', 'P'):
            image = image.convert('RGB')

        return self.save_image(
            image, file_path,
            quality=quality,
            progressive=progressive,
            optimize=optimize
        )

    def save_png(self,
                 image: Image.Image,
                 file_path: str,
                 compress_level: int = 6,
                 optimize: bool = False) -> bool:
        """
        保存为PNG格式

        参数:
            image: Image对象
            file_path: 输出路径
            compress_level: 压缩级别(0-9)
            optimize: 是否优化

        返回:
            是否保存成功
        """
        return self.save_image(
            image, file_path,
            compress_level=compress_level,
            optimize=optimize
        )

    def save_webp(self,
                  image: Image.Image,
                  file_path: str,
                  quality: int = 80,
                  lossless: bool = False) -> bool:
        """
        保存为WebP格式

        参数:
            image: Image对象
            file_path: 输出路径
            quality: 质量(1-100)
            lossless: 是否无损压缩

        返回:
            是否保存成功
        """
        return self.save_image(
            image, file_path,
            quality=quality,
            lossless=lossless
        )

    def save_tiff(self,
                  image: Image.Image,
                  file_path: str,
                  compression: str = 'tiff_deflate') -> bool:
        """
        保存为TIFF格式

        参数:
            image: Image对象
            file_path: 输出路径
            compression: 压缩方法

        返回:
            是否保存成功
        """
        return self.save_image(
            image, file_path,
            compression=compression
        )

    def convert_format(self,
                       input_path: str,
                       output_path: str,
                       output_format: Optional[str] = None,
                       **kwargs) -> bool:
        """
        转换图像格式

        参数:
            input_path: 输入路径
            output_path: 输出路径
            output_format: 输出格式
            **kwargs: 格式参数

        返回:
            是否转换成功
        """
        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: str = 'PNG',
                      input_extensions: List[str] = None) -> Dict[str, Any]:
        """
        批量转换图像格式

        参数:
            input_dir: 输入目录
            output_dir: 输出目录
            output_format: 输出格式
            input_extensions: 输入文件扩展名列表

        返回:
            转换结果统计
        """
        if input_extensions is None:
            input_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']

        # 确保输出目录存在
        if not os.path.exists(output_dir):
            os.makedirs(output_dir, exist_ok=True)

        results = {
            'total': 0,
            'success': 0,
            'failed': 0,
            'failed_files': []
        }

        # 遍历输入目录中的文件
        for filename in os.listdir(input_dir):
            file_ext = os.path.splitext(filename)[1].lower()
            if file_ext in input_extensions:
                input_file = os.path.join(input_dir, filename)
                file_stem = os.path.splitext(filename)[0]
                output_file = os.path.join(output_dir, f'{file_stem}.{output_format.lower()}')

                results['total'] += 1
                if self.convert_format(input_file, output_file):
                    results['success'] += 1
                else:
                    results['failed'] += 1
                    results['failed_files'].append(filename)

        return results

    def get_image_info(self, file_path: str) -> Dict[str, Any]:
        """
        获取图像信息

        参数:
            file_path: 图像文件路径

        返回:
            图像信息字典
        """
        image = self.open_image(file_path)
        if image is None:
            return {'success': False, 'error': '无法打开图像'}

        info = {
            'success': True,
            'file_path': file_path,
            'format': image.format,
            'mode': image.mode,
            'size': image.size,
            'width': image.width,
            'height': image.height,
            'bits': image.bits if hasattr(image, 'bits') else None,
            'is_animated': getattr(image, 'is_animated', False),
            'n_frames': getattr(image, 'n_frames', 1)
        }

        # 获取EXIF信息
        exif = self.get_exif_data(image)
        if exif:
            info['exif'] = exif

        return info

    def get_exif_data(self, image: Image.Image) -> Optional[Dict[str, Any]]:
        """
        获取EXIF元数据

        参数:
            image: Image对象

        返回:
            EXIF数据字典
        """
        try:
            exif_data = image._getexif()
            if exif_data is None:
                return None

            result = {}
            for tag_id, value in exif_data.items():
                tag = ExifTags.TAGS.get(tag_id, tag_id)
                result[tag] = value

            return result
        except Exception:
            return None

    def create_test_image(self,
                          width: int = 640,
                          height: int = 480,
                          mode: str = 'RGB',
                          pattern: str = 'gradient') -> Image.Image:
        """
        创建测试图像

        参数:
            width: 宽度
            height: 高度
            mode: 颜色模式
            pattern: 图案类型

        返回:
            测试图像
        """
        if pattern == 'gradient':
            # 创建渐变图像
            r = np.linspace(0, 255, width, dtype=np.uint8)
            g = np.linspace(0, 255, height, dtype=np.uint8)
            r_grid, g_grid = np.meshgrid(r, g)
            b = np.full((height, width), 128, dtype=np.uint8)
            arr = np.stack([r_grid, g_grid, b], axis=2)
            return Image.fromarray(arr, mode='RGB')

        elif pattern == 'checkerboard':
            # 创建棋盘格
            block_size = 50
            arr = np.zeros((height, width, 3), dtype=np.uint8)
            for y in range(0, height, block_size * 2):
                for x in range(0, width, block_size * 2):
                    arr[y:y+block_size, x:x+block_size] = [255, 255, 255]
                    arr[y+block_size:y+block_size*2,
                        x+block_size:x+block_size*2] = [255, 255, 255]
            return Image.fromarray(arr, mode='RGB')

        elif pattern == 'noise':
            # 随机噪声
            arr = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8)
            return Image.fromarray(arr, mode='RGB')

        else:
            return Image.new(mode, (width, height), color='white')


def demonstrate_image_io():
    """
    演示图像读写操作
    """
    io_handler = PillowImageIO()

    print("Pillow图像读写演示")
    print("=" * 50)

    # 创建测试图像
    test_image = io_handler.create_test_image(640, 480, 'RGB', 'gradient')
    print(f"创建测试图像: {test_image.size}, 模式: {test_image.mode}")

    # 保存为不同格式 - 使用当前目录下的测试文件夹
    current_dir = os.path.dirname(os.path.abspath(__file__))
    test_dir = os.path.join(current_dir, "pillow_test")

    # 确保测试目录存在
    if not os.path.exists(test_dir):
        os.makedirs(test_dir, exist_ok=True)

    # JPEG
    jpeg_path = os.path.join(test_dir, "test.jpg")
    io_handler.save_jpeg(test_image, jpeg_path, quality=95)
    print(f"保存JPEG: {os.path.getsize(jpeg_path) / 1024:.1f} KB")

    # PNG
    png_path = os.path.join(test_dir, "test.png")
    io_handler.save_png(test_image, png_path, compress_level=6)
    print(f"保存PNG: {os.path.getsize(png_path) / 1024:.1f} KB")

    # WebP
    webp_path = os.path.join(test_dir, "test.webp")
    io_handler.save_webp(test_image, webp_path, quality=80)
    print(f"保存WebP: {os.path.getsize(webp_path) / 1024:.1f} KB")

    # 获取图像信息
    info = io_handler.get_image_info(jpeg_path)
    print(f"\n图像信息:")
    print(f"  格式: {info['format']}")
    print(f"  尺寸: {info['size']}")
    print(f"  模式: {info['mode']}")

    return test_image


if __name__ == "__main__":
    image = demonstrate_image_io()
    print("\n图像读写演示完成")

5.2.2 图像模式与转换

Pillow支持多种图像模式,每种模式对应不同的颜色空间和像素表示方式。理解图像模式对于正确处理图像非常重要。以下表格列出了Pillow支持的主要图像模式。

模式 说明 通道数 每像素位数
1 二值图像 1 1
L 灰度图像 1 8
P 调色板图像 1 8
RGB 真彩色 3 24
RGBA 带透明通道的真彩色 4 32
CMYK 印刷四色 4 32
YCbCr 视频颜色空间 3 24
LAB CIE Lab颜色空间 3 24
HSV 色相饱和度明度 3 24
I 32位整数灰度 1 32
F 32位浮点灰度 1 32

以下代码展示了图像模式转换的各种操作。

python 复制代码
"""
Pillow图像模式转换详解
演示各种颜色模式之间的转换
兼容Python 3.13
"""

from PIL import Image
import numpy as np
from typing import Optional, Tuple, List


class ImageModeConverter:
    """
    图像模式转换类
    """
    
    def __init__(self, image: Image.Image):
        """
        初始化模式转换器
        
        参数:
            image: 输入图像
        """
        self.image = image
        self.original_mode = image.mode
    
    def to_grayscale(self, method: str = 'standard') -> Image.Image:
        """
        转换为灰度图像
        
        参数:
            method: 转换方法
                    'standard': 标准转换
                    'luminance': 亮度转换
                    'desaturation': 去饱和
                    'average': 平均值
                    
        返回:
            灰度图像
        """
        if method == 'standard':
            return self.image.convert('L')
        
        # 转换为RGB
        rgb = self.image.convert('RGB')
        arr = np.array(rgb, dtype=np.float64)
        
        if method == 'luminance':
            # 使用亮度公式
            gray = 0.299 * arr[:, :, 0] + 0.587 * arr[:, :, 1] + 0.114 * arr[:, :, 2]
        elif method == 'desaturation':
            # 去饱和
            gray = np.max(arr, axis=2) / 2 + np.min(arr, axis=2) / 2
        elif method == 'average':
            # 平均值
            gray = np.mean(arr, axis=2)
        else:
            gray = np.mean(arr, axis=2)
        
        return Image.fromarray(gray.astype(np.uint8), mode='L')
    
    def to_rgb(self) -> Image.Image:
        """
        转换为RGB模式
        
        返回:
            RGB图像
        """
        return self.image.convert('RGB')
    
    def to_rgba(self, 
                background_color: Tuple[int, int, int] = (255, 255, 255)) -> Image.Image:
        """
        转换为RGBA模式
        
        参数:
            background_color: 背景色(用于无透明通道的图像)
            
        返回:
            RGBA图像
        """
        if self.image.mode == 'RGBA':
            return self.image.copy()
        
        if self.image.mode == 'P':
            # 调色板模式可能有透明通道
            return self.image.convert('RGBA')
        
        if self.image.mode == 'L':
            # 灰度图转RGBA
            rgb = self.image.convert('RGB')
            return rgb.convert('RGBA')
        
        return self.image.convert('RGBA')
    
    def to_binary(self, threshold: int = 128) -> Image.Image:
        """
        转换为二值图像
        
        参数:
            threshold: 阈值
            
        返回:
            二值图像
        """
        gray = self.image.convert('L')
        return gray.point(lambda x: 255 if x > threshold else 0, mode='1')
    
    def to_cmyk(self) -> Image.Image:
        """
        转换为CMYK模式
        
        返回:
            CMYK图像
        """
        rgb = self.image.convert('RGB')
        return rgb.convert('CMYK')
    
    def to_palette(self, 
                   palette: str = 'WEB',
                   colors: int = 256) -> Image.Image:
        """
        转换为调色板模式
        
        参数:
            palette: 调色板类型
            colors: 颜色数量
            
        返回:
            调色板图像
        """
        rgb = self.image.convert('RGB')
        
        if palette == 'WEB':
            return rgb.convert('P', palette=Image.Palette.WEB)
        elif palette == 'ADAPTIVE':
            return rgb.convert('P', palette=Image.Palette.ADAPTIVE, colors=colors)
        else:
            return rgb.convert('P')
    
    def to_hsv(self) -> Image.Image:
        """
        转换为HSV模式
        
        返回:
            HSV图像
        """
        return self.image.convert('HSV')
    
    def to_lab(self) -> Image.Image:
        """
        转换为LAB模式
        
        返回:
            LAB图像
        """
        return self.image.convert('LAB')
    
    def separate_channels(self) -> List[Image.Image]:
        """
        分离颜色通道
        
        返回:
            通道图像列表
        """
        return list(self.image.split())
    
    def merge_channels(self, channels: List[Image.Image], mode: str) -> Image.Image:
        """
        合并颜色通道
        
        参数:
            channels: 通道图像列表
            mode: 输出模式
            
        返回:
            合并后的图像
        """
        return Image.merge(mode, channels)
    
    def swap_channels(self, order: Tuple[int, ...]) -> Image.Image:
        """
        交换通道顺序
        
        参数:
            order: 新的通道顺序
            
        返回:
            交换后的图像
        """
        channels = self.separate_channels()
        reordered = [channels[i] for i in order]
        return Image.merge(self.image.mode, reordered)
    
    def extract_channel(self, channel: int) -> Image.Image:
        """
        提取单个通道
        
        参数:
            channel: 通道索引
            
        返回:
            单通道图像
        """
        channels = self.separate_channels()
        return channels[channel]
    
    def set_channel(self, 
                    channel_index: int, 
                    channel_image: Image.Image) -> Image.Image:
        """
        设置单个通道
        
        参数:
            channel_index: 通道索引
            channel_image: 通道图像
            
        返回:
            修改后的图像
        """
        channels = self.separate_channels()
        channels[channel_index] = channel_image
        return Image.merge(self.image.mode, channels)
    
    def get_mode_info(self) -> dict:
        """
        获取当前模式信息
        
        返回:
            模式信息字典
        """
        return {
            'mode': self.image.mode,
            'bands': self.image.getbands(),
            'bits': self.image.bits if hasattr(self.image, 'bits') else 8,
            'size': self.image.size
        }


def demonstrate_mode_conversion():
    """
    演示模式转换
    """
    # 创建测试图像
    image = Image.new('RGB', (200, 200), color=(255, 128, 64))
    
    converter = ImageModeConverter(image)
    
    print("图像模式转换演示")
    print("=" * 50)
    
    # 原始模式信息
    info = converter.get_mode_info()
    print(f"原始模式: {info['mode']}")
    print(f"通道: {info['bands']}")
    
    # 转换为灰度
    gray = converter.to_grayscale()
    print(f"\n灰度模式: {gray.mode}")
    
    # 转换为RGBA
    rgba = converter.to_rgba()
    print(f"RGBA模式: {rgba.mode}")
    
    # 转换为二值
    binary = converter.to_binary(128)
    print(f"二值模式: {binary.mode}")
    
    # 转换为CMYK
    cmyk = converter.to_cmyk()
    print(f"CMYK模式: {cmyk.mode}")
    
    # 分离通道
    channels = converter.separate_channels()
    print(f"\n分离通道: {[ch.mode for ch in channels]}")
    
    # 不同灰度转换方法比较
    print("\n灰度转换方法比较:")
    for method in ['standard', 'luminance', 'desaturation', 'average']:
        gray = converter.to_grayscale(method)
        arr = np.array(gray)
        print(f"  {method}: 均值={arr.mean():.2f}")
    
    return {
        'original': image,
        'gray': gray,
        'rgba': rgba,
        'binary': binary
    }


if __name__ == "__main__":
    results = demonstrate_mode_conversion()
    print("\n模式转换演示完成")

5.3 图像基本操作

5.3.1 几何变换

Pillow提供了丰富的几何变换功能,包括裁剪、缩放、旋转、翻转等操作。这些操作都返回新的Image对象,不会修改原始图像。以下代码展示了各种几何变换操作。

python 复制代码
"""
Pillow图像几何变换详解
演示裁剪、缩放、旋转、翻转等操作
兼容Python 3.13
"""

from PIL import Image, ImageOps
import numpy as np
from typing import Tuple, Optional, List
import math


class PillowGeometry:
    """
    Pillow几何变换类
    """
    
    def __init__(self, image: Image.Image):
        """
        初始化几何变换器
        
        参数:
            image: 输入图像
        """
        self.image = image
        self.width, self.height = image.size
    
    def crop(self, 
             box: Tuple[int, int, int, int]) -> Image.Image:
        """
        裁剪图像
        
        参数:
            box: 裁剪区域 (left, upper, right, lower)
            
        返回:
            裁剪后的图像
        """
        return self.image.crop(box)
    
    def crop_center(self, 
                    width: int, 
                    height: int) -> Image.Image:
        """
        中心裁剪
        
        参数:
            width: 裁剪宽度
            height: 裁剪高度
            
        返回:
            裁剪后的图像
        """
        left = (self.width - width) // 2
        upper = (self.height - height) // 2
        right = left + width
        lower = upper + height
        
        return self.image.crop((left, upper, right, lower))
    
    def crop_to_aspect_ratio(self, 
                             ratio: float,
                             mode: str = 'center') -> Image.Image:
        """
        按宽高比裁剪
        
        参数:
            ratio: 目标宽高比 (width/height)
            mode: 裁剪模式 ('center', 'top', 'bottom', 'left', 'right')
            
        返回:
            裁剪后的图像
        """
        current_ratio = self.width / self.height
        
        if abs(current_ratio - ratio) < 0.01:
            return self.image.copy()
        
        if current_ratio > ratio:
            # 当前图像更宽,裁剪宽度
            new_width = int(self.height * ratio)
            
            if mode == 'center':
                left = (self.width - new_width) // 2
            elif mode == 'left':
                left = 0
            elif mode == 'right':
                left = self.width - new_width
            else:
                left = (self.width - new_width) // 2
            
            return self.image.crop((left, 0, left + new_width, self.height))
        
        else:
            # 当前图像更高,裁剪高度
            new_height = int(self.width / ratio)
            
            if mode == 'center':
                upper = (self.height - new_height) // 2
            elif mode == 'top':
                upper = 0
            elif mode == 'bottom':
                upper = self.height - new_height
            else:
                upper = (self.height - new_height) // 2
            
            return self.image.crop((0, upper, self.width, upper + new_height))
    
    def resize(self, 
               size: Tuple[int, int],
               resample: int = Image.Resampling.LANCZOS) -> Image.Image:
        """
        调整图像大小
        
        参数:
            size: 目标尺寸 (width, height)
            resample: 重采样方法
            
        返回:
            调整后的图像
        """
        return self.image.resize(size, resample=resample)
    
    def resize_to_width(self, 
                        width: int,
                        resample: int = Image.Resampling.LANCZOS) -> Image.Image:
        """
        按宽度缩放(保持宽高比)
        
        参数:
            width: 目标宽度
            resample: 重采样方法
            
        返回:
            缩放后的图像
        """
        ratio = width / self.width
        height = int(self.height * ratio)
        return self.image.resize((width, height), resample=resample)
    
    def resize_to_height(self, 
                         height: int,
                         resample: int = Image.Resampling.LANCZOS) -> Image.Image:
        """
        按高度缩放(保持宽高比)
        
        参数:
            height: 目标高度
            resample: 重采样方法
            
        返回:
            缩放后的图像
        """
        ratio = height / self.height
        width = int(self.width * ratio)
        return self.image.resize((width, height), resample=resample)
    
    def resize_contain(self, 
                       size: Tuple[int, int],
                       background_color: Tuple[int, ...] = (255, 255, 255),
                       resample: int = Image.Resampling.LANCZOS) -> Image.Image:
        """
        缩放并包含在指定尺寸内(保持宽高比,不足部分填充)
        
        参数:
            size: 目标尺寸 (width, height)
            background_color: 背景色
            resample: 重采样方法
            
        返回:
            缩放后的图像
        """
        return ImageOps.fit(self.image, size, method=resample, 
                           bleed=0.0, centering=(0.5, 0.5))
    
    def resize_cover(self, 
                     size: Tuple[int, int],
                     resample: int = Image.Resampling.LANCZOS) -> Image.Image:
        """
        缩放并覆盖指定尺寸(保持宽高比,可能裁剪)
        
        参数:
            size: 目标尺寸 (width, height)
            resample: 重采样方法
            
        返回:
            缩放后的图像
        """
        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=resample)
        
        # 居中裁剪
        left = (resized.width - size[0]) // 2
        upper = (resized.height - size[1]) // 2
        
        return resized.crop((left, upper, left + size[0], upper + size[1]))
    
    def rotate(self, 
               angle: float,
               expand: bool = False,
               fillcolor: Optional[Tuple[int, ...]] = None) -> Image.Image:
        """
        旋转图像
        
        参数:
            angle: 旋转角度(逆时针为正)
            expand: 是否扩展画布
            fillcolor: 填充色
            
        返回:
            旋转后的图像
        """
        return self.image.rotate(angle, expand=expand, fillcolor=fillcolor)
    
    def rotate_90(self) -> Image.Image:
        """
        旋转90度
        
        返回:
            旋转后的图像
        """
        return self.image.rotate(90, expand=True)
    
    def rotate_180(self) -> Image.Image:
        """
        旋转180度
        
        返回:
            旋转后的图像
        """
        return self.image.rotate(180)
    
    def rotate_270(self) -> Image.Image:
        """
        旋转270度
        
        返回:
            旋转后的图像
        """
        return self.image.rotate(270, expand=True)
    
    def flip_horizontal(self) -> Image.Image:
        """
        水平翻转
        
        返回:
            翻转后的图像
        """
        return ImageOps.mirror(self.image)
    
    def flip_vertical(self) -> Image.Image:
        """
        垂直翻转
        
        返回:
            翻转后的图像
        """
        return ImageOps.flip(self.image)
    
    def transpose(self, method: int) -> Image.Image:
        """
        图像转置
        
        参数:
            method: 转置方法
                    Image.Transpose.FLIP_LEFT_RIGHT
                    Image.Transpose.FLIP_TOP_BOTTOM
                    Image.Transpose.ROTATE_90
                    Image.Transpose.ROTATE_180
                    Image.Transpose.ROTATE_270
                    Image.Transpose.TRANSPOSE
                    Image.Transpose.TRANSVERSE
                    
        返回:
            转置后的图像
        """
        return self.image.transpose(method)
    
    def transform_affine(self, 
                         matrix: Tuple[float, ...],
                         size: Optional[Tuple[int, int]] = None) -> Image.Image:
        """
        仿射变换
        
        参数:
            matrix: 变换矩阵(6个元素)
            size: 输出尺寸
            
        返回:
            变换后的图像
        """
        if size is None:
            size = self.image.size
        
        return self.image.transform(
            size, 
            Image.Transform.AFFINE,
            matrix,
            resample=Image.Resampling.BICUBIC
        )
    
    def transform_perspective(self, 
                              coeffs: Tuple[float, ...],
                              size: Optional[Tuple[int, int]] = None) -> Image.Image:
        """
        透视变换
        
        参数:
            coeffs: 变换系数(8个元素)
            size: 输出尺寸
            
        返回:
            变换后的图像
        """
        if size is None:
            size = self.image.size
        
        return self.image.transform(
            size,
            Image.Transform.PERSPECTIVE,
            coeffs,
            resample=Image.Resampling.BICUBIC
        )
    
    def transform_quad(self, 
                       quad: Tuple[Tuple[int, int], ...],
                       size: Tuple[int, int]) -> Image.Image:
        """
        四边形变换
        
        参数:
            quad: 目标四边形的四个顶点
            size: 输出尺寸
            
        返回:
            变换后的图像
        """
        # 计算透视变换系数
        # 这里简化处理,实际需要计算变换矩阵
        return self.image.transform(
            size,
            Image.Transform.QUAD,
            quad,
            resample=Image.Resampling.BICUBIC
        )
    
    def create_thumbnail(self, size: Tuple[int, int]) -> Image.Image:
        """
        创建缩略图
        
        参数:
            size: 最大尺寸 (width, height)
            
        返回:
            缩略图
        """
        img_copy = self.image.copy()
        img_copy.thumbnail(size, resample=Image.Resampling.LANCZOS)
        return img_copy


def demonstrate_geometry():
    """
    演示几何变换
    """
    # 创建测试图像
    image = Image.new('RGB', (400, 300), color=(100, 150, 200))
    
    # 添加一些标记
    from PIL import ImageDraw
    draw = ImageDraw.Draw(image)
    draw.rectangle([50, 50, 150, 100], fill=(255, 0, 0))
    draw.ellipse([200, 100, 350, 200], fill=(0, 255, 0))
    draw.text((100, 200), "Test Image", fill=(255, 255, 255))
    
    geometry = PillowGeometry(image)
    
    print("Pillow几何变换演示")
    print("=" * 50)
    
    # 裁剪
    cropped = geometry.crop((50, 50, 200, 150))
    print(f"裁剪: {image.size} -> {cropped.size}")
    
    # 中心裁剪
    center_cropped = geometry.crop_center(200, 150)
    print(f"中心裁剪: {image.size} -> {center_cropped.size}")
    
    # 缩放
    resized = geometry.resize((200, 150))
    print(f"缩放: {image.size} -> {resized.size}")
    
    # 按宽度缩放
    width_scaled = geometry.resize_to_width(200)
    print(f"按宽度缩放: {image.size} -> {width_scaled.size}")
    
    # 旋转
    rotated = geometry.rotate(45, expand=True)
    print(f"旋转45度: {rotated.size}")
    
    # 翻转
    h_flipped = geometry.flip_horizontal()
    v_flipped = geometry.flip_vertical()
    print(f"水平翻转: {h_flipped.size}")
    print(f"垂直翻转: {v_flipped.size}")
    
    # 缩略图
    thumbnail = geometry.create_thumbnail((100, 100))
    print(f"缩略图: {thumbnail.size}")
    
    return {
        'original': image,
        'cropped': cropped,
        'resized': resized,
        'rotated': rotated,
        'thumbnail': thumbnail
    }


if __name__ == "__main__":
    results = demonstrate_geometry()
    print("\n几何变换演示完成")

5.3.2 图像增强

Pillow提供了多种图像增强功能,包括色彩调整、对比度调整、锐化、模糊等。这些功能主要通过ImageEnhance模块和ImageFilter模块实现。以下代码展示了各种图像增强操作。

python 复制代码
"""
Pillow图像增强详解
演示色彩调整、滤镜应用等增强操作
兼容Python 3.13
"""

from PIL import Image, ImageEnhance, ImageFilter, ImageOps
import numpy as np
from typing import Tuple, Optional, List


class PillowEnhance:
    """
    Pillow图像增强类
    """
    
    def __init__(self, image: Image.Image):
        """
        初始化图像增强器
        
        参数:
            image: 输入图像
        """
        self.image = image.convert('RGB')
    
    def adjust_brightness(self, factor: float) -> Image.Image:
        """
        调整亮度
        
        参数:
            factor: 亮度因子(1.0为原始亮度)
            
        返回:
            调整后的图像
        """
        enhancer = ImageEnhance.Brightness(self.image)
        return enhancer.enhance(factor)
    
    def adjust_contrast(self, factor: float) -> Image.Image:
        """
        调整对比度
        
        参数:
            factor: 对比度因子(1.0为原始对比度)
            
        返回:
            调整后的图像
        """
        enhancer = ImageEnhance.Contrast(self.image)
        return enhancer.enhance(factor)
    
    def adjust_color(self, factor: float) -> Image.Image:
        """
        调整色彩饱和度
        
        参数:
            factor: 饱和度因子(1.0为原始饱和度)
            
        返回:
            调整后的图像
        """
        enhancer = ImageEnhance.Color(self.image)
        return enhancer.enhance(factor)
    
    def adjust_sharpness(self, factor: float) -> Image.Image:
        """
        调整锐度
        
        参数:
            factor: 锐度因子(1.0为原始锐度)
            
        返回:
            调整后的图像
        """
        enhancer = ImageEnhance.Sharpness(self.image)
        return enhancer.enhance(factor)
    
    def auto_contrast(self, cutoff: float = 0) -> Image.Image:
        """
        自动对比度调整
        
        参数:
            cutoff: 截断百分比
            
        返回:
            调整后的图像
        """
        return ImageOps.autocontrast(self.image, cutoff=cutoff)
    
    def equalize(self) -> Image.Image:
        """
        直方图均衡化
        
        返回:
            均衡化后的图像
        """
        return ImageOps.equalize(self.image)
    
    def invert(self) -> Image.Image:
        """
        反色
        
        返回:
            反色后的图像
        """
        return ImageOps.invert(self.image)
    
    def posterize(self, bits: int = 3) -> Image.Image:
        """
        色调分离
        
        参数:
            bits: 每通道保留的位数
            
        返回:
            处理后的图像
        """
        return ImageOps.posterize(self.image, bits)
    
    def solarize(self, threshold: int = 128) -> Image.Image:
        """
        曝光效果
        
        参数:
            threshold: 阈值
            
        返回:
            处理后的图像
        """
        return ImageOps.solarize(self.image, threshold=threshold)
    
    def apply_filter(self, filter_type: str, **kwargs) -> Image.Image:
        """
        应用滤镜
        
        参数:
            filter_type: 滤镜类型
            **kwargs: 滤镜参数
            
        返回:
            滤镜处理后的图像
        """
        filters = {
            'blur': ImageFilter.BLUR,
            'contour': ImageFilter.CONTOUR,
            'detail': ImageFilter.DETAIL,
            'edge_enhance': ImageFilter.EDGE_ENHANCE,
            'edge_enhance_more': ImageFilter.EDGE_ENHANCE_MORE,
            'emboss': ImageFilter.EMBOSS,
            'find_edges': ImageFilter.FIND_EDGES,
            'sharpen': ImageFilter.SHARPEN,
            'smooth': ImageFilter.SMOOTH,
            'smooth_more': ImageFilter.SMOOTH_MORE,
            'unsharp_mask': ImageFilter.UnsharpMask(
                radius=kwargs.get('radius', 2),
                percent=kwargs.get('percent', 150),
                threshold=kwargs.get('threshold', 3)
            )
        }
        
        if filter_type in filters:
            return self.image.filter(filters[filter_type])
        
        return self.image.copy()
    
    def gaussian_blur(self, radius: int = 2) -> Image.Image:
        """
        高斯模糊
        
        参数:
            radius: 模糊半径
            
        返回:
            模糊后的图像
        """
        return self.image.filter(ImageFilter.GaussianBlur(radius=radius))
    
    def box_blur(self, radius: int = 2) -> Image.Image:
        """
        盒式模糊
        
        参数:
            radius: 模糊半径
            
        返回:
            模糊后的图像
        """
        return self.image.filter(ImageFilter.BoxBlur(radius=radius))
    
    def median_filter(self, size: int = 3) -> Image.Image:
        """
        中值滤波
        
        参数:
            size: 滤波器大小
            
        返回:
            滤波后的图像
        """
        return self.image.filter(ImageFilter.MedianFilter(size=size))
    
    def min_filter(self, size: int = 3) -> Image.Image:
        """
        最小值滤波
        
        参数:
            size: 滤波器大小
            
        返回:
            滤波后的图像
        """
        return self.image.filter(ImageFilter.MinFilter(size=size))
    
    def max_filter(self, size: int = 3) -> Image.Image:
        """
        最大值滤波
        
        参数:
            size: 滤波器大小
            
        返回:
            滤波后的图像
        """
        return self.image.filter(ImageFilter.MaxFilter(size=size))
    
    def mode_filter(self, size: int = 3) -> Image.Image:
        """
        众数滤波
        
        参数:
            size: 滤波器大小
            
        返回:
            滤波后的图像
        """
        return self.image.filter(ImageFilter.ModeFilter(size=size))
    
    def rank_filter(self, size: int = 3, rank: int = 4) -> Image.Image:
        """
        秩滤波
        
        参数:
            size: 滤波器大小
            rank: 秩
            
        返回:
            滤波后的图像
        """
        return self.image.filter(ImageFilter.RankFilter(size=size, rank=rank))
    
    def custom_kernel(self, kernel: List[List[float]], scale: float = None) -> Image.Image:
        """
        自定义卷积核
        
        参数:
            kernel: 卷积核
            scale: 缩放因子
            
        返回:
            卷积后的图像
        """
        # 展平卷积核
        flat_kernel = [item for row in kernel for item in row]
        
        if scale is None:
            scale = sum(flat_kernel)
            if scale == 0:
                scale = 1
        
        kernel_size = (len(kernel[0]), len(kernel))
        
        return self.image.filter(
            ImageFilter.Kernel(kernel_size, flat_kernel, scale=scale)
        )
    
    def colorize(self, 
                 black: str = 'black', 
                 white: str = 'white',
                 mid: Optional[str] = None) -> Image.Image:
        """
        灰度图着色
        
        参数:
            black: 暗部颜色
            white: 亮部颜色
            mid: 中间调颜色
            
        返回:
            着色后的图像
        """
        gray = self.image.convert('L')
        return ImageOps.colorize(gray, black=black, white=white, mid=mid)
    
    def add_border(self, 
                   border: int = 10, 
                   color: str = 'black') -> Image.Image:
        """
        添加边框
        
        参数:
            border: 边框宽度
            color: 边框颜色
            
        返回:
            添加边框后的图像
        """
        return ImageOps.expand(self.image, border=border, fill=color)
    
    def crop_border(self, border: int = 10) -> Image.Image:
        """
        裁剪边框
        
        参数:
            border: 边框宽度
            
        返回:
            裁剪后的图像
        """
        return ImageOps.crop(self.image, border=border)
    
    def fit(self, 
            size: Tuple[int, int], 
            method: int = Image.Resampling.LANCZOS,
            bleed: float = 0.0,
            centering: Tuple[float, float] = (0.5, 0.5)) -> Image.Image:
        """
        适配尺寸
        
        参数:
            size: 目标尺寸
            method: 重采样方法
            bleed: 边缘去除比例
            centering: 居中方式
            
        返回:
            适配后的图像
        """
        return ImageOps.fit(self.image, size, method=method, 
                           bleed=bleed, centering=centering)
    
    def pad(self, 
            size: Tuple[int, int], 
            color: str = 'black',
            centering: Tuple[float, float] = (0.5, 0.5)) -> Image.Image:
        """
        填充到指定尺寸
        
        参数:
            size: 目标尺寸
            color: 填充颜色
            centering: 居中方式
            
        返回:
            填充后的图像
        """
        return ImageOps.pad(self.image, size, color=color, centering=centering)


def demonstrate_enhancement():
    """
    演示图像增强
    """
    # 创建测试图像
    image = Image.new('RGB', (400, 300), color=(100, 150, 200))
    
    from PIL import ImageDraw
    draw = ImageDraw.Draw(image)
    draw.rectangle([50, 50, 150, 100], fill=(255, 0, 0))
    draw.ellipse([200, 100, 350, 200], fill=(0, 255, 0))
    
    enhance = PillowEnhance(image)
    
    print("Pillow图像增强演示")
    print("=" * 50)
    
    # 亮度调整
    bright = enhance.adjust_brightness(1.5)
    dark = enhance.adjust_brightness(0.5)
    print(f"亮度调整: 1.5x 和 0.5x")
    
    # 对比度调整
    high_contrast = enhance.adjust_contrast(1.5)
    low_contrast = enhance.adjust_contrast(0.5)
    print(f"对比度调整: 1.5x 和 0.5x")
    
    # 饱和度调整
    high_color = enhance.adjust_color(1.5)
    low_color = enhance.adjust_color(0.5)
    print(f"饱和度调整: 1.5x 和 0.5x")
    
    # 滤镜效果
    blurred = enhance.gaussian_blur(5)
    sharpened = enhance.apply_filter('sharpen')
    embossed = enhance.apply_filter('emboss')
    print(f"滤镜: 高斯模糊、锐化、浮雕")
    
    # 自动增强
    auto_contrast = enhance.auto_contrast()
    equalized = enhance.equalize()
    print(f"自动增强: 自动对比度、直方图均衡化")
    
    # 特殊效果
    inverted = enhance.invert()
    posterized = enhance.posterize(2)
    solarized = enhance.solarize(128)
    print(f"特殊效果: 反色、色调分离、曝光")
    
    return {
        'original': image,
        'bright': bright,
        'dark': dark,
        'blurred': blurred,
        'sharpened': sharpened,
        'embossed': embossed,
        'inverted': inverted
    }


if __name__ == "__main__":
    results = demonstrate_enhancement()
    print("\n图像增强演示完成")

5.4 图像绘制与文字处理

5.4.1 图像绘制

Pillow的ImageDraw模块提供了在图像上绘制各种图形的功能,包括直线、矩形、圆形、椭圆、多边形、弧线等。这些功能对于创建标注图像、添加水印、生成图表等应用非常有用。以下代码展示了各种绘制操作。

python 复制代码
"""
Pillow图像绘制详解
演示各种图形和文字的绘制方法
兼容Python 3.13
"""

from PIL import Image, ImageDraw, ImageFont
import numpy as np
from typing import Tuple, List, Optional, Union
from pathlib import Path


class PillowDrawing:
    """
    Pillow图像绘制类
    """
    
    def __init__(self, 
                 image: Optional[Image.Image] = None,
                 size: Tuple[int, int] = (640, 480),
                 background: Union[str, Tuple[int, ...]] = 'white'):
        """
        初始化绘制器
        
        参数:
            image: 输入图像(如果为None则创建新图像)
            size: 新图像尺寸
            background: 背景色
        """
        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 get_image(self) -> Image.Image:
        """
        获取当前图像
        
        返回:
            Image对象
        """
        return self.image
    
    def draw_line(self, 
                  start: Tuple[int, int], 
                  end: Tuple[int, int],
                  color: Union[str, Tuple[int, ...]] = 'black',
                  width: int = 1) -> None:
        """
        绘制直线
        
        参数:
            start: 起点
            end: 终点
            color: 颜色
            width: 线宽
        """
        self.draw.line([start, end], fill=color, width=width)
    
    def draw_lines(self, 
                   points: List[Tuple[int, int]],
                   color: Union[str, Tuple[int, ...]] = 'black',
                   width: int = 1,
                   closed: bool = False) -> None:
        """
        绘制折线
        
        参数:
            points: 点列表
            color: 颜色
            width: 线宽
            closed: 是否闭合
        """
        self.draw.line(points, fill=color, width=width, joint=None)
        if closed and len(points) > 2:
            self.draw.line([points[-1], points[0]], fill=color, width=width)
    
    def draw_rectangle(self, 
                       box: Tuple[int, int, int, int],
                       outline: Optional[Union[str, Tuple[int, ...]]] = 'black',
                       fill: Optional[Union[str, Tuple[int, ...]]] = None,
                       width: int = 1) -> None:
        """
        绘制矩形
        
        参数:
            box: 矩形区域 (left, upper, right, lower)
            outline: 边框颜色
            fill: 填充颜色
            width: 边框宽度
        """
        self.draw.rectangle(box, outline=outline, fill=fill, width=width)
    
    def draw_rounded_rectangle(self, 
                                box: Tuple[int, int, int, int],
                                radius: int = 10,
                                outline: Optional[Union[str, Tuple[int, ...]]] = 'black',
                                fill: Optional[Union[str, Tuple[int, ...]]] = None,
                                width: int = 1) -> None:
        """
        绘制圆角矩形
        
        参数:
            box: 矩形区域
            radius: 圆角半径
            outline: 边框颜色
            fill: 填充颜色
            width: 边框宽度
        """
        self.draw.rounded_rectangle(box, radius=radius, 
                                    outline=outline, fill=fill, width=width)
    
    def draw_ellipse(self, 
                     box: Tuple[int, int, int, int],
                     outline: Optional[Union[str, Tuple[int, ...]]] = 'black',
                     fill: Optional[Union[str, Tuple[int, ...]]] = None,
                     width: int = 1) -> None:
        """
        绘制椭圆
        
        参数:
            box: 边界框
            outline: 边框颜色
            fill: 填充颜色
            width: 边框宽度
        """
        self.draw.ellipse(box, outline=outline, fill=fill, width=width)
    
    def draw_circle(self, 
                    center: Tuple[int, int],
                    radius: int,
                    outline: Optional[Union[str, Tuple[int, ...]]] = 'black',
                    fill: Optional[Union[str, Tuple[int, ...]]] = None,
                    width: int = 1) -> None:
        """
        绘制圆形
        
        参数:
            center: 圆心
            radius: 半径
            outline: 边框颜色
            fill: 填充颜色
            width: 边框宽度
        """
        x, y = center
        box = (x - radius, y - radius, x + radius, y + radius)
        self.draw.ellipse(box, outline=outline, fill=fill, width=width)
    
    def draw_arc(self, 
                 box: Tuple[int, int, int, int],
                 start: float,
                 end: float,
                 color: Union[str, Tuple[int, ...]] = 'black',
                 width: int = 1) -> None:
        """
        绘制弧线
        
        参数:
            box: 边界框
            start: 起始角度
            end: 结束角度
            color: 颜色
            width: 线宽
        """
        self.draw.arc(box, start=start, end=end, fill=color, width=width)
    
    def draw_chord(self, 
                   box: Tuple[int, int, int, int],
                   start: float,
                   end: float,
                   outline: Optional[Union[str, Tuple[int, ...]]] = 'black',
                   fill: Optional[Union[str, Tuple[int, ...]]] = None,
                   width: int = 1) -> None:
        """
        绘制弦
        
        参数:
            box: 边界框
            start: 起始角度
            end: 结束角度
            outline: 边框颜色
            fill: 填充颜色
            width: 边框宽度
        """
        self.draw.chord(box, start=start, end=end, 
                        outline=outline, fill=fill, width=width)
    
    def draw_pieslice(self, 
                      box: Tuple[int, int, int, int],
                      start: float,
                      end: float,
                      outline: Optional[Union[str, Tuple[int, ...]]] = 'black',
                      fill: Optional[Union[str, Tuple[int, ...]]] = None,
                      width: int = 1) -> None:
        """
        绘制扇形
        
        参数:
            box: 边界框
            start: 起始角度
            end: 结束角度
            outline: 边框颜色
            fill: 填充颜色
            width: 边框宽度
        """
        self.draw.pieslice(box, start=start, end=end, 
                           outline=outline, fill=fill, width=width)
    
    def draw_polygon(self, 
                     points: List[Tuple[int, int]],
                     outline: Optional[Union[str, Tuple[int, ...]]] = 'black',
                     fill: Optional[Union[str, Tuple[int, ...]]] = None,
                     width: int = 1) -> None:
        """
        绘制多边形
        
        参数:
            points: 顶点列表
            outline: 边框颜色
            fill: 填充颜色
            width: 边框宽度
        """
        self.draw.polygon(points, outline=outline, fill=fill)
    
    def draw_point(self, 
                   position: Tuple[int, int],
                   color: Union[str, Tuple[int, ...]] = 'black') -> None:
        """
        绘制点
        
        参数:
            position: 位置
            color: 颜色
        """
        self.draw.point(position, fill=color)
    
    def draw_points(self, 
                    points: List[Tuple[int, int]],
                    color: Union[str, Tuple[int, ...]] = 'black') -> None:
        """
        绘制多个点
        
        参数:
            points: 点列表
            color: 颜色
        """
        for point in points:
            self.draw.point(point, fill=color)
    
    def draw_text(self, 
                  position: Tuple[int, int],
                  text: str,
                  color: Union[str, Tuple[int, ...]] = 'black',
                  font: Optional[ImageFont.FreeTypeFont] = None) -> None:
        """
        绘制文字
        
        参数:
            position: 位置
            text: 文字内容
            color: 颜色
            font: 字体对象
        """
        self.draw.text(position, text, fill=color, font=font)
    
    def draw_text_with_box(self, 
                           position: Tuple[int, int],
                           text: str,
                           color: Union[str, Tuple[int, ...]] = 'black',
                           font: Optional[ImageFont.FreeTypeFont] = None,
                           box_color: Optional[Union[str, Tuple[int, ...]]] = None,
                           padding: int = 5) -> None:
        """
        绘制带背景框的文字
        
        参数:
            position: 位置
            text: 文字内容
            color: 文字颜色
            font: 字体对象
            box_color: 背景框颜色
            padding: 内边距
        """
        # 获取文字边界框
        if font:
            bbox = font.getbbox(text)
        else:
            bbox = self.draw.textbbox((0, 0), text)
        
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]
        
        # 绘制背景框
        if box_color:
            box = (
                position[0] - padding,
                position[1] - padding,
                position[0] + text_width + padding,
                position[1] + text_height + padding
            )
            self.draw.rectangle(box, fill=box_color)
        
        # 绘制文字
        self.draw.text(position, text, fill=color, font=font)
    
    def draw_text_centered(self, 
                           text: str,
                           color: Union[str, Tuple[int, ...]] = 'black',
                           font: Optional[ImageFont.FreeTypeFont] = None) -> None:
        """
        绘制居中文字
        
        参数:
            text: 文字内容
            color: 颜色
            font: 字体对象
        """
        # 获取文字边界框
        if font:
            bbox = font.getbbox(text)
        else:
            bbox = self.draw.textbbox((0, 0), text)
        
        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_cross(self, 
                   center: Tuple[int, int],
                   size: int = 10,
                   color: Union[str, Tuple[int, ...]] = 'red',
                   width: int = 2) -> None:
        """
        绘制十字标记
        
        参数:
            center: 中心点
            size: 大小
            color: 颜色
            width: 线宽
        """
        x, y = center
        self.draw.line([(x - size, y), (x + size, y)], fill=color, width=width)
        self.draw.line([(x, y - size), (x, y + size)], fill=color, width=width)
    
    def draw_arrow(self, 
                   start: Tuple[int, int],
                   end: Tuple[int, int],
                   color: Union[str, Tuple[int, ...]] = 'black',
                   width: int = 2,
                   head_size: int = 10) -> None:
        """
        绘制箭头
        
        参数:
            start: 起点
            end: 终点
            color: 颜色
            width: 线宽
            head_size: 箭头大小
        """
        # 绘制线段
        self.draw.line([start, end], fill=color, width=width)
        
        # 计算箭头方向
        import math
        dx = end[0] - start[0]
        dy = end[1] - start[1]
        length = math.sqrt(dx * dx + dy * dy)
        
        if length == 0:
            return
        
        # 单位向量
        ux = dx / length
        uy = dy / length
        
        # 垂直向量
        px = -uy
        py = ux
        
        # 箭头顶点
        arrow_points = [
            end,
            (int(end[0] - head_size * ux + head_size * px * 0.5),
             int(end[1] - head_size * uy + head_size * py * 0.5)),
            (int(end[0] - head_size * ux - head_size * px * 0.5),
             int(end[1] - head_size * uy - head_size * py * 0.5))
        ]
        
        self.draw.polygon(arrow_points, fill=color)
    
    def draw_grid(self, 
                  spacing: int = 50,
                  color: Union[str, Tuple[int, ...]] = (200, 200, 200),
                  width: int = 1) -> None:
        """
        绘制网格
        
        参数:
            spacing: 网格间距
            color: 颜色
            width: 线宽
        """
        # 垂直线
        for x in range(0, self.width, spacing):
            self.draw.line([(x, 0), (x, self.height)], fill=color, width=width)
        
        # 水平线
        for y in range(0, self.height, spacing):
            self.draw.line([(0, y), (self.width, y)], fill=color, width=width)
    
    def draw_checkerboard(self, 
                          square_size: int = 50,
                          color1: Union[str, Tuple[int, ...]] = 'white',
                          color2: Union[str, Tuple[int, ...]] = 'gray') -> None:
        """
        绘制棋盘格背景
        
        参数:
            square_size: 方块大小
            color1: 颜色1
            color2: 颜色2
        """
        for y in range(0, self.height, square_size):
            for x in range(0, self.width, square_size):
                color = color1 if ((x // square_size) + (y // square_size)) % 2 == 0 else color2
                self.draw.rectangle(
                    [x, y, x + square_size, y + square_size],
                    fill=color
                )
    
    def clear(self, color: Union[str, Tuple[int, ...]] = 'white') -> None:
        """
        清空画布
        
        参数:
            color: 背景色
        """
        self.draw.rectangle([0, 0, self.width, self.height], fill=color)


class FontManager:
    """
    字体管理类
    """
    
    def __init__(self):
        """初始化字体管理器"""
        self.fonts = {}
    
    def load_font(self, 
                  name: str,
                  font_path: str,
                  size: int = 16) -> ImageFont.FreeTypeFont:
        """
        加载字体
        
        参数:
            name: 字体名称
            font_path: 字体文件路径
            size: 字体大小
            
        返回:
            字体对象
        """
        try:
            font = ImageFont.truetype(font_path, size)
            self.fonts[name] = font
            return font
        except Exception as e:
            print(f"加载字体失败: {e}")
            return ImageFont.load_default()
    
    def get_font(self, name: str) -> Optional[ImageFont.FreeTypeFont]:
        """
        获取字体
        
        参数:
            name: 字体名称
            
        返回:
            字体对象
        """
        return self.fonts.get(name)
    
    def get_default_font(self, size: int = 16) -> ImageFont.FreeTypeFont:
        """
        获取默认字体
        
        参数:
            size: 字体大小
            
        返回:
            字体对象
        """
        try:
            return ImageFont.truetype("arial.ttf", size)
        except Exception:
            return ImageFont.load_default()


def demonstrate_drawing():
    """
    演示图像绘制
    """
    # 创建绘制器
    drawer = PillowDrawing(size=(800, 600), background='white')
    
    print("Pillow图像绘制演示")
    print("=" * 50)
    
    # 绘制网格
    drawer.draw_grid(50, (230, 230, 230))
    print("绘制网格")
    
    # 绘制基本图形
    drawer.draw_rectangle((50, 50, 200, 150), outline='blue', fill='lightblue', width=2)
    print("绘制矩形")
    
    drawer.draw_circle((350, 100), 50, outline='red', fill='pink', width=2)
    print("绘制圆形")
    
    drawer.draw_ellipse((450, 50, 650, 150), outline='green', fill='lightgreen', width=2)
    print("绘制椭圆")
    
    # 绘制多边形
    points = [(100, 250), (150, 200), (200, 250), (175, 300), (125, 300)]
    drawer.draw_polygon(points, outline='purple', fill='lavender')
    print("绘制多边形")
    
    # 绘制弧线和扇形
    drawer.draw_arc((300, 200, 450, 350), 0, 180, 'orange', 3)
    print("绘制弧线")
    
    drawer.draw_pieslice((500, 200, 650, 350), 0, 270, outline='brown', fill='tan')
    print("绘制扇形")
    
    # 绘制线条和箭头
    drawer.draw_line((50, 400), (200, 400), 'black', 2)
    drawer.draw_arrow((250, 400), (400, 400), 'darkblue', 2, 15)
    print("绘制线条和箭头")
    
    # 绘制十字标记
    drawer.draw_cross((500, 400), 20, 'red', 3)
    print("绘制十字标记")
    
    # 绘制文字
    font_manager = FontManager()
    default_font = font_manager.get_default_font(20)
    
    drawer.draw_text((50, 500), "Hello, Pillow!", 'darkblue', default_font)
    drawer.draw_text_with_box((50, 540), "Text with Background", 'white', 
                              default_font, 'darkgreen', 5)
    drawer.draw_text_centered("Centered Text", 'purple', default_font)
    print("绘制文字")
    
    # 绘制圆角矩形
    drawer.draw_rounded_rectangle((550, 400, 750, 550), 20, 
                                   outline='teal', fill='lightcyan', width=2)
    print("绘制圆角矩形")
    
    return drawer.get_image()


if __name__ == "__main__":
    image = demonstrate_drawing()
    print("\n图像绘制演示完成")

5.5 本章小结

本章详细介绍了Pillow库的使用方法,包括图像的读取、写入、格式转换、几何变换、图像增强和图像绘制等功能。Pillow是Python图像处理的重要工具,它提供了简洁易用的API和丰富的功能支持。

Pillow与OpenCV各有优势,在实际项目中通常会结合使用。Pillow适合处理图像格式转换、基本操作和图像绘制等任务,而OpenCV更适合复杂的计算机视觉任务。两个库之间的转换非常简单,可以通过NumPy数组实现无缝衔接。

下一章将介绍图像色彩空间转换与通道操作,深入讲解RGB、HSV、LAB等色彩空间的原理和应用。


GPT-5.4辅助编程提示词

text 复制代码
我需要使用Pillow处理一批图像,请帮我编写完整的Python代码:

需求描述:
1. 读取指定目录下的所有图像
2. 对每张图像进行以下处理:
   - 自动调整方向(根据EXIF信息)
   - 缩放到指定最大尺寸(保持宽高比)
   - 添加水印(文字水印,位置可配置)
   - 调整亮度和对比度
   - 添加边框
3. 支持输出为JPEG/PNG/WebP格式
4. 生成处理报告

技术要求:
- 使用Pillow库
- 支持批量处理
- 兼容Python 3.13
- 提供命令行接口
- 完善的错误处理
相关推荐
人工干智能2 小时前
科普:%%matplotlib inline:魔法命令 (Cell Magic)
python·matplotlib
05大叔2 小时前
优化器Adam,神经网络处理文本,CNN,RNN
开发语言·python·机器学习
徒 花2 小时前
Python知识学习08
java·python·算法
前端技术3 小时前
ArkTS第三章:声明式UI开发实战
java·前端·人工智能·python·华为·鸿蒙
疯狂成瘾者3 小时前
.pyc格式文件
python
鬼圣3 小时前
Python 生成器与迭代器详解
python
西魏陶渊明3 小时前
解决异步挑战:Reactor Context 实现响应式上下文传递
开发语言·python
学习永无止境@3 小时前
Sobel边缘检测的MATLAB实现
图像处理·opencv·算法·计算机视觉·fpga开发
疯狂成瘾者3 小时前
增强型大模型代理
python