前言

拖更一时爽,一直拖更一直爽。

不过继续拖下去,下半年就有的痛苦了,目前已经欠了五篇,一个多月的工作量,想想都令人头大。

书回正传,今天更新的是Python常用模块系列,主角是用于图像处理的第三方库——PIL。

 

PIL库的介绍

PIL(Python Image Library)是python的第三方图像处理库,但是由于其强大的功能与众多的使用人数,几乎已经被认为是python官方图像处理库了。

PIL历史悠久,但已经是一个失效的库了,作为替代的是pillow,pillow号称是friendly fork for PIL,在PIL库基础上进行了扩展,完全兼容PIL的用法,功能更加强大。

PIL对图像的处理主要分为以下几类:

  • 图像归档(Image Archives)。PIL非常适合于图像归档以及图像的批处理任务,可以使用Pillow创建缩略图,转换图像格式,打印图像等等。
  • 图像展示(Image Display)。PIL较新的版本支持包括Tk PhotoImage,BitmapImage还有Windows DIB等接口。PIL支持众多的GUI框架接口,可以用于图像展示。
  • 图像处理(Image Processing)。PIL包括了基础的图像处理函数,包括对点的处理,使用众多的卷积核(convolution kernels)做过滤(filter),还有颜色空间的转换。PIL库同样支持图像的大小转换,图像旋转,以及任意的仿射变换。
  • Pillow还有一些直方图的方法,可以用于展示图像的一些统计特性,可以用来实现图像的自动对比度增强,以及全局的统计分析等。

 

PIL库的安装

PIL库的安装非常简单,直接使用pip即可。

pip install Pillow

 

PIL库的使用

导入

为了兼容PIL库,因此在导入时不能写作import Pillow,而应该是以下方式。

import PIL

更推荐的是以下导入方式。

from PIL import Image

后面的使用案例都使用这种方式导入Pillow库,导入的部分就不再重复了。

 

读取图像

读取图像很简单,调用Image的open方法即可,得到的是一个Image对象。

img = Image.open("test.jpg","r")

 

预览图像

通过Image对象的show方法,就可以很方便地预览图像。

img = Image.open("test.jpg","r")
img.show()

show方法可以自动将Image对象的当前图像状态保存在临时目录下,并调用系统默认的图片查看软件进行查看。

 

图像保存(转换格式)

通过Image对象的save方法,可以很方便地将Image对象保存成图片文件,并且可以实现简单的图像格式转换。

img = Image.open("test.jpg","r")
img.save("2.png", "png")

save函数的第一个参数是保存的文件路径,第二个参数则是文件的格式,如果省略的话会根据文件的扩展名识别。

 

获取图像属性

通过Image对象,可以很方便地获取图像的属性,包括分辨率、格式等等。

img = Image.open("1.jpg", "r")

# 分辨率
print(img.width)   # 1280
print(img.height)  # 720

# 图像大小
print(img.size)    # (1280, 720)

# 图像格式
print(img.format)  # JPEG

# 图像模式
print(img.mode)    # RGB

# 图像信息
print(img.info)    #不同格式的图像信息内容不同,例如PNG图像的信息中有Gamma值

其中mode属性代表图像的格式,例如像素类型和图像深度等等,例如RGB、RGBA、HSV、CMYK、L(灰度图像)等等。

 

截取图像

通过Image对象的crop函数可以截取图像的部分区域

img = Image.open("1.jpg", "r")

# 截取图像部分区域并修改图像尺寸
cropImg = img.crop((100,0,200,100))

crop函数的输入值是一个矩形区域的4个坐标,依次代表左上角x坐标、Y坐标、右下角的x坐标、y坐标,规定图像左上角的坐标为原点(0,0),宽度的方向为x轴,高度的方向为y轴,每一个像素代表一个坐标单位。

返回值也是一个Image对象。

 

修改图像尺寸

通过Image对象的resize函数可以修改图像尺寸。

img = Image.open("1.jpg", "r")

# 修改图像尺寸
resizeImg = img.resize((200, 200))

# 截取图像部分区域并修改图像尺寸
resizePartImg = img.resize((200, 200), box = (0,0,100,100))

resize参数修改图像尺寸时,会按照输入的图像宽度和高度对图像进行拉伸。box参数为要修改尺寸的区域,格式和crop函数的参数一致,默认是整个图像区域,如果设置为一定的图像坐标,就可以实现先截取后拉伸(crop+resize)的效果。

返回值也是一个Image对象。

 

创建缩略图

通过Image对象的thumbnail函数可以创建缩略图。

img = Image.open("1.jpg", "r")

# 创建缩略图
img.thumbnail((50,50),resample=Image.BICUBIC)

# 显示图片
img.show()

