教程¶
使用 Image 类¶
Python Imaging Library 中最重要的类是 Image
类,它在同名模块中定义。您可以通过多种方式创建此类的实例;要么从文件加载图像,要么处理其他图像,要么从头开始创建图像。
要从文件加载图像,请使用 open()
函数,该函数在 Image
模块中。
>>> from PIL import Image
>>> im = Image.open("hopper.ppm")
如果成功,此函数将返回一个 Image
对象。您现在可以使用实例属性来检查文件内容
>>> print(im.format, im.size, im.mode)
PPM (512, 512) RGB
The format
属性标识图像的来源。如果图像不是从文件读取的,则将其设置为 None。size 属性是一个包含宽度和高度(以像素为单位)的 2 元组。The mode
属性定义图像中波段的数量和名称,以及像素类型和深度。常见的模式有“L”(亮度),用于灰度图像,“RGB”,用于真彩色图像,“CMYK”,用于印刷前图像。
如果无法打开文件,则会引发 OSError
异常。
一旦您拥有 Image
类的实例,就可以使用此类定义的方法来处理和操作图像。例如,让我们显示我们刚刚加载的图像
>>> im.show()
注意
标准版本的 show()
效率不高,因为它会将图像保存到临时文件,并调用实用程序来显示图像。如果您没有安装适当的实用程序,它甚至无法工作。但是,当它确实起作用时,它对于调试和测试非常方便。
以下部分概述了此库中提供的不同函数。
读取和写入图像¶
Python Imaging Library 支持各种图像文件格式。要从磁盘读取文件,请使用 open()
函数,该函数在 Image
模块中。您不必知道文件格式即可打开文件。该库会根据文件的内容自动确定格式。
要保存文件,请使用 save()
方法,该方法是 Image
类的。保存文件时,名称变得很重要。除非您指定格式,否则库将使用文件名扩展名来发现要使用的文件存储格式。
将文件转换为 JPEG¶
import os, sys
from PIL import Image
for infile in sys.argv[1:]:
f, e = os.path.splitext(infile)
outfile = f + ".jpg"
if infile != outfile:
try:
with Image.open(infile) as im:
im.save(outfile)
except OSError:
print("cannot convert", infile)
第二个参数可以提供给 save()
方法,它明确指定文件格式。如果您使用非标准扩展名,则必须始终以这种方式指定格式
创建 JPEG 缩略图¶
import os, sys
from PIL import Image
size = (128, 128)
for infile in sys.argv[1:]:
outfile = os.path.splitext(infile)[0] + ".thumbnail"
if infile != outfile:
try:
with Image.open(infile) as im:
im.thumbnail(size)
im.save(outfile, "JPEG")
except OSError:
print("cannot create thumbnail for", infile)
重要的是要注意,除非确实需要,否则库不会解码或加载光栅数据。当您打开文件时,会读取文件头以确定文件格式并提取诸如模式、大小以及解码文件所需的其他属性,但其余文件直到以后才会处理。
这意味着打开图像文件是一个快速操作,与文件大小和压缩类型无关。以下是一个简单脚本,用于快速识别一组图像文件
识别图像文件¶
import sys
from PIL import Image
for infile in sys.argv[1:]:
try:
with Image.open(infile) as im:
print(infile, im.format, f"{im.size}x{im.mode}")
except OSError:
pass
剪切、粘贴和合并图像¶
The Image
类包含允许您操作图像中区域的方法。要从图像中提取子矩形,请使用 crop()
方法。
从图像中复制子矩形¶
box = (0, 0, 64, 64)
region = im.crop(box)
该区域由一个 4 元组定义,其中坐标为 (left, upper, right, lower)。Python Imaging Library 使用以左上角为 (0, 0) 的坐标系。还要注意,坐标指的是像素之间的位置,因此上述示例中的区域正好是 64x64 像素。
现在可以以某种方式处理该区域,并将其粘贴回去。
处理子矩形,并将其粘贴回去¶
region = region.transpose(Image.Transpose.ROTATE_180)
im.paste(region, box)
粘贴区域时,区域的大小必须与给定区域完全匹配。此外,区域不能扩展到图像之外。但是,原始图像和区域的模式不必匹配。如果它们不匹配,则在粘贴之前会自动转换区域(有关详细信息,请参阅下面的 颜色转换 部分)。
以下是一个额外的示例
滚动图像¶
def roll(im: Image.Image, delta: int) -> Image.Image:
"""Roll an image sideways."""
xsize, ysize = im.size
delta = delta % xsize
if delta == 0:
return im
part1 = im.crop((0, 0, delta, ysize))
part2 = im.crop((delta, 0, xsize, ysize))
im.paste(part1, (xsize - delta, 0, xsize, ysize))
im.paste(part2, (0, 0, xsize - delta, ysize))
return im
或者,如果您想将两张图像合并成更宽的图像
合并图像¶
def merge(im1: Image.Image, im2: Image.Image) -> Image.Image:
w = im1.size[0] + im2.size[0]
h = max(im1.size[1], im2.size[1])
im = Image.new("RGBA", (w, h))
im.paste(im1)
im.paste(im2, (im1.size[0], 0))
return im
对于更高级的技巧,粘贴方法也可以选择性地接受一个透明度蒙版作为参数。在这个蒙版中,值 255 表示粘贴的图像在该位置是不透明的(即,粘贴的图像应该按原样使用)。值 0 表示粘贴的图像完全透明。介于两者之间的值表示不同的透明度级别。例如,粘贴一个 RGBA 图像并将其用作蒙版,将会粘贴图像的透明部分,但不会粘贴其透明背景。
Python 图像库还允许您处理多波段图像的各个波段,例如 RGB 图像。split 方法会创建一个新的图像集,每个图像包含原始多波段图像中的一个波段。merge 函数接收一个模式和一个图像元组,并将它们合并成一个新图像。以下示例交换了 RGB 图像的三个波段
拆分和合并波段¶
r, g, b = im.split()
im = Image.merge("RGB", (b, g, r))
注意,对于单波段图像,split()
会返回图像本身。要处理各个颜色波段,您可能需要先将图像转换为“RGB”。
几何变换¶
PIL.Image.Image
类包含用于resize()
和 rotate()
图像的方法。前者接收一个元组,表示新的大小,后者接收一个表示逆时针旋转角度的数值。
简单的几何变换¶
out = im.resize((128, 128))
out = im.rotate(45) # degrees counter-clockwise
要将图像以 90 度的步长旋转,您可以使用 rotate()
方法或 transpose()
方法。后者还可以用于将图像围绕其水平或垂直轴翻转。
转置图像¶
out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
out = im.transpose(Image.Transpose.ROTATE_90)
out = im.transpose(Image.Transpose.ROTATE_180)
out = im.transpose(Image.Transpose.ROTATE_270)
transpose(ROTATE)
操作也可以与 rotate()
操作完全相同地执行,前提是 expand
标志为真,以提供对图像大小的相同更改。
更一般的图像变换形式可以通过 transform()
方法来执行。
相对大小调整¶
在调整大小的时候,除了计算新图像的大小之外,您还可以选择相对于给定大小进行调整大小。
from PIL import Image, ImageOps
size = (100, 150)
with Image.open("hopper.webp") as im:
ImageOps.contain(im, size).save("imageops_contain.webp")
ImageOps.cover(im, size).save("imageops_cover.webp")
ImageOps.fit(im, size).save("imageops_fit.webp")
ImageOps.pad(im, size, color="#f00").save("imageops_pad.webp")
# thumbnail() can also be used,
# but will modify the image object in place
im.thumbnail(size)
im.save("image_thumbnail.webp")
给定大小 |
|
|
|
|
|
结果图像 |
|||||
结果大小 |
|
|
|
|
|
颜色变换¶
Python 图像库允许您使用 convert()
方法在不同的像素表示之间转换图像。
在模式之间转换¶
from PIL import Image
with Image.open("hopper.ppm") as im:
im = im.convert("L")
该库支持在每个支持的模式和“L”和“RGB”模式之间进行转换。要转换其他模式,您可能需要使用中间图像(通常是“RGB”图像)。
图像增强¶
Python 图像库提供了一些方法和模块,可用于增强图像。
过滤器¶
ImageFilter
模块包含一些预定义的增强过滤器,可与 filter()
方法一起使用。
应用过滤器¶
from PIL import ImageFilter
out = im.filter(ImageFilter.DETAIL)
点操作¶
point()
方法可用于转换图像的像素值(例如图像对比度操作)。在大多数情况下,可以将一个接受一个参数的函数对象传递给此方法。每个像素都根据该函数进行处理
应用点变换¶
# multiply each pixel by 20
out = im.point(lambda i: i * 20)
使用上述技术,您可以快速将任何简单表达式应用于图像。您还可以将 point()
和 paste()
方法结合起来,有选择地修改图像
处理各个波段¶
# split the image into individual bands
source = im.split()
R, G, B = 0, 1, 2
# select regions where red is less than 100
mask = source[R].point(lambda i: i < 100 and 255)
# process the green band
out = source[G].point(lambda i: i * 0.7)
# paste the processed band back, but only where red was < 100
source[G].paste(out, None, mask)
# build a new multiband image
im = Image.merge(im.mode, source)
注意用于创建蒙版的语法
imout = im.point(lambda i: expression and 255)
Python 仅根据确定结果所需的部分逻辑表达式进行求值,并返回检查的最后一个值作为表达式的结果。因此,如果上述表达式为假 (0),Python 不会查看第二个操作数,因此返回 0。否则,它会返回 255。
增强¶
对于更高级的图像增强,您可以使用 ImageEnhance
模块中的类。从图像创建后,增强对象可用于快速尝试不同的设置。
您可以通过这种方式调整对比度、亮度、色彩平衡和锐度。
增强图像¶
from PIL import ImageEnhance
enh = ImageEnhance.Contrast(im)
enh.enhance(1.3).show("30% more contrast")
图像序列¶
Python 图像库包含对图像序列(也称为动画格式)的一些基本支持。支持的序列格式包括 FLI/FLC、GIF 和一些实验性格式。TIFF 文件也可以包含多个帧。
当您打开一个序列文件时,PIL 会自动加载序列中的第一帧。您可以使用 seek 和 tell 方法在不同的帧之间移动
读取序列¶
from PIL import Image
with Image.open("animation.gif") as im:
im.seek(1) # skip to the second frame
try:
while 1:
im.seek(im.tell() + 1)
# do something to im
except EOFError:
pass # end of sequence
如本示例所示,当序列结束时,您将收到一个 EOFError
异常。
写入序列¶
您可以使用 Pillow 创建动画 GIF,例如
from PIL import Image
# List of image filenames
image_filenames = [
"hopper.jpg",
"rotated_hopper_270.jpg",
"rotated_hopper_180.jpg",
"rotated_hopper_90.jpg",
]
# Open images and create a list
images = [Image.open(filename) for filename in image_filenames]
# Save the images as an animated GIF
images[0].save(
"animated_hopper.gif",
save_all=True,
append_images=images[1:],
duration=500, # duration of each frame in milliseconds
loop=0, # loop forever
)
以下类允许您使用 for 语句遍历序列
使用 Iterator
类¶
from PIL import ImageSequence
for frame in ImageSequence.Iterator(im):
# ...do something to frame...
PostScript 打印¶
Python 图像库包含用于在 PostScript 打印机上打印图像、文本和图形的函数。这是一个简单的示例
绘制 PostScript¶
from PIL import Image, PSDraw
import os
# Define the PostScript file
ps_file = open("hopper.ps", "wb")
# Create a PSDraw object
ps = PSDraw.PSDraw(ps_file)
# Start the document
ps.begin_document()
# Set the text to be drawn
text = "Hopper"
# Define the PostScript font
font_name = "Helvetica-Narrow-Bold"
font_size = 36
# Calculate text size (approximation as PSDraw doesn't provide direct method)
# Assuming average character width as 0.6 of the font size
text_width = len(text) * font_size * 0.6
text_height = font_size
# Set the position (top-center)
page_width, page_height = 595, 842 # A4 size in points
text_x = (page_width - text_width) // 2
text_y = page_height - text_height - 50 # Distance from the top of the page
# Load the image
image_path = "hopper.ppm" # Update this with your image path
with Image.open(image_path) as im:
# Resize the image if it's too large
im.thumbnail((page_width - 100, page_height // 2))
# Define the box where the image will be placed
img_x = (page_width - im.width) // 2
img_y = text_y + text_height - 200 # 200 points below the text
# Draw the image (75 dpi)
ps.image((img_x, img_y, img_x + im.width, img_y + im.height), im, 75)
# Draw the text
ps.setfont(font_name, font_size)
ps.text((text_x, text_y), text)
# End the document
ps.end_document()
ps_file.close()
注意
为显示目的将 PostScript 转换为 PDF
有关读取图像的更多信息¶
如前所述,open()
函数是 Image
模块中用于打开图像文件的函数。在大多数情况下,您只需将文件名作为参数传递给它。 Image.open()
可用作上下文管理器
from PIL import Image
with Image.open("hopper.ppm") as im:
...
如果一切顺利,结果将是一个 PIL.Image.Image
对象。否则,将引发一个 OSError
异常。
您可以使用类文件对象代替文件名。该对象必须实现 file.read
、file.seek
和 file.tell
方法,并且必须以二进制模式打开。
从打开的文件读取¶
from PIL import Image
with open("hopper.ppm", "rb") as fp:
im = Image.open(fp)
要从二进制数据中读取图像,请使用 BytesIO
类
从二进制数据读取¶
from PIL import Image
import io
im = Image.open(io.BytesIO(buffer))
注意,库在读取图像头之前会将文件倒带(使用 seek(0)
)。此外,在读取图像数据(通过 load 方法)时,也会使用 seek。如果图像文件嵌入在更大的文件中(例如 tar 文件),您可以使用 ContainerIO
或 TarIO
模块访问它。
从 URL 读取¶
from PIL import Image
from urllib.request import urlopen
url = "https://python-pillow.org/assets/images/pillow-logo.png"
img = Image.open(urlopen(url))
从 tar 归档文件读取¶
from PIL import Image, TarIO
fp = TarIO.TarIO("hopper.tar", "hopper.jpg")
im = Image.open(fp)
批量处理¶
操作可以应用于多个图像文件。例如,可以将当前目录中的所有 PNG 图像以较低的质量保存为 JPEG。
import glob
from PIL import Image
def compress_image(source_path: str, dest_path: str) -> None:
with Image.open(source_path) as img:
if img.mode != "RGB":
img = img.convert("RGB")
img.save(dest_path, "JPEG", optimize=True, quality=80)
paths = glob.glob("*.png")
for path in paths:
compress_image(path, path[:-4] + ".jpg")
由于图像也可以从 Path
(来自 pathlib
模块)打开,因此可以修改示例以使用 pathlib
而不是 glob
模块。
from pathlib import Path
paths = Path(".").glob("*.png")
for path in paths:
compress_image(path, path.stem + ".jpg")
控制解码器¶
一些解码器允许您在从文件读取图像时操作图像。这通常可用于在创建缩略图(速度通常比质量更重要)和打印到单色激光打印机(仅需要图像的灰度版本)时加速解码。
The draft()
方法操作打开但尚未加载的图像,使其尽可能接近给定的模式和大小。这是通过重新配置图像解码器来完成的。
以草稿模式读取¶
这仅适用于 JPEG 和 MPO 文件。
from PIL import Image
with Image.open(file) as im:
print("original =", im.mode, im.size)
im.draft("L", (100, 100))
print("draft =", im.mode, im.size)
这将打印类似以下内容:
original = RGB (512, 512)
draft = L (128, 128)
请注意,生成的图像可能与请求的模式和大小不完全匹配。要确保图像不超过给定大小,请使用缩略图方法。