Pillow综述

PIL(Python Imaging Library)为您的Python解释器添加了图像处理功能。

这个库提供了广泛的文件格式支持、高效的内部表示和相当强大的图像处理功能。

核心图像库是为快速访问以几种基本像素格式存储的数据而设计的。为通用图像处理工具提供了坚实的基础。

让我们看看这个库的一些可能用途。

图像存档(Image Archives)

PIL是理想的图像存档和批处理应用程序。您可以使用库创建缩略图,在文件格式、打印图像等之间进行转换。

当前版本标识和读取大量格式。写支持被有意限制为最常用的交换和表示格式。

图像显示(Image Display)

当前版本包含TK的 PhotoImageBitmapImage 接口,以及一个可以与PythonWin和其他基于Windows的工具包一起使用的 Windows DIB 接口。许多其他GUI工具包都带有某种PIL支持。

对于调试,还有一个 show() 方法,它将图像保存到磁盘,并调用外部显示程序。

图像处理(Image Processing)

该库包含基本的图像处理功能,包括像素点操作、使用一组内置卷积内核进行过滤和颜色空间转换。

还支持图像大小调整、旋转和任意仿射变换。

有一种直方图方法可以让你从一张图片中提取一些统计数据。这可以用于自动对比度增强和全局统计分析。

用户手册

使用 Image 类

PIL中最重要的类是 Image 类,它在同名模块中定义。您可以通过几种方式创建该类的实例;要么从文件中加载图像,要么处理其他图像,要么从头创建图像。

要从一个文件中加载一个图像,请使用 Image 模块中的 open() 方法:

>>> from PIL import Image
>>> im = Image.open("hopper.ppm")

如果上述代码正确执行,则会返回一个 Image 对象。你可以使用实例属性检查文件内容:

  • format 标识图像的源。如果没有从文件中读取图像,则其将被设置为 None
  • size 一个包含宽度和高度(以像素为单位)的2元组
  • mode 定义图像中频带的数量和名称,以及像素类型和深度。常用的模式是灰度图像的 “L”(亮度)、真彩色图像的 “RGB” 和印前图像的 “CMYK”。

如果文件无法打开,将引发 IOError 异常。

>>> print(im.format, im.size, im.mode)
JPEG (1278, 2274) RGB

一旦有了 Image 类的实例,就可以使用该类定义的方法来处理和操作图像。例如,显示刚才加载的图像:

>>> im.show()

注意:

标准版本的 show() 方法不是很有效,因为它将图像保存到一个临时文件中,并调用一个实用程序来显示图像。如果没有安装适当的实用程序,它甚至无法工作。但是当它可以正常工作时,它对于调试和测试非常方便。

下面几节概述了这个库中提供的不同功能。

图像的读和写

PIL支持多种图像文件格式。要从磁盘读取文件,请使用 Image 模块中的 open() 函数。您不必知道打开文件的文件格式。库根据文件的内容自动确定格式。

要保存文件,请使用 Image 类的 save() 方法。保存文件时,名称变得很重要。除非指定格式,否则库将使用文件名扩展名来发现要使用哪种文件存储格式。

将文件转换为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:
            Image.open(infile).save(outfile)
        except IOError:
            print("cannot convert", infile)

创建JPEG缩略图

Image 对象的 thumbnail(size, resample=3) 方法用于生成缩略图,该方法的第一个参数用于指定要生成的缩略图的大小,第二个参数用于指定重新取样的方式。

可以给 Image 对象的 save() 方法指定第二个参数,用以显式地指定文件格式。如果使用非标准扩展名,则必须始终以这种方式指定格式:

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:
            im = Image.open(infile)
            im.thumbnail(size)
            im.save(outfile, "JPEG")
        except IOError:
            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, "%dx%d" % im.size, im.mode)
    except IOError:
        pass

剪切、粘贴和合并图像

Image 类包含允许您操作图像中的区域的方法。要从图像中提取子矩形,可以使用 crop() 方法。

从图像中复制子矩形

box = (100, 100, 400, 400)
region = im.crop(box)