thumbnail函数的第一个参数是缩略图的尺寸,注意生成的缩略图的长宽比和原图的长宽比是一致的,不会拉伸图像,如果输入的缩略图尺寸长宽比和原图长宽比不同,那么生成的缩略图,宽度为所设置的缩略图宽度参数,而高度则自动按比例缩放,所设置的缩略图高度参数是无效的。

第二个参数则是生成缩略图的采样方式,有Image.BICUBICImage.LANCZOSImage.BILINEARImage.NEAREST这四种采样方法,默认是Image.BICUBIC方式。

不过等到明年,Pillow10.X版本开始,采样方式会从Image模块调整到Resampling模块中,到时候可以通过PIL.Resampling.BICUBIC方式使用。

另外需要注意thumbnail函数是没有返回值的,是直接在Image对象上操作,也就是说生成的缩略图会覆盖原来的图像信息,效果和resize函数是类似的。

 

图像旋转、翻转

通过Image对象的rotate函数可以实现图像内容的旋转

img = Image.open("1.jpg", "r")
rotateImg = img.rotate(90)

rotate函数的参数为逆时针旋转的角度,要注意的是,rotate函数旋转的只是图像的内容,图像的尺寸不会变化,因此大概率会出现图片内容超出图像边缘或者图像部分区域内没有图像内容的情况,例如一个png格式的图像旋转90度的结果是这样的。

右图为旋转后的结果,显然超出图像区域外的内容会丢失,而没有内容的图像区域会变成透明(因为区域内RGBA4个通道都为0)。

如果要实现图像内容和图像尺寸的同时旋转,可以使用transpose函数实现。

img = Image.open("1.jpg", "r")
rotateImg = img.transpose(Image.ROTATE_90)

得到的效果是这样的

显然这样的效果是比较直观的,但是transpose函数的输入值不是旋转角度值,而是特定的预设,主要有以下几种。

  • Image.FLIP_LEFT_RIGHT,表示将图像左右水平翻转
  • Image.FLIP_TOP_BOTTOM,表示将图像上下垂直翻转
  • Image.ROTATE_90,表示将图像逆时针旋转90°
  • Image.ROTATE_180,表示将图像逆时针旋转180°
  • Image.ROTATE_270,表示将图像逆时针旋转270°
  • Image.TRANSPOSE,表示将图像进行转置(相当于顺时针旋转90°)
  • Image.TRANSVERSE,表示将图像进行转置,再水平翻转

显然transpose更适合用于整个图像进行水平垂直角度的简单旋转和翻转,而rotate更适合用于复杂图像处理时,将部分图像内容进行任意角度的旋转。

 

图像复制粘贴

Image对象的copy函数可以复制Image对象,paste函数则可以用于将一个Image对象粘贴到另一个Image对象上,用于图像拼贴、覆盖等。

img1 = Image.open("1.png", "r")
img2 = Image.open("2.png", "r")
img2.paste(img1,(100,100),None)

首先需要注意paste函数没有返回值,会直接修改当前的Image对象。

第一个参数是要粘贴的Image对象,第二个参数是目标坐标位置,如果坐标为2个元素的元组,那么就代表一个定位点的x、y坐标,要粘贴的Image对象左上角要对齐这个定位点;如果第二个参数坐标为4个元素的元组,那么就代表左上和右下两个定位点的x、y坐标,要粘贴的Image对象如果尺寸不符合两个定位点构成的尺寸,那么会自动拉伸以满足这个尺寸。

 

图像透明叠加

可以通过Image对象的alpha_composite函数实现简单的图像透明叠加

img1 = Image.open("1.png", "r")
img2 = Image.open("2.png", "r")
img3 = Image.alpha_composite(img1, img2)

这种叠加方式有点类似于paste图像粘贴,但使用比较苛刻,要求两个Image对象都为RGBA模式并且分辨率一致。

另一种方式是通过blend函数实现图片的透明混合效果。

img1 = Image.open("1.png", "r")
img2 = Image.open("2.png", "r")
img3 = Image.blend(img1, img2, 0.3)

最后一个参数是alpha值,最后得到的结果是img1 * (1 - alpha) + img2 * (alpha),这种方式要求两个Image对象模式一致并且分辨率一致。

还有一个composite函数,则是通过mask参数,也就是遮罩图片来控制透明混合区域。

 

分离通道

Image对象的split函数可以将图像的颜色通道分离,比如对于RGB图像,可以将其R,G,B三个颜色通道分离。

而merge函数则可以将颜色通道进行合并,组成一个多通道的图像。

img = Image.open("1.jpg", "r")
r, g, b = img.split()

mergeImg = Image.merge("RGB", [g, b, r])

merge函数的第一个参数是合并得到的图像的颜色模式,和前面图像信息的mode属性一致,第二个参数则是用来合并的各个通道,顺序与颜色模式相同,因此上面的例子就是将三个颜色通道互换,结果如下

