Pillow

用于图像处理的Python第三方库

Posted by Welt Xing on August 21, 2021

引言

图片从数学上来说,只是一个二维矩阵(黑白图片)或者是一个三维张量,因此存在用程序操作的可能。笔者在这里尝试用Python处理图片,为后续卷积和池化操作做铺垫。

Pillow入门

用 Python 做图片处理,最著名的库就是 PIL(Python Imaging Library)了,不过由于年久失修,一群有志青年在 PIL 的基础上创建了 Pillow,支持最新的 Python3,而且有许多新的特性,Pillow 也成为了 Python 图片处理必不可少的工具之一了。

安装Pillow很简单:

pip install pillow

然后在Python中通过下面的语句读取和展示图片:

from PIL import Image
import numpy as np

im = Image.open("test.jpg")
im.show()

如果我们想读取图片的数字结构,则将其转为numpy数组:

im_array = np.array(im)
print(im_array.shape)

对于下面的png图片:

img

输出数组的形状:

(608, 482, 4)

也就是长608个像素,宽482像素,4通道的图片。png比jpg多出一个Alpha通道,用来表示各个像素点透明度。为了后续处理的方便,我们想将其转换成3通道格式并保存:

im = im.convert('RGB')
im.save('test.jpg')

注:im.convert语句不可缺少,否则会报错,因为通道数不同。

这时再查看其参数:

print(im.shape)

输出(608, 482, 3),也就是三通道(R、G、B)。

图片的简单操作

在知道了图片参数的意义后,我们就可以通过变换ndarray数组来变换图片。比如通过数组截取将图片压缩:

Image.fromarray(im_array[::2, ::2]).show()

然后图片变成了:

compressed

我们也可以通过改变数组的值,以改变图片外观,比如将各个通道的值分别设成255:

array0 = np.array(im_array, copy=True)
array0[:, :, 0] = 255
array1 = np.array(im_array, copy=True)
array1[:, :, 1] = 255
array2 = np.array(im_array, copy=True)
array2[:, :, 2] = 255
# 将三张图片水平拼接,然后压缩一下:
Image.fromarray(np.hstack((array0, array1, array2))[::2, ::2]).show()

然后呈现下面的图片:

rbg

也就是RGB三原色分别拉到最高时的样子。

而如果将上面的255改成0,相当于把图片中的红绿蓝色素分别移出去,呈现的就是青色,紫色和黄色:

rgb2

关于数据格式

由于本文是为后面的卷积神经网络做铺垫,因此不会深入太多关于图像处理的知识点。这里要提到关于数据格式的问题,事实上,jpg图片中,每个像素的每个通道值都是在0~255之间的,因此Pillow和numpy环境下图像数据是用uint8存储:

print(im_array.dtype) # 输出dtype('uint8')

这也导致了一个问题,假如我们这样操作:

new_im_array = im_array / 3
new_im = Image.fromarray(new_im_array)

就会报错,因为此时new_im_array中的元素是float类型,不满足图片格式。我们需要手动转换类型:

new_im = Image.fromarray(new_im_array.astype("uint8"))

但这样处理也不是周全的,比如我们这样操作:

new_im_array = im_array * 2
new_im = Image.fromarray(new_im_array)

想获得更浓郁的色彩,假设此时有个R值为200的像素点,乘以2之后,由于数据类型始终是uint8,因此数据溢出,运算后的值是144,不符合预期,因此最合理的操作应该是:先将数组转换成float类型,进行一系列运算,然后运算后的结果限制在0~255之间,最后再转换成uint8类型:

new_im_array = im_array.astype("float64")

new_im_array += 16
new_im_array /= 4
... # 一系列运算

new_im_array[new_im_array < 0] = 0
new_im_array[new_im_array > 255] = 255

new_im = Image.fromarray(new_im_array.astype("uint8"))