PIL将图片左上角的坐标 (0, 0) 定义为坐标原点。每一个区域由一个4元组定义,四元组的四个元素分别表示区域的左上角的横坐标、区域左上角的纵坐标、区域右下角的横坐标和区域右下角的纵坐标(upper_left_x、upper_y、lower_right_x、lower_right_y)。区域的大小(size)为 (lower_right_x - upper_left_x, lower_right_y - upper_left_y,所以上面例子中的区域的大小是300x300像素。

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

得到一个表示区域的 Image 对象以后,就可以以某种方式处理该区域并将其粘贴回去。

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

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

滚动图像

例如,我们有如下一张名称为 sexy.jpg 的图片:

在这里插入图片描述

可以通过下面的函数将其进行横向滚动:

def sideways_roll(image, delta):
    """Roll an image sideways."""
    xsize, ysize = image.size
    delta = delta % xsize
    if delta == 0: return image
    part1 = image.crop((0, 0, delta, ysize))
    part2 = image.crop((delta, 0, xsize, ysize))
    image.paste(part1, (xsize-delta, 0, xsize, ysize))
    image.paste(part2, (0, 0, xsize-delta, ysize))
    return image

其中 image 参数用于指定一个 Image 对象,delta 参数用于指定滚动的幅度。

下面的代码:

sideways_roll(Image.open('sexy.jpg'), 200).show()

返回的 Image 对象打印出的效果如下:

在这里插入图片描述
类似的,下面的函数用于将图片进行纵向滚动:

def lengthways_roll(image, delta):
    """Roll an image lengthways."""
    width_size, high_size = image.size
    delta = delta % high_size
    if delta == 0:
        return image
    part1 = image.crop((0, 0, width_size, delta))
    part2 = image.crop((0, delta, width_size, high_size))
    image.paste(part1, (0, high_size - delta, width_size, high_size))
    image.paste(part2, (0, 0, width_size, high_size - delta))
    return image

同样是上面的图片,下面的代码:

lengthways_roll(Image.open('sexy.jpg'), 200).show()

返回的 Image 对象打印出的效果如下:

在这里插入图片描述

下面的函数将会把图片先进行横向滚动,滚动的幅度等于图片宽度的一半,再进行纵向滚动,滚动的图片等于图片高度的一半:

def roll(image):
    img = Image.open(image)
    width, high = img.size
    width_delta, high_delta = map(lambda x: int(x / 2), (width, high))
    width_left = img.crop((0, 0, width_delta, high))
    width_right = img.crop((width_delta, 0, width, high))
    img.paste(width_left, (width - width_delta, 0, width, high))
    img.paste(width_right, (0, 0, width - width_delta, high))
    high_upper = img.crop((0, 0, width, high_delta))
    high_lower = img.crop((0, high_delta, width, high))
    img.paste(high_upper, (0, high - high_delta, width, high))
    img.paste(high_lower, (0, 0, width, high - high_delta))
    return img

同样是上面的图片,下面的代码:

roll('sexy.jpg').show()

返回的 Image 对象打印出的效果如下:

在这里插入图片描述

对于更高级的技巧,paste() 方法还可以将透明蒙版作为可选参数。在这个掩码中,值 255 表示粘贴的图像在那个位置是不透明的(也就是说,应该按原样使用粘贴的图像)。值 0 表示粘贴的图像是完全透明的。中间值表示透明度的不同级别。例如,粘贴RGBA图像并将其用作蒙版,将粘贴图像的不透明部分,但不粘贴其透明背景。

波段的分割与合并

PIL还允许您处理多波段图像的单个波段,比如RGB图像。split() 方法创建一组新图像,每个图像包含来自原始多波段图像的一个波段。merge() 方法接受一个模式和一个图像元组,并将它们组合成一个新的图像。下面的示例交换了RGB图像的三个波段:

r, g, b = im.split()  # r, b, b 均是一个只包含原始Image对象单个波段的Image对象
im = Image.merge("RGB", (b, g, r))

上面的代码生成的新的 Image 对象打印出来的效果如下:

在这里插入图片描述
注意,对于单波段的图片, split() 方法将返回这个图片本身。要处理单个颜色带,可能需要首先将图像转换为 “RGB”。

几何变换

PIL.Image.Image 类的 resize()rotate() 方法分别用于调整图片大小和旋转图片。

resize() 方法接受一个用于提供图片新的大小的二元组作为参数,返回一个新的 Image 对象。

rotate() 方法接受一个数值,用于指定要旋转的度数。当是正数时,进行逆时针旋转,当是负数时,进行顺时针旋转。

前者采用一个元组来提供新的大小,后者进行逆时针角度的旋转。

简单的几何变换

out = im.resize((128, 128))  # 返回一个宽和高均为128像素的原始图像的副本
out = im.rotate(45)  # 返回一个进行了逆时针45度旋转的原始图像的副本

翻转图片

要将图像旋转90度,可以使用 rotate() 方法或 transpose() 方法。后者也可以用来将图像按照水平轴或垂直轴进行翻转。

out = im.transpose(Image.FLIP_LEFT_RIGHT)
out = im.transpose(Image.FLIP_TOP_BOTTOM)
out = im.transpose(Image.ROTATE_90)
out = im.transpose(Image.ROTATE_180)
out = im.transpose(Image.ROTATE_270)

transpose(ROTATE) 操作也可以与 rotate() 操作执行相同的操作,将 ratate() 方法的 expand 参数设置为 True, 以提供对图像大小的相同更改。

可以通过 transform() 方法执行更一般形式的图像转换。

颜色变换

PIL允许使用 convert() 方法在不同像素表示之间转换图像。

在模式间转换

from PLI import Image
im = Image.open('hopper.ppm').convert('L')

PIL支持在每个受支持的模式与 “L” 和 “RGB” 模式之间进行转换。要在其他模式之间进行转换,您可能必须使用中间图像(通常是 “RGB” 图像)。

图像增强

PIL提供了许多方法和模块,可用于增强图像。

过滤器

ImageFilter 模块包含一系列预定义的可用于 filter() 方法的增强过滤器。

  • ImageFilter.BLUR 模糊化
  • ImageFilter.CONTOUR 轮廓
  • ImageFilter.DETAIL 细节
  • ImageFilter.EDGE_ENHANCE 边缘增强
  • ImageFilter.EDGE_ENHANCE_MORE 更多的边缘增强
  • ImageFilter.EMBOSS 浮雕
  • ImageFilter.FIND_EDGES 寻找边缘
  • ImageFilter.SHARPEN 锐化
  • ImageFilter.SMOOTH 平滑
  • ImageFilter.SMOOTH_MORE 更平滑
from PIL import ImageFilter
out = im.filter(ImageFilter.DETAIL)

像素点操作

point() 方法可用于转换图像的像素值(例如图像对比度处理)。在大多数情况下,该方法接受一个单参数函数,每个像素将按照该函数进行处理:

out = im.point(lambda i: i * 2)

使用上述技术,您可以快速地将任何简单的表达式应用于图像。您还可以结合 point()paste() 方法来选择性地修改图像:

# 将图像分割为独立的波段
source = im.split()
R, G, B = 0, 1, 2
# 选择红色小于100的区域
mask = source[R].point(lambda i: i < 100 and 255)
# 处理绿色波段
out = source[G].point(lambda i: i * 0.7)
# 将处理过的波段粘贴回图像,但只限于红色小于100的像素点
source[G].paste(out, None, mask)
# 构建一个新的多波段图像
im = Image.merge(im.mode, source)

注意用于创建掩码(mask)的语法:

imout = im.point(lambda i: expression and 255)

Python仅评估逻辑表达式的一部分,以确定结果,并返回作为表达式结果检查的最后一个值。因此,如果上面的表达式的值为 False0), Python不会查看第二个操作数,因此返回 0。否则,返回 255

