教程

使用 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()
../_images/show_hopper.webp

注意

标准版本的 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)
../_images/hopper.jpg

第二个参数可以提供给 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)
../_images/thumbnail_hopper.jpg

重要的是要注意,除非确实需要,否则库不会解码或加载光栅数据。当您打开文件时,会读取文件头以确定文件格式并提取诸如模式、大小以及解码文件所需的其他属性,但其余文件直到以后才会处理。

这意味着打开图像文件是一个快速操作,与文件大小和压缩类型无关。以下是一个简单脚本,用于快速识别一组图像文件

识别图像文件

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 像素。

现在可以以某种方式处理该区域,并将其粘贴回去。

../_images/cropped_hopper.webp

处理子矩形,并将其粘贴回去

region = region.transpose(Image.Transpose.ROTATE_180)
im.paste(region, box)

粘贴区域时,区域的大小必须与给定区域完全匹配。此外,区域不能扩展到图像之外。但是,原始图像和区域的模式不必匹配。如果它们不匹配,则在粘贴之前会自动转换区域(有关详细信息,请参阅下面的 颜色转换 部分)。

../_images/pasted_hopper.webp

以下是一个额外的示例

滚动图像

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
../_images/rolled_hopper.webp

或者,如果您想将两张图像合并成更宽的图像

合并图像

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
../_images/merged_hopper.webp

对于更高级的技巧,粘贴方法也可以选择性地接受一个透明度蒙版作为参数。在这个蒙版中,值 255 表示粘贴的图像在该位置是不透明的(即,粘贴的图像应该按原样使用)。值 0 表示粘贴的图像完全透明。介于两者之间的值表示不同的透明度级别。例如,粘贴一个 RGBA 图像并将其用作蒙版,将会粘贴图像的透明部分,但不会粘贴其透明背景。

Python 图像库还允许您处理多波段图像的各个波段,例如 RGB 图像。split 方法会创建一个新的图像集,每个图像包含原始多波段图像中的一个波段。merge 函数接收一个模式和一个图像元组,并将它们合并成一个新图像。以下示例交换了 RGB 图像的三个波段

拆分和合并波段

r, g, b = im.split()
im = Image.merge("RGB", (b, g, r))

注意,对于单波段图像,split() 会返回图像本身。要处理各个颜色波段,您可能需要先将图像转换为“RGB”。

../_images/rebanded_hopper.webp

几何变换

PIL.Image.Image 类包含用于resize()rotate() 图像的方法。前者接收一个元组,表示新的大小,后者接收一个表示逆时针旋转角度的数值。

简单的几何变换

out = im.resize((128, 128))
out = im.rotate(45) # degrees counter-clockwise
../_images/rotated_hopper_90.webp

要将图像以 90 度的步长旋转,您可以使用 rotate() 方法或 transpose() 方法。后者还可以用于将图像围绕其水平或垂直轴翻转。

转置图像

out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
../_images/flip_left_right_hopper.webp
out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
../_images/flip_top_bottom_hopper.webp
out = im.transpose(Image.Transpose.ROTATE_90)
../_images/rotated_hopper_90.webp
out = im.transpose(Image.Transpose.ROTATE_180)
../_images/rotated_hopper_180.webp
out = im.transpose(Image.Transpose.ROTATE_270)
../_images/rotated_hopper_270.webp

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")

thumbnail()

contain()

cover()

fit()

pad()

给定大小

(100, 150)

(100, 150)

(100, 150)

(100, 150)

(100, 150)

结果图像

../_images/image_thumbnail.webp ../_images/imageops_contain.webp ../_images/imageops_cover.webp ../_images/imageops_fit.webp ../_images/imageops_pad.webp

结果大小

100×100

100×100

150×150

100×150

100×150

颜色变换

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)
../_images/enhanced_hopper.webp

点操作

point() 方法可用于转换图像的像素值(例如图像对比度操作)。在大多数情况下,可以将一个接受一个参数的函数对象传递给此方法。每个像素都根据该函数进行处理

应用点变换

# multiply each pixel by 20
out = im.point(lambda i: i * 20)
../_images/transformed_hopper.webp

使用上述技术,您可以快速将任何简单表达式应用于图像。您还可以将 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)
../_images/masked_hopper.webp

Python 仅根据确定结果所需的部分逻辑表达式进行求值,并返回检查的最后一个值作为表达式的结果。因此,如果上述表达式为假 (0),Python 不会查看第二个操作数,因此返回 0。否则,它会返回 255。

增强

对于更高级的图像增强,您可以使用 ImageEnhance 模块中的类。从图像创建后,增强对象可用于快速尝试不同的设置。

您可以通过这种方式调整对比度、亮度、色彩平衡和锐度。

增强图像

from PIL import ImageEnhance

enh = ImageEnhance.Contrast(im)
enh.enhance(1.3).show("30% more contrast")
../_images/contrasted_hopper.jpg

图像序列

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
)
../_images/animated_hopper.gif

以下类允许您使用 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()
../_images/hopper_ps.webp

注意

为显示目的将 PostScript 转换为 PDF

有关读取图像的更多信息

如前所述,open() 函数是 Image 模块中用于打开图像文件的函数。在大多数情况下,您只需将文件名作为参数传递给它。 Image.open() 可用作上下文管理器

from PIL import Image
with Image.open("hopper.ppm") as im:
    ...

如果一切顺利,结果将是一个 PIL.Image.Image 对象。否则,将引发一个 OSError 异常。

您可以使用类文件对象代替文件名。该对象必须实现 file.readfile.seekfile.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 文件),您可以使用 ContainerIOTarIO 模块访问它。

从 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)

请注意,生成的图像可能与请求的模式和大小不完全匹配。要确保图像不超过给定大小,请使用缩略图方法。