Python图像处理(Pillow/PIL)入门

时间:2023-01-06 22:21:35

Pillow概况

PIL是Python的一种图像处理工具。
PIL支持大部分的图像格式,高效并强大。
核心库设计用来高速访问基于基于像素的数据存储,给这个通用的图像处理工具提供了坚实的基础。

来看下这个库的一般用途:

图像归档

PIL是较为理想的图片归档和批处理应用。你可以使用这个库去生成缩略图、转换图片格式、打印图像等。
当前版本可以识别和读取大量的格式。写操作被限制用于大多数通用的转换处理和显示格式上。

图像展示

当前发行版本包含TkPhotoImageBitmapImage接口,这个和Windows DIB interface一样,可以用于PythonWin和其他基于窗口的程序。许多其他的GUI程序都是基于PIL。
为了调试方便,PIL提供了show(),用来保存图片到硬盘和调用外部图片查看器。

图形处理

PIL包含基本的图像处理功能,包括像素操作、一套内置卷积核的滤镜、色空间转换。
PIL支持图像缩放、旋转、任意角度的转换。
PIL支持用于统计图像的直方图。这个可以用来自动自动对比增强、全局统计分析。

安装

使用PIP安装

pip install Pillow

使用

使用Image类

Image类是PIL最核心的类,定义在同名的模块中。你可以创建这个类实例,可以通过加载图片文件、处理其他Image实例、或新建一个空的Image实例。
为了从图片文件加载Image类,需要使用Image模块中的open()方法。

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

如果加载成功,这个函数会返回一个Image对象。你可以使用实例属性去查看文件内容。

>>> from __future__ import print_function
>>> print(im.format, im.size, im.mode)
PPM (512, 512) RGB

format属性定义了图片来源格式。如果实例没有读取文件,format就是None。size属性是一个两个元素的元组,包含像素计的宽高。mode属性定义了图像通道的编号和名字,以及像素类型和深度。常用模式有用于灰度图的”L”(luminance)、用于真彩图的”RGB”、用于预印刷图的”CMYK”模式。

如果你没有打开文件,会触发一个IOError异常。

一旦你有了一个Image实例,你就可以使用类里的方法来处理和维护图像了。例如,让我们显示图像,我们仅仅加载如下:

>>> im.show()

这个版本的show()不是非常高效,因为他是保存图像到一个临时文件,然后调用xv协助去显示图像。如果你没有安装xv,他将不会工作。尽管他工作了,也会非常难以调试和测试。

下一节介绍PIL提供的几种不用的方法。

读写图像

PIL支持广泛的图片格式。为了从硬盘读取文件,需要使用Image模块中的open()方法。你不必知道文件格式就可以打开文件。PIL会根据文件内容自动判断文件格式。

可以使用Image模块里的save()方法来保存文件。保存文件的时候,命名会很重要。除非你规定了格式,PIL会用文件扩展名来存储。

转换图片为JPEG格式

from __future__ import print_function
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)

第二个参数被应用到save()方法里,准确定义一个文件格式。如果你使用了一个非标准的扩展,你必须通过这种方式来定义格式:

创建JPEG缩略图

from __future__ import print_function
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)

PIL不解码加载光栅数据,除非必须这样。当你打开一个文件,在文件头里就可以判断出文件格式,并抽取出像模式、大小和其他需要解码文件的属性,但是文件的其他部分不会被处理,除非以后需要。

这个意思就是打开图像文件是一个很快速的操作,这个跟文件大小和压缩方式无关。这里举几个简单的例子,快速识别一批图像文件。

识别图像文件

from __future__ import print_function
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)

使用包含4个元素的元组来定义区块,相匹配的坐标是(左上右下)。PIL使用一个坐标系统,原点位于左上角。坐标系统使用像素来引用位置,所以上例中的区块大小是300x300像素。

现在这个区块可以以某种方式处理和粘贴了。

处理子矩形并粘贴

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

当粘贴回的时候,区块大小必须精确匹配已给的区块大小。另外,这个区块不能扩展到外部图像。但是,原始图像的模式必须和区块的模式匹配。如果他们比匹配,区块会提前动转换为匹配的模式。