除了split函数以外,也可以通过getchannel函数单独获取图像中的某一个通道。

操作图像通道的功能在游戏领域很有用,可以用来将3或者4个灰度贴图合并成一个颜色贴图,从而降低贴图数量,压缩存储空间并提高采样效率。

 

改变图像模式

通过Image对象的convert函数可以改变图像的模式(mode)。

img = Image.open("1.jpg", "r")
lImg = img.convert("L")

例如这个例子是将测试图片转换成灰度图,效果如下

 

像素操作

Image对象的load函数可以获取图像的单个像素的数值。

img = Image.open("1.jpg", "r")
pixels = img.load()
print(pixels[10,10])  # (234, 118, 225)
pixels[10,10] = (0, 0, 0)

像上面的例子一样,既可以获取像素的值,也可以修改像素的值。除了load函数以外,也可以通过Image对象的getpixel函数获取每个像素的值。

Image对象的point函数可以对图像的每个像素进行操作。

img = Image.open("1.jpg", "r")
pixelImg = img.point(lambda x: x * 1.5)

上面的代码对point方法传入了一个匿名函数,将图像的每个像素点数值都乘以1.5,效果如下

 

图像滤镜

Image对象的filter函数可以给图像设置滤镜,实现模糊、锐化、查找边缘等等效果。

from PIL import ImageFilter

img = Image.open("1.jpg", "r")
fImg = img.filter(ImageFilter.GaussianBlur(radius=2))

上面的例子是高斯模糊滤镜,效果如下

常用的filter有以下几类

  • ImageFilter.BLUR,简单模糊
  • ImageFilter.SHARPEN,简单锐化
  • ImageFilter.BoxBlur(radius),盒状模糊
  • ImageFilter.GaussianBlur(radius=2),高斯模糊
  • ImageFilter.FIND_EDGES,查找图像过渡边缘
  • ImageFilter.CONTOUR,获取图像轮廓
  • ImageFilter.EMBOSS,图像浮雕效果
  • ImageFilter.MaxFilter,ImageFilter.MinFilter,ImageFilter.MedianFilter,ImageFilter.RankFilter,ImageFilter.ModeFilter..

 

图像调整

PIL的ImageEnhance模块可以对图像进行增强处理,例如改变亮度、对比度等等

from PIL import ImageEnhance

img = Image.open("1.jpg", "r")

brightness = ImageEnhance.Brightness(img)
brightImg = brightness.enhance(1.5)

contrast = ImageEnhance.Contrast(brightImg)
contrastImg = contrast.enhance(0.8)

上面的例子就是将图片亮度增加50%,对比度降低20%,效果如下。

增强方式有以下几种:

  • ImageEnhance.Color,饱和度
  • ImageEnhance.Contrast,对比度
  • ImageEnhance.Brightness,亮度
  • ImageEnhance.Sharpness,锐化度

 

gif动图处理

PIL除了处理静态图像以外,PIL的ImageSequence模块可以处理gif格式的动图,例如通过下面的代码可以将动图的每一帧单独保存成一张静态图片。

from PIL import ImageSequence

gif = Image.open(".gif")
i = 
for frame in ImageSequence.Iterator(gif):
    if frame.mode == 'JPEG':
        frame.save("{}.jpg".format(i))
    else:
        frame.save("{}.png".format(i))
    i += 

 

Image转换

在使用PyQt做图形界面程序时,可以使用PIL的Image模块读取图片,生成Image对象,再将Image对象转化成QImage对象或者QPixmap对象使用,使用也很简单。

img = Image.open("1.jpg", "r")
qimage = img.toqimage()
qpixmap = img.toqpixmap

反过来,将PyQt的QImage对象或者QPixmap对象转成PIL的Image对象也很简单,通过PIL的ImageQt模块即可实现。

from PIL import ImageQt

img = ImageQt.fromqimage(qimage)
img = ImageQt.fromqpixmap(pixmap)

Image对象其他常用的函数:

  • transform,平移图像
  • verify,验证图像文件是否损坏
  • histogram,获取图像的直方图数据
  • entropy,计算图像的熵
  • getbands,获取图像每个通道的名称
  • getextrema,获取图像每个通道的最大值与最小值
  • getbbox,获取图像非零区域的边界框
  • tobytes,将图像转成字节形式
  • frombytes,将字节转成图像形
  • effect_spread,像素随机散布效果

PIL其他常用的模块还有:

  • ImageChops,用于对图像进行混合叠加
  • ImageCms,处理颜色配置
  • ImageDraw,绘制图像
  • ImageGrab,获取屏幕或者剪贴板的图像

具体可以查看官网文档,这里就不赘述了。

 


我害怕我的判断蕴含着某种偏见,
而这种偏见总是以真理的面目出现。

《中国在梁庄》
——梁鸿