Pillow综述
PIL(Python Imaging Library)为您的Python解释器添加了图像处理功能。
这个库提供了广泛的文件格式支持、高效的内部表示和相当强大的图像处理功能。
核心图像库是为快速访问以几种基本像素格式存储的数据而设计的。为通用图像处理工具提供了坚实的基础。
让我们看看这个库的一些可能用途。
图像存档(Image Archives)
PIL是理想的图像存档和批处理应用程序。您可以使用库创建缩略图,在文件格式、打印图像等之间进行转换。
当前版本标识和读取大量格式。写支持被有意限制为最常用的交换和表示格式。
图像显示(Image Display)
当前版本包含TK的 PhotoImage
和 BitmapImage
接口,以及一个可以与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仅评估逻辑表达式的一部分,以确定结果,并返回作为表达式结果检查的最后一个值。因此,如果上面的表达式的值为 False (0
), 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文件,则可以使用 ContainerIO
或 TarIO
模块访问它:
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()
方法。