附件例子:

旋转图像

def 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))
part1.load()
part2.load()
image.paste(part2, (0, 0, xsize-delta, ysize))
image.paste(part1, (xsize-delta, 0, xsize, ysize))

return image

当从crop()粘贴回的时候,要先调用load()。这是因为剪切是一个惰性操作。如果load()没有被调用,剪切操作就不会执行,直到有粘贴命令执行。这就意味着part1会被首先修改的image剪切。

对于更高级的技巧,粘贴命令可以将透明遮罩作为可选参数。在这个遮罩里面,255表明在这个位置上粘贴的图像是不透明的。0表明粘贴的图像是完全透明的。在这两个值之间的数值表明不同级别的透明度。例如,粘贴一张RGBA图像,并使用它作为遮罩,将会粘贴图像不透明的部分,而不是他的透明背景。

PIL也可以让你处理单个或多个通道图像,例如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.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()的功能,如果expand标签为真,则图像尺寸有相同的更改。

更详细的图像转换,可以查阅transform()方法说明。

颜色转换

PIL可以使用convert()方法在不同的像素表示之间转换。

转换图像模式

im = Image.open("hopper.ppm").convert("L")

该库支持每个受支持的模式、“L”和“RGB”模式之间的转换。
要在其他模式之间转换,您可能需要使用中间图像(通常是“RGB”图像)。

图象增强

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

滤镜

ImageFilter模块包含一些预定义的增强滤镜,可以与filter()方法一起使用。

应用滤镜

from PIL import ImageFilter
out = im.filter(ImageFilter.DETAIL)

点运算

point()方法可以用来转换图像的像素值(例如图像对比操作)。在大多数情况下,期望一个参数的函数对象可以传递给这个方法。根据这个函数处理每个像素:

应用点转换

# multiply each pixel by 1.2
out = im.point(lambda i: i * 1.2)

使用上面的技术,您可以快速地将任何简单的表达式应用于图像。您还可以组合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")

图像序列

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

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

读取学列

from PIL import Image

im = Image.open("animation.gif")
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异常。

注意,当前版本库中的大多数驱动程序只允许您寻找下一帧(如上面示例中所示)。要重新修改文件,您可能需要重新打开它。

下面的类让您使用for语句来对序列进行循环:

使用ImageSequence迭代器类

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

Postscript打印

PIL包括打印图像、文字和在Postscript打印机上绘图的功能。这是一个简单的例子:

画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()函数用于打开图像文件。在大多数情况下,您只需将文件名作为参数传过去:

im = Image.open("hopper.ppm")

如果一切顺利,结果就是一个PIL.Image.Image对象。否则,将抛出IOError异常。

您可以使用类似文件的对象而不是文件名。对象必须实现read()seek()tell()方法,并以二进制模式打开。

从一个打开的文件读取

fp = open("hopper.ppm", "rb")
im = Image.open(fp)

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

从字符串读取

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

请注意,在读取图像头之前,库会重新调整文件(使用seek(0))。此外,还可以在读取图像数据时使用seek(通过load方法)。
如果图像文件嵌入到较大的文件中,如tar文件,则可以使用ContainerIOTarIO模块来访问它。

从tar压缩包里读取

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
from __future__ import print_function
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)

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

举例

微信验证码地址
https://mp.weixin.qq.com/mp/verifycode?cert=1494918122899.514

im=Image.open('dog.jpg')  
draw=ImageDraw.Draw(im)
newfont=ImageFont.truetype('simkai.ttf',40) #设置字体,simkai为楷体,字体大小40,truetype相关知识可百度
draw.text((200,100),'you are so good!',(255,255,0),font=newfont) #第一个tuple表示要写在哪里,(left,up),之后写的#文字,颜色为黄色,三通道设置可以百度,第<span style="white-space:pre"> </span> #一个是红色,第二个绿色,第三个是蓝色,从0到255,最后设置字体
im.show()
im.save('target.jpg')

参考

译文