Python 图片处理:PIL/Pillow 库简介与使用详解
图像处理是计算机视觉和数据科学领域中一个非常常见的任务,无论是简单的图片格式转换、尺寸调整,还是复杂的滤镜应用、特征提取,都需要强大的工具库支持。在 Python 生态系统中,Pillow 库(基于 PIL,Python Imaging Library 的一个友好分支)是进行图像处理的首选库。它功能丰富、易于使用,并且性能良好,支持多种图像文件格式。
本文将详细介绍 Pillow 库,从其历史渊源、安装、基本概念讲起,逐步深入到各种常用的图像处理操作,包括打开、保存、显示图片,尺寸调整、裁剪、旋转、翻转,像素操作,颜色模式转换,以及在图片上绘制图形和文本等。
1. PIL 与 Pillow 的历史渊源
在介绍 Pillow 之前,不得不提它的前身——PIL(Python Imaging Library)。PIL 是由 Fredrik Lundh 创建的一个强大的图像处理库,它为 Python 提供了广泛的图像文件格式支持、强大的图像处理能力以及像素级的操作接口。在很长一段时间内,PIL 是 Python 图像处理领域的标杆。
然而,PIL 的开发在 2009 年之后陷入了停滞,并且在较新的 Python 版本(如 Python 3)上安装和使用存在困难。为了解决这个问题,Pillow 应运而生。Pillow 是 PIL 的一个分支,它由一个活跃的社区维护,旨在为 PIL 添加对 Python 3 的支持,并不断地修复 bug、改进功能和兼容性。
现在,当你听到“PIL”时,通常指的也是 Pillow,因为 Pillow 是 PIL 的事实上的继承者,并且在使用时,我们仍然通常会使用 import PIL
这样的语句。安装 Pillow 库后,你就可以导入 PIL 模块及其子模块来使用其功能了。
总之,Pillow 就是你现在在 Python 中进行图像处理时应该使用的库,它是 PIL 的现代化和持续维护版本。
2. 安装 Pillow
安装 Pillow 非常简单,使用 Python 的包管理器 pip 即可完成。打开终端或命令提示符,执行以下命令:
bash
pip install Pillow
如果你使用的是虚拟环境,请确保在激活虚拟环境后执行此命令。
Pillow 依赖于一些外部库来处理特定的文件格式(如 JPEG, PNG, TIFF 等),但在大多数现代操作系统上,这些依赖通常是预装的或者 Pillow 会自动处理。如果遇到特定格式的支持问题,可能需要安装额外的系统库,但对于大多数常见操作而言,pip install Pillow
就足够了。
安装完成后,你可以在 Python 解释器中导入 PIL
模块来验证是否安装成功:
python
import PIL
print(PIL.__version__)
如果能成功导入并打印版本号,说明 Pillow 已经安装成功。
3. Pillow 的基本概念
在使用 Pillow 进行图像处理之前,了解几个核心概念非常有帮助:
3.1 Image 对象
在 Pillow 中,所有的图像处理操作都围绕着 Image
对象进行。Image
对象代表了一张图片,它包含了图片的像素数据以及相关的元信息,如尺寸、模式、格式等。
你可以通过 PIL.Image.open()
函数加载一张图片文件到内存中,创建一个 Image
对象。大多数 Pillow 函数和方法都接受或返回 Image
对象。
3.2 图像模式 (Mode)
图像模式决定了像素的表示方式和通道数量。这是 Pillow 中一个非常重要的概念,不同的模式适用于不同的场景。常见的图像模式包括:
- ‘L’ (luminance): 灰度图像,每个像素用 8 位表示,取值范围 0-255,0 为黑色,255 为白色。
- ‘RGB’: 真彩色图像,每个像素由红、绿、蓝三个通道组成,每个通道 8 位,共 24 位。取值范围 (0-255, 0-255, 0-255)。
- ‘RGBA’: 真彩色图像,包含红、绿、蓝和 Alpha (透明度) 四个通道。Alpha 通道通常也用 8 位表示,取值范围 0-255,0 为完全透明,255 为完全不透明。常用于 PNG 图片。
- ‘CMYK’: 青、洋红、黄、黑四通道图像,常用于印刷。
- ‘P’ (palette): 调色板图像,每个像素是 8 位或更少,表示在预定义的 256 色调色板中的索引。常用于 GIF 和一些 PNG 图片。
- ‘I’ (integer pixels): 32 位有符号整数像素。
- ‘F’ (float pixels): 32 位浮点数像素。
理解图像模式对于进行正确的图像操作至关重要,例如,某些操作只适用于特定模式的图像。你可以通过 img.mode
属性获取图片的模式。
3.3 图像尺寸 (Size)
图像尺寸表示图片的宽度和高度,以像素为单位。在 Pillow 中,尺寸用一个元组 (width, height)
表示。你可以通过 img.size
属性获取图片的尺寸。
3.4 图像格式 (Format)
图像格式表示图片的原始文件格式,如 JPEG, PNG, GIF, BMP 等。你可以通过 img.format
属性获取图片的格式。请注意,这个属性只在图片通过 Image.open()
加载时设置,表示源文件的格式;如果是新创建的图片,这个属性可能为 None。保存图片时,可以通过指定文件名后缀或格式参数来控制输出格式。
3.5 图像信息 (Info)
Image
对象还有一个 info
属性,它是一个字典,包含了与图像相关的元数据,如 JPEG 图片的 EXIF 信息、PNG 图片的调色板等。这些信息依赖于具体的图像文件。
4. 基本的图像操作:打开、显示与保存
这是使用 Pillow 的第一步。
4.1 打开图片
使用 PIL.Image.open()
函数可以打开一张图片文件:
“`python
from PIL import Image
import os
假设你有一个名为 ‘your_image.jpg’ 的图片文件在当前目录下
或者提供完整的图片路径
image_path = ‘your_image.jpg’
try:
img = Image.open(image_path)
# 获取并打印图片信息
print(f"成功打开图片:{image_path}")
print(f"图片格式:{img.format}")
print(f"图片模式:{img.mode}")
print(f"图片尺寸:{img.size}") # (宽度, 高度)
print(f"图片信息字典:{img.info}")
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”打开图片时发生错误:{e}”)
在处理完成后,关闭图片文件(推荐使用 with 语句)
img.close() # 或者使用 with 语句,更安全
“`
推荐使用 with
语句来打开图片,这样可以确保在代码块执行完毕后,图片文件被正确关闭,即使发生错误也不会导致文件资源泄露:
“`python
from PIL import Image
import os
image_path = ‘your_image.jpg’
try:
with Image.open(image_path) as img:
print(f”成功打开图片:{image_path}”)
print(f”图片格式:{img.format}”)
print(f”图片模式:{img.mode}”)
print(f”图片尺寸:{img.size}”) # (宽度, 高度)
print(f”图片信息字典:{img.info}”)
# 在这个 with 块内部可以对 img 进行各种操作
# 在 with 块外部,文件已经被关闭,不能再访问 img 的像素数据
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”打开图片时发生错误:{e}”)
“`
4.2 显示图片
Pillow 提供了一个简单的 show()
方法来显示图片。这个方法会调用操作系统默认的图片查看器来打开图片。这在开发和调试时非常方便。
“`python
from PIL import Image
image_path = ‘your_image.jpg’
try:
with Image.open(image_path) as img:
print(“尝试显示图片…”)
img.show()
print(“显示图片命令已执行。”)
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_image_path}'”)
except Exception as e:
print(f”显示图片时发生错误:{e}”)
“`
需要注意的是,show()
方法会启动一个外部进程来显示图片,它不是在 Python 程序内部渲染图片。
4.3 保存图片
使用 img.save()
方法可以将 Image
对象保存到文件中。你可以指定输出文件名,Pillow 会根据文件名的后缀猜测保存格式;或者通过 format
参数明确指定格式。
“`python
from PIL import Image
import os
input_image_path = ‘your_image.jpg’
output_image_path_png = ‘output_image.png’
output_image_path_jpeg = ‘output_image_compressed.jpg’
try:
with Image.open(input_image_path) as img:
# 保存为 PNG 格式
img.save(output_image_path_png)
print(f”图片已保存为 {output_image_path_png}”)
# 保存为 JPEG 格式,并指定压缩质量 (0-100)
# 质量越高,文件越大,图像失真越小
img.save(output_image_path_jpeg, quality=85)
print(f"图片已保存为 {output_image_path_jpeg} (质量 85)")
except FileNotFoundError:
print(f”错误:输入文件未找到 ‘{input_image_path}'”)
except Exception as e:
print(f”保存图片时发生错误:{e}”)
“`
save()
方法还支持许多其他格式特定的参数,例如:
* JPEG: quality
(压缩质量), optimize
(优化文件大小), progressive
(渐进式加载)。
* PNG: optimize
(优化文件大小), compress_level
(压缩级别), icc_profile
(ICC 配置文件)。
* GIF: save_all
(保存多帧 GIF), append_images
(追加帧), duration
(帧间隔), loop
(循环次数)。
5. 图像尺寸与变换
对图片的尺寸进行调整或进行基本的几何变换(裁剪、旋转、翻转)是图像处理中最常见的任务之一。
5.1 调整尺寸 (Resizing)
使用 img.resize()
方法可以改变图片的尺寸。它接受一个表示新尺寸的元组 (width, height)
作为参数,并返回一个新的 Image
对象,原始图片不变。
“`python
from PIL import Image
image_path = ‘your_image.jpg’
output_path_resized = ‘resized_image.jpg’
try:
with Image.open(image_path) as img:
original_width, original_height = img.size
print(f”原始尺寸:{original_width}x{original_height}”)
# 将图片宽度调整为 300 像素,高度按比例缩放
new_width = 300
# 计算新高度,保持宽高比
aspect_ratio = original_height / original_width
new_height = int(new_width * aspect_ratio)
resized_img_proportional = img.resize((new_width, new_height))
print(f"按比例调整后尺寸:{resized_img_proportional.size}")
resized_img_proportional.save(output_path_resized.replace('.jpg', '_prop.jpg'))
# 调整为固定尺寸,不保持宽高比
fixed_size = (200, 150)
resized_img_fixed = img.resize(fixed_size)
print(f"固定尺寸调整后尺寸:{resized_img_fixed.size}")
resized_img_fixed.save(output_path_resized.replace('.jpg', '_fixed.jpg'))
# resize 方法还可以指定采样滤波器 (filter),用于控制缩放时的图像质量
# Lanczos 滤波器通常用于高质量缩放,尤其适合缩小
# BICUBIC 也是常见的选择
# NEAREST, BOX, BILINEAR 计算速度快但质量较低
# PIL.Image.Resampling (或 PIL.Image.ANTIALIAS 在旧版本) 是推荐的滤波器枚举
from PIL import Image
resized_img_hq = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
resized_img_hq.save(output_path_resized.replace('.jpg', '_hq.jpg'))
print(f"使用 Lanczos 滤波器调整尺寸并保存")
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”调整尺寸时发生错误:{e}”)
“`
resize()
方法默认使用 BICUBIC
滤波器(在 Pillow 9.0.0 之前是 BICUBIC
或根据情况选择,之后默认是 Resampling.BICUBIC
),但在进行缩小时,使用 Resampling.LANCZOS
通常能获得更好的视觉效果。
注意: 如果仅仅是为了创建图片缩略图,并且希望保持宽高比且不放大图片,推荐使用 img.thumbnail()
方法,它会就地修改图片对象,或者返回一个新的缩略图对象(如果原始图片过大)。
“`python
from PIL import Image
image_path = ‘your_image.jpg’
output_path_thumbnail = ‘thumbnail_image.jpg’
try:
with Image.open(image_path) as img:
original_size = img.size
print(f”原始尺寸:{original_size}”)
# 创建一个最大尺寸为 128x128 的缩略图
max_size = (128, 128)
# thumbnail 方法会按比例缩小图片,直到适应指定的尺寸,不会放大
img.thumbnail(max_size) # 注意:thumbnail 是就地修改,但通常我们会先复制一份
print(f"缩略图尺寸:{img.size}")
img.save(output_path_thumbnail)
print(f"缩略图已保存为 {output_path_thumbnail}")
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”创建缩略图时发生错误:{e}”)
``
img.copy()
通常为了不修改原始图片对象,会先用创建一个副本再调用
thumbnail。不过
with Image.open(…) as img:` 的用法已经确保了每次打开都是一个新的 Image 对象。
5.2 裁剪 (Cropping)
使用 img.crop()
方法可以从图片中裁剪出一个矩形区域。它接受一个四元组 (left, upper, right, lower)
作为参数,分别表示裁剪区域的左上角和右下角的坐标。坐标系统以图片左上角为原点 (0, 0)
,向右为 x 轴正方向,向下为 y 轴正方向。
“`python
from PIL import Image
image_path = ‘your_image.jpg’
output_path_cropped = ‘cropped_image.jpg’
try:
with Image.open(image_path) as img:
original_size = img.size
print(f”原始尺寸:{original_size}”)
# 裁剪图片中心区域
width, height = img.size
# 定义裁剪框 (left, upper, right, lower)
# 例如:从左边1/4处开始,到右边3/4处结束
# 从顶部1/4处开始,到底部3/4处结束
box = (width * 0.25, height * 0.25, width * 0.75, height * 0.75)
box = tuple(map(int, box)) # 坐标必须是整数
cropped_img = img.crop(box)
print(f"裁剪区域:{box}")
print(f"裁剪后尺寸:{cropped_img.size}")
cropped_img.save(output_path_cropped)
print(f"裁剪后的图片已保存为 {output_path_cropped}")
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”裁剪图片时发生错误:{e}”)
“`
5.3 旋转 (Rotating)
使用 img.rotate()
方法可以旋转图片。它接受旋转角度(以度为单位,逆时针方向)作为参数。常用的参数包括 expand
(旋转后是否扩展画布以容纳整个图片,避免裁剪),center
(旋转中心,默认为图片中心)。
“`python
from PIL import Image
image_path = ‘your_image.jpg’
output_path_rotated_45 = ‘rotated_image_45.jpg’
output_path_rotated_90 = ‘rotated_image_90.jpg’
try:
with Image.open(image_path) as img:
# 逆时针旋转 45 度,不扩展画布(部分区域会被裁剪)
rotated_img_45 = img.rotate(45)
rotated_img_45.save(output_path_rotated_45.replace(‘.jpg’, ‘_no_expand.jpg’))
print(f”图片已逆时针旋转 45 度(不扩展)并保存为 {output_path_rotated_45.replace(‘.jpg’, ‘_no_expand.jpg’)}”)
# 逆时针旋转 45 度,扩展画布以容纳整个图片
rotated_img_45_expand = img.rotate(45, expand=True)
rotated_img_45_expand.save(output_path_rotated_45)
print(f"图片已逆时针旋转 45 度(扩展)并保存为 {output_path_rotated_45}")
# 逆时针旋转 90 度,扩展画布
rotated_img_90 = img.rotate(90, expand=True)
rotated_img_90.save(output_path_rotated_90)
print(f"图片已逆时针旋转 90 度并保存为 {output_path_rotated_90}")
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”旋转图片时发生错误:{e}”)
“`
5.4 翻转 (Flipping) 和预设旋转
Pillow 提供了 img.transpose()
方法来实现水平翻转、垂直翻转以及按 90 度、180 度、270 度的预设旋转。这比 rotate()
方法更高效,尤其适用于这些标准变换。
“`python
from PIL import Image
image_path = ‘your_image.jpg’
output_path_flipped_h = ‘flipped_image_horizontal.jpg’
output_path_flipped_v = ‘flipped_image_vertical.jpg’
output_path_rotated_180 = ‘rotated_image_180.jpg’
try:
with Image.open(image_path) as img:
# 水平翻转 (镜像)
flipped_img_h = img.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
flipped_img_h.save(output_path_flipped_h)
print(f”图片已水平翻转并保存为 {output_path_flipped_h}”)
# 垂直翻转
flipped_img_v = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
flipped_img_v.save(output_path_flipped_v)
print(f"图片已垂直翻转并保存为 {output_path_flipped_v}")
# 旋转 180 度
rotated_img_180 = img.transpose(Image.Transpose.ROTATE_180)
rotated_img_180.save(output_path_rotated_180)
print(f"图片已旋转 180 度并保存为 {output_path_rotated_180}")
# 还有 ROTATE_90 和 ROTATE_270
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”翻转/旋转图片时发生错误:{e}”)
``
Image.Transpose` 枚举来指定变换类型,而不是旧版本中的整数常量。
在 Pillow 8.0.0 之后,推荐使用
6. 像素访问与操作
Pillow 允许你直接访问和修改单个像素的值,尽管对于大批量像素操作,通常有更高效的方法(如使用 point()
方法、操作整个通道或结合 NumPy)。
6.1 获取像素值
使用 img.getpixel((x, y))
方法可以获取指定坐标 (x, y)
的像素值。返回值的类型取决于图片模式:
* ‘L’ 模式:返回一个整数 (0-255)。
* ‘RGB’, ‘RGBA’ 等模式:返回一个元组,包含各通道的值。
“`python
from PIL import Image
image_path = ‘your_image.jpg’
try:
with Image.open(image_path) as img:
width, height = img.size
# 获取图片中心像素值
center_x, center_y = width // 2, height // 2
pixel_value = img.getpixel((center_x, center_y))
print(f"图片模式:{img.mode}")
print(f"中心像素 ({center_x},{center_y}) 的值:{pixel_value}")
# 获取左上角像素值
top_left_pixel = img.getpixel((0, 0))
print(f"左上角像素 (0,0) 的值:{top_left_pixel}")
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”获取像素值时发生错误:{e}”)
“`
6.2 设置像素值
使用 img.putpixel((x, y), color)
方法可以设置指定坐标 (x, y)
的像素值。color
参数的类型取决于图片模式:
* ‘L’ 模式:一个整数 (0-255)。
* ‘RGB’, ‘RGBA’ 等模式:一个元组,包含各通道的值。
“`python
from PIL import Image
image_path = ‘your_image.jpg’
output_path_pixel = ‘pixel_manipulated.jpg’
try:
with Image.open(image_path).convert(‘RGB’) as img: # 确保是可修改像素的模式
width, height = img.size
# 在图片中心绘制一个红色的像素点
center_x, center_y = width // 2, height // 2
red = (255, 0, 0) # RGB 颜色值
img.putpixel((center_x, center_y), red)
print(f"在中心点 ({center_x},{center_y}) 设置像素为红色")
# 在左上角绘制一个蓝色的像素点
blue = (0, 0, 255)
img.putpixel((0, 0), blue)
print(f"在左上角点 (0,0) 设置像素为蓝色")
# 注意:直接 putpixel 通常用于少量像素操作
# 如果需要修改大量像素,或者对每个像素应用函数,考虑使用 img.point()
# 或 img.load() 结合循环(但仍然不如 NumPy 高效)
# 或者转换为 NumPy 数组进行操作
img.save(output_path_pixel)
print(f"修改像素后的图片已保存为 {output_path_pixel}")
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”操作像素时发生错误:{e}”)
“`
对于大批量像素操作,img.point(function)
方法非常有用,它可以将一个函数应用于图片的每一个像素(或每一个通道的像素),并返回一个新的图片。例如,实现图片的二值化:
“`python
from PIL import Image
image_path = ‘your_image.jpg’
output_path_binary = ‘binary_image.jpg’
try:
with Image.open(image_path).convert(‘L’) as img_gray: # 转换为灰度图
# 对灰度图进行二值化,阈值为 128
# 小于 128 的像素设为 0 (黑色),大于等于 128 的设为 255 (白色)
binary_img = img_gray.point(lambda p: 0 if p < 128 else 255, ‘1’) # ‘1’ 模式表示 1位像素,黑白
# 注意:使用 '1' 模式保存可以节省空间,但通常我们会转回 'L' 或 'RGB' 以便后续处理或显示
# binary_img = img_gray.point(lambda p: 0 if p < 128 else 255, 'L') # 保存为灰度图模式
binary_img.save(output_path_binary)
print(f"图片已二值化并保存为 {output_path_binary}")
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”二值化图片时发生错误:{e}”)
“`
point()
方法的第二个参数可以指定输出模式。
7. 颜色模式转换
不同图像模式之间的转换是常见的需求,例如将彩色图片转换为灰度图,或者为图片添加 Alpha 通道以支持透明度。
使用 img.convert(mode)
方法可以改变图片的模式,并返回一个新的 Image
对象。
“`python
from PIL import Image
image_path_color = ‘your_image_color.jpg’ # 假设这是一个彩色图片
image_path_transparent = ‘your_image_transparent.png’ # 假设这是一个带透明度的图片
创建一些示例图片文件(如果不存在)
if not os.path.exists(image_path_color):
# 创建一个示例 RGB 图片
color_img = Image.new(‘RGB’, (100, 100), color = ‘red’)
color_img.save(image_path_color)
print(f”创建了示例文件: {image_path_color}”)
if not os.path.exists(image_path_transparent):
# 创建一个示例 RGBA 图片
transparent_img = Image.new(‘RGBA’, (100, 100), color = (0, 0, 255, 128)) # 半透明蓝色
transparent_img.save(image_path_transparent)
print(f”创建了示例文件: {image_path_transparent}”)
output_path_gray = ‘grayscale_image.jpg’
output_path_rgba = ‘rgba_image.png’
output_path_rgb_flatten = ‘rgb_flattened.jpg’
try:
# 将彩色图片转换为灰度图
with Image.open(image_path_color) as img_color:
gray_img = img_color.convert(‘L’)
print(f”将 {img_color.mode} 图片转换为 {gray_img.mode} 模式”)
gray_img.save(output_path_gray)
print(f”灰度图已保存为 {output_path_gray}”)
# 将彩色图片添加 Alpha 通道,转换为 RGBA 模式
with Image.open(image_path_color) as img_color:
rgba_img = img_color.convert('RGBA')
print(f"将 {img_color.mode} 图片转换为 {rgba_img.mode} 模式")
rgba_img.save(output_path_rgba.replace('.png', '_from_rgb.png'))
print(f"转换为 RGBA 的图片已保存为 {output_path_rgba.replace('.png', '_from_rgb.png')}")
# 将 RGBA 图片转换为 RGB 模式 (Alpha 通道会被忽略或与背景混合)
# 默认情况下,透明区域会与黑色背景混合
with Image.open(image_path_transparent) as img_transparent:
rgb_img_flatten = img_transparent.convert('RGB')
print(f"将 {img_transparent.mode} 图片转换为 {rgb_img_flatten.mode} 模式 (默认黑色背景)")
rgb_img_flatten.save(output_path_rgb_flatten.replace('.jpg', '_default.jpg'))
print(f"转换为 RGB (默认黑色背景) 的图片已保存为 {output_path_rgb_flatten.replace('.jpg', '_default.jpg')}")
# 将 RGBA 图片转换为 RGB 模式,并指定背景颜色
with Image.open(image_path_transparent) as img_transparent:
# 将透明区域与白色背景混合
rgb_img_flatten_white = img_transparent.convert('RGB', background=(255, 255, 255))
print(f"将 {img_transparent.mode} 图片转换为 {rgb_img_flatten_white.mode} 模式 (白色背景)")
rgb_img_flatten_white.save(output_path_rgb_flatten.replace('.jpg', '_white.jpg'))
print(f"转换为 RGB (白色背景) 的图片已保存为 {output_path_rgb_flatten.replace('.jpg', '_white.jpg')}")
except FileNotFoundError:
print(f”错误:文件未找到”) # 针对示例文件可能未创建的情况
except Exception as e:
print(f”颜色模式转换时发生错误:{e}”)
“`
8. 在图片上绘制图形和文本
Pillow 的 ImageDraw
模块提供了在 Image
对象上绘制各种图形(点、线、矩形、椭圆、多边形)和文本的功能。
首先需要从 PIL
导入 ImageDraw
,然后创建一个 Draw
对象,将其关联到你要绘制的 Image
对象上。所有的绘制操作都在 Draw
对象上进行。
“`python
from PIL import Image, ImageDraw, ImageFont
创建一个空白图片作为画布
width, height = 400, 300
canvas = Image.new(‘RGB’, (width, height), color = (200, 200, 200)) # 浅灰色背景
创建 Draw 对象
draw = ImageDraw.Draw(canvas)
绘制图形
绘制一条直线 (start_x, start_y, end_x, end_y),颜色为黑色
draw.line([(50, 50), (350, 250)], fill=(0, 0, 0), width=2)
draw.line([(50, 250), (350, 50)], fill=(0, 0, 0), width=2)
绘制一个矩形 (left, upper, right, lower)
draw.rectangle([(100, 100), (300, 200)], outline=(255, 0, 0), width=3) # 红色边框
绘制一个填充了蓝色的圆形 (椭圆的外接矩形 box=(left, upper, right, lower))
draw.ellipse([(150, 120), (250, 180)], fill=(0, 0, 255)) # 蓝色填充
绘制一个多边形 (点坐标列表)
draw.polygon([(200, 10), (250, 40), (150, 40)], fill=(0, 255, 0)) # 绿色填充三角形
绘制文本
加载字体 (使用系统字体或提供 .ttf 文件路径)
Pillow 会尝试加载默认字体,但可能依赖于操作系统环境
try:
# 尝试加载一个常见的字体文件,或者使用 load_default_font()
font = ImageFont.truetype(“arial.ttf”, 30) # 例如,Windows 下的 Arial 字体
except IOError:
# 如果 arial.ttf 不存在,加载 Pillow 的默认字体
print(“未找到 arial.ttf 字体文件,使用默认字体。”)
font = ImageFont.load_default() # 默认字体通常较小且简陋
绘制文本 (position, text, fill, font)
text_position = (50, 20)
text_color = (50, 50, 50) # 深灰色
draw.text(text_position, “Hello, Pillow!”, fill=text_color, font=font)
保存绘制后的图片
output_path_drawing = ‘drawing_example.png’
canvas.save(output_path_drawing)
print(f”绘制图形和文本的图片已保存为 {output_path_drawing}”)
你也可以在现有图片上绘制
try:
with Image.open(‘your_image.jpg’) as img:
draw_on_img = ImageDraw.Draw(img)
# 在 img 上进行绘制操作
draw_on_img.text((10, 10), “Watermark”, fill=(255, 255, 255), font=font)
img.save(‘watermarked_image.jpg’)
except FileNotFoundError:
print(“未找到 your_image.jpg”)
except Exception as e:
print(f”在现有图片上绘制时发生错误:{e}”)
``
ImageFont.truetype()
绘制文本时,字体的加载是一个需要注意的地方,需要一个字体文件路径。如果没有特定的字体文件,可以使用
ImageFont.load_default()加载一个基础字体。为了获得文本的精确边界框以进行定位(如居中),可以使用
draw.textbbox(position, text, font=font)` 方法。
9. 图像滤镜 (Image Filtering)
Pillow 提供了 ImageFilter
模块,其中包含了一些预定义的图像滤镜,如模糊、锐化、边缘检测等。
“`python
from PIL import Image, ImageFilter
image_path = ‘your_image.jpg’
output_path_blurred = ‘blurred_image.jpg’
output_path_sharpened = ‘sharpened_image.jpg’
try:
with Image.open(image_path) as img:
# 应用高斯模糊滤镜
blurred_img = img.filter(ImageFilter.GaussianBlur(radius=2))
blurred_img.save(output_path_blurred)
print(f”图片已应用高斯模糊并保存为 {output_path_blurred}”)
# 应用锐化滤镜
sharpened_img = img.filter(ImageFilter.SHARPEN)
sharpened_img.save(output_path_sharpened)
print(f"图片已应用锐化滤镜并保存为 {output_path_sharpened}")
# 还可以尝试其他滤镜:
# ImageFilter.BLUR
# ImageFilter.CONTOUR
# ImageFilter.DETAIL
# ImageFilter.EDGE_ENHANCE
# ImageFilter.EMBOSS
# ImageFilter.FIND_EDGES
# ImageFilter.SMOOTH
# ImageFilter.GaussianBlur(radius=...)
# ImageFilter.UnsharpMask(radius=..., percent=..., threshold=...)
except FileNotFoundError:
print(f”错误:文件未找到 ‘{image_path}'”)
except Exception as e:
print(f”应用滤镜时发生错误:{e}”)
``
ImageFilter.GaussianBlur()接受一个
radius` 参数来控制模糊程度。
10. 图像合成与粘贴
使用 img.paste()
方法可以将一个图像粘贴到另一个图像上。这在创建合成图像、添加水印等方面非常有用。
paste()
方法接受至少两个参数:要粘贴的图像 (source
) 和粘贴的位置 (box
)。如果 source
是 RGBA 图像或者提供了 mask
参数,可以实现透明粘贴。
“`python
from PIL import Image
创建一个背景图片
background = Image.new(‘RGB’, (400, 300), color = (255, 255, 255)) # 白色背景
创建一个前景图片 (带有透明度的 PNG 图片)
如果你没有带透明度的图片,可以创建一个示例
try:
foreground_path = ‘your_logo_transparent.png’ # 假设这是一个带透明度的 PNG
if not os.path.exists(foreground_path):
# 创建一个示例 RGBA 图片作为前景
fg_img_example = Image.new(‘RGBA’, (100, 80), color=(0, 255, 0, 128)) # 半透明绿色矩形
fg_draw = ImageDraw.Draw(fg_img_example)
fg_draw.ellipse([(10, 10), (90, 70)], fill=(255, 0, 0, 192)) # 半透明红色椭圆
fg_img_example.save(foreground_path)
print(f”创建了示例文件: {foreground_path}”)
with Image.open(foreground_path) as foreground:
# 将前景图片粘贴到背景图片的右下角
# 计算粘贴位置 (left, upper)
bg_width, bg_height = background.size
fg_width, fg_height = foreground.size
paste_x = bg_width - fg_width - 10 # 距离右边缘 10 像素
paste_y = bg_height - fg_height - 10 # 距离底边缘 10 像素
paste_box = (paste_x, paste_y)
# 执行粘贴操作
# 如果前景图片是 RGBA 模式,直接 paste 即可实现透明粘贴
# 如果前景图片是 RGB,但有单独的 alpha mask 图片,可以用 mask=alpha_img 参数
background.paste(foreground, paste_box, foreground) # 第三个参数是 mask
output_path_pasted = 'pasted_image.png' # 使用 PNG 格式保存以保留可能的透明度(尽管背景是白色)
background.save(output_path_pasted)
print(f"图片已合成并保存为 {output_path_pasted}")
except FileNotFoundError:
print(f”错误:文件未找到 foreground 图片 ‘{foreground_path}'”)
except Exception as e:
print(f”合成图片时发生错误:{e}”)
``
mask` 图)有 Alpha 通道,Pillow 会自动使用 Alpha 通道作为掩码进行透明混合。
在粘贴时,如果前景图(或
11. 处理多种图片格式
Pillow 支持打开和保存多种图片格式,包括但不限于 JPEG, PNG, GIF, TIFF, BMP, SVG (需要额外安装插件) 等。通常情况下,你只需要通过文件扩展名来指定格式,Pillow 会自动识别和处理。
对于一些特殊格式(如多帧 GIF)或需要格式特定参数(如 JPEG 压缩质量),你可以在 save()
方法中指定。
“`python
from PIL import Image
import os
示例:处理一个可能存在的 GIF 动画
gif_path = ‘animated.gif’
output_first_frame = ‘first_frame.png’
创建一个示例 GIF 文件(如果不存在)
if not os.path.exists(gif_path):
print(f”未找到示例 GIF 文件 {gif_path},跳过 GIF 示例。”)
# 你可以使用 Pillow 创建一个简单的 GIF 动画
try:
frames = []
for i in range(10):
frame = Image.new(‘RGB’, (100, 50), color=(i25, 0, 255 – i25))
d = ImageDraw.Draw(frame)
d.text((10, 10), f”Frame {i}”, fill=(255, 255, 255), font=ImageFont.load_default())
frames.append(frame)
frames[0].save(gif_path, save_all=True, append_images=frames[1:], duration=200, loop=0)
print(f”创建了示例 GIF 文件: {gif_path}”)
except Exception as e:
print(f”创建示例 GIF 失败: {e}”)
try:
if os.path.exists(gif_path):
# 打开 GIF 动画
with Image.open(gif_path) as img_gif:
print(f”打开了 GIF 文件:{gif_path}”)
print(f”GIF 格式:{img_gif.format}”)
print(f”GIF 模式:{img_gif.mode}”)
print(f”GIF 尺寸:{img_gif.size}”)
# 获取第一帧并保存
img_gif.seek(0) # 定位到第一帧 (索引 0)
first_frame = img_gif.copy() # 复制当前帧
first_frame.save(output_first_frame)
print(f"第一帧已保存为 {output_first_frame}")
# 遍历所有帧
frame_count = 0
try:
while True:
img_gif.seek(frame_count)
# 在这里可以处理每一帧 img_gif 对象
# 例如 frame_img = img_gif.copy()
frame_count += 1
except EOFError:
pass # 已到达 GIF 结尾
print(f"GIF 共 {frame_count} 帧。")
except FileNotFoundError:
# 这个错误应该不会发生,因为上面已经检查或创建了文件
pass
except Exception as e:
print(f”处理 GIF 时发生错误:{e}”)
``
img.seek(frame_index)` 来跳转到指定帧,然后对当前帧进行操作。通过循环和异常处理可以遍历所有帧。
对于多帧图片,使用
12. 其他有用模块和功能
- ImageEnhance: 包含用于调整图像亮度、对比度、颜色平衡和锐度的类。
python
from PIL import Image, ImageEnhance
# ... 打开图片 ...
enhancer = ImageEnhance.Brightness(img)
bright_img = enhancer.enhance(1.5) # 增加 50% 亮度
# ... 保存图片 ... - ExifTags: 用于访问 JPEG 图片的 EXIF 元数据。
python
from PIL import Image, ExifTags
# ... 打开 JPEG 图片 ...
exif = img._getexif()
if exif:
exif_tags = {
ExifTags.TAGS[k]: v
for k, v in exif.items()
if k in ExifTags.TAGS
}
print(exif_tags) # 打印 EXIF 字典 - ImageSequence: 提供了一种更方便的方式来迭代多帧图片。
13. 实际应用场景
Pillow 库在许多实际场景中都发挥着重要作用:
- Web 应用: 生成缩略图、处理用户上传的图片、添加水印等。
- 自动化脚本: 批量处理大量图片,例如格式转换、重命名、调整尺寸。
- 数据预处理: 为机器学习和计算机视觉任务准备图像数据,如尺寸归一化、颜色空间转换、数据增强(旋转、翻转、裁剪)。
- 简单的图像编辑工具: 构建命令行或 GUI 图片处理工具。
- 生成艺术或图表: 在图片上绘制图形、文字,结合数据生成可视化图片。
14. 性能考虑与与 NumPy 的结合
虽然 Pillow 对于大多数常见的图像处理任务已经足够高效,但对于像素级的复杂计算,例如自定义滤镜、复杂的颜色变换等,直接使用 Pillow 的 putpixel
或 point
可能不够快。
在这种情况下,一个常见的做法是将 Pillow 的 Image
对象转换为 NumPy 数组,利用 NumPy 强大的数组计算能力进行处理,然后再将结果转换回 Image
对象。
- Image 转 NumPy 数组:
numpy.asarray(img)
或np.array(img)
- NumPy 数组转 Image:
Image.fromarray(np_array)
“`python
import numpy as np
from PIL import Image
… 打开图片 …
try:
with Image.open(‘your_image.jpg’) as img:
# 转换为 NumPy 数组
img_np = np.asarray(img)
# 对 NumPy 数组进行操作 (例如,反转颜色)
# 注意:操作取决于数组的 dtype 和图片的 mode
if img.mode == ‘RGB’:
inverted_img_np = 255 – img_np
elif img.mode == ‘L’:
inverted_img_np = 255 – img_np
else:
print(“当前模式不支持简单反转”)
inverted_img_np = img_np # 不做操作
# 将 NumPy 数组转换回 Image 对象
# 需要确保 dtype 和 mode 匹配
inverted_img = Image.fromarray(np.uint8(inverted_img_np), mode=img.mode)
# … 保存 inverted_img …
except Exception as e:
print(f”结合 NumPy 操作时发生错误:{e}”)
“`
结合 NumPy 可以显著提高复杂像素操作的性能,是进行更高级图像处理时非常有用的技巧。
15. 结论
Pillow 库是 Python 中进行图像处理的首选工具,它提供了丰富的功能和易于使用的接口,能够满足从简单的图片操作到复杂的图像分析和生成的需求。本文从 Pillow 的基本概念入手,详细介绍了图片文件的打开、保存、显示,以及尺寸调整、裁剪、旋转、翻转等常用几何变换,像素的访问与操作,颜色模式的转换,以及在图片上绘制图形和文本。同时,也简要提及了滤镜应用、图像合成、多格式处理和与 NumPy 结合进行高性能操作等进阶话题。
掌握 Pillow 库,你将能够高效地处理各种图像相关的任务,无论是用于个人项目、Web 开发还是数据科学领域。希望本文能为你深入学习和应用 Pillow 库提供坚实的基础。
继续探索 Pillow 的官方文档,你会发现更多高级的功能和灵活的应用方式。祝你在 Python 图像处理的世界中取得成功!