增强

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

你可以用这种方法调整对比度、亮度、色彩平衡和锐度。

from PIL import ImageEnhance
enh = ImageEnhance.Contrast(im)
enh.enhance(1.3).show('30% more contrast')

图像序列

PIL包含对图像序列(也称为动画格式)的一些基本支持。支持的序列格式包括FLI/FLC、GIF和一些实验性质的格式。TIFF文件还可以包含多个帧。

当你打开一个序列文件时,PIL会自动加载序列中的第一帧。你可以使用 seek() 方法和 tell() 方法在不同的帧之间移动。

读取序列

from PIL import Image

im = Image.open("animation.gif")
im.seek(1) # 跳到第二帧

try:
    while 1:
        im.seek(im.tell() + 1)
        # do something to im
except EOFError:
    pass # end of sequence

如上例所示,当迭代到序列末尾时,会引发一个 EOFError 异常。下面的类可以让你使用 for 表达式迭代整个序列:

使用ImageSequence迭代器类

from PIL import ImageSequence
for frame in ImageSequence.Iterator(im):
    # ...do something to frame...

图像打印

PIL包含在Postscript打印机上打印图像、文本和图形的函数。这里有一个简单的例子:

from PIL import Image
from PIL import PSDraw

im = Image.open("hopper.ppm")
title = "hopper"
box = (1*72, 2*72, 7*72, 10*72) # in points

ps = PSDraw.PSDraw() # default is sys.stdout
ps.begin_document(title)

# draw the image (75 dpi)
ps.image(box, im, 75)
ps.rectangle(box)

# draw title
ps.setfont("HelveticaNarrow-Bold", 36)
ps.text((3*72, 4*72), title)

ps.end_document()

图像打印相关的更多知识

如之前所述, Image 模块的 open() 方法用于打开一个图片文件。在大多数情况下,你可以传递一个文件名作为参数:

from PIL import Image
im = Image.open('hopper.ppm')

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

可以使用一个类文件对象代替文件名。这个类文件对象必须实现 read()seek()tell() 方法,并以二进制格式模式打开。

从一个打开的文件读取图像

from PIL import Image
with open("hopper.ppm", "rb") as fp:
    im = Image.open(fp)

从一个字符串读取图像

要从一个字符串数据中读取图像,可以使用 StringIO 类:

import StringIO

im = Image.open(StringIO.StringIO(buffer))

注意,PIL在读取图像头部之前会回滚文件(使用 seek(0))。此外,当读取图像数据时也将使用 seek() 方法(通过 load()方法)。

从tar存档中读取图像

如果图像文件嵌入到更大的文件中,例如tar文件,则可以使用 ContainerIOTarIO 模块访问它:

from PIL import Image, TarIO

fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg")
im = Image.open(fp)

控制解码器

有些解码器允许您在从文件中读取图像时操作图像。当创建缩略图(当速度通常比质量更重要时)和在单色激光打印机上打印(当只需要图像的灰度版本时),这通常可以用来加快解码速度。

draft() 方法操作打开但尚未加载的图像,使其尽可能接近给定的模式和大小。这是通过重新配置图像解码器完成的。

以草稿模式读取图像

这只适用于JPEG和MPO文件。

from PIL import Image

im = Image.open(file)
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)

注意,生成的映像可能与请求的模式和大小不完全匹配。要确保图像不大于给定的大小,请使用 thumbnail() 方法。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