天天看点

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

##Pillow概况

PIL是Python的一种图像处理工具。

PIL支持大部分的图像格式,高效并强大。

核心库设计用来高速访问基于基于像素的数据存储,给这个通用的图像处理工具提供了坚实的基础。

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

###图像归档

PIL是较为理想的图片归档和批处理应用。你可以使用这个库去生成缩略图、转换图片格式、打印图像等。

当前版本可以识别和读取大量的格式。写操作被限制用于大多数通用的转换处理和显示格式上。

###图像展示

当前发行版本包含Tk​​

​PhotoImage​

​​和​

​BitmapImage​

​​接口,这个和​

​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()​

​方法在不同的像素表示之间转换。

转换图像模式

Python图像处理(Pillow/PIL)入门
im = Image.open("hopper.ppm").convert("L")      

该库支持每个受支持的模式、“L”和“RGB”模式之间的转换。

要在其他模式之间转换,您可能需要使用中间图像(通常是“RGB”图像)。

# 将不同模式的图片打印出shape 和 [0, 0]像素点的值
from PIL import Image
import matplotlib.pyplot as plt
image = Image.open('images/tower.jpg') # 本地一个文件
mode_list = ['1', 'L', 'I', 'F', 'P', 'RGB', 'RGBA', 'CMYK', 'YCbCr' ]
for mode in mode_list:
    img = image.convert(mode)
    img_data = np.array(img)
    print('img_{:>1}.shape: {}' .format(mode, img_data.shape))
    print('img_{:>}_data[0, 0]: {}'.format(mode, img_data[0, 0]))
    print('---')      

RGB 为真色彩模式, 可组合为 256 x 256 x256 种, 打印需要更改为 CMYK模式, 需要注意数值溢出的问题。

HSB 模式(本篇没有涉及),建立基于人类感觉颜色的方式,将颜色分为色相(Hue),饱和度(Saturation),明亮度(Brightness),这里不详细展开。

CMYK模式,应用在印刷领域,4个字母意思是青、洋红、黄、黑,因为不能保证纯度,所以需要黑。

位图模式,见1, 颜色由黑和白表示(True, False)。

灰度模式,只有灰度, 所有颜色转化为灰度值,见L,I,F。

双色调模式(未有涉及),节约成本将可使用双色调。

Lab模式(未涉及,ps内置),由3通道组成(亮度,a,b)组成,作为RGB到CMYK的过渡。

多通道模式,删除RGB,CMYK,Lab中某一个通道后,会转变为多通道,多通道用于处理特殊打印,它的每个通道都为256级灰度通道。

索引颜色模式,用在多媒体和网页,通过颜色表查取,没有则就近取,仅支持单通道,(8位/像素)。

P模式利用了重映射的思想,事先准备一个调色板,然后图片数据里保存的是调色板的位置,而不是具体的色值,色值都保存在调色板里。

###图象增强

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文件,则可以使用​​

​ContainerIO​

​​或​

​TarIO​

​模块来访问它。

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

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

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