Python3 Pillow 库图像（表情包）处理教程

Pillow 简介

Pillow 是 Python 图像处理的基础库，它是一个免费开源的第三方库，由一群 Python 社区志愿者使用 Python 语言开发而成（主要贡献者：Alex Clark）。
Pillow 的前身是 PIL 库。不过 PIL 仅支持 Python2 版本，后由 Python 社区志愿者移植到 Python3 版本，改名为 Pillow。Pillow 与 PIL 不能共存于同一环境中。

基础知识

这部分需要一定的 Python 基础知识，以及对于颜色模式如 RGB 等的了解。

Pillow 安装与导入

安装命令：pip install pillow
导入命令：from PIL import Image

Image 类

Image 类是 Pillow 中最重要的类，使用 Image 类可以实例化 Image 对象。

Image 对象的创建

在创建 Image 对象时，既可以创建新的图片，也可以选择打开已有文件。

使用 new () 方法可以创建新的对象，语法格式如下：

Copy
img = Image.new(mode, size, color)

参数说明如下：

mode：图像模式，字符串参数，比如 RGB（真彩图像）、RGBA（真彩透明图像）、L（灰度图像）、CMYK（色彩图打印模式）等；

size：图像大小，元组参数 (width, height) ，代表图像的像素大小；

color：图片颜色，默认值为 0 表示黑色，参数值支持 (R,G,B) 三元组数字格式、颜色的十六进制值（如 '#66CCFF'）以及颜色英文单词。注意：当选择不同颜色模式时，参数的形式也有差别。

使用 open () 方法可以从已有文件创建新的对象，语法格式如下：

Copy
img = Image.open(fp, mode='r', format=None)

参数说明如下：

fp：文件路径，字符串参数；

mode：可选参数，默认设置为 'r'，否则会出现异常。

format：文件格式，默认为 None，表示按图片默认格式打开。

Image 对象的属性

Image 对象常用属性如下：

size：存储图像大小的二元组。

width：宽。

height：高。

format：文件格式。用 new () 方法生成的 Image 对象的 format 为 None。

mode：图像的色彩模式。

readonly：表示图像是否为只读，返回 0 或 1。

info：查看图像相关信息，字典类型。

Image 对象的操作函数

save()

save () 方法用于保存图像，当不指定文件格式时，它会以默认的图片格式来存储；如果指定图片格式，则会以指定的格式存储图片。save () 的语法格式如下：

Copy
img.save(fp, format=None)

参数说明如下：

fp：文件路径；

format：文件格式。

convert()

有些情况下 save () 方法无法直接保存，例如 RGBA 颜色格式的图片无法保存为 JPG。而 convert () 方法可以转换图片的颜色格式。其语法格式如下：

Copy
img1 = img.convert('RGB')

resize () 与 thumbnail ()

实际使用中经常遇到需要调整图像大小的情况，这时就要用 resize () 方法，该方法返回一个新的对象。其语法如下：

Copy
img1 = img.resize(size, resample=image.BICUBIC, box=None, reducing_gap=None)

参数说明：

size：元组参数 (width, height)，图片缩放后的尺寸；

resample：可选参数，指图像重采样滤波器，与 thumbnail () 的 resample 参数类似，默认为 Image.BICUBIC；

box：对指定图片区域进行缩放，box 的参数值是长度为 4 的像素坐标元组，即 (左，上，右，下)。注意，被指定的区域必须在原图的范围内，如果超出范围就会报错。当不传该参数时，默认对整个原图进行缩放；

reducing_gap：可选参数，浮点参数值，用于优化图片的缩放效果，常用参数值有 3.0 和 5.0。

当需要创建缩略图时，往往使用 thumbnail () 方法，该方法直接修改原对象。其语法如下：

Copy
img.thumbnail(size, resample)

参数与 resize () 方法含义相同。

split () 与 merge ()

split () 与 merge () 方法分别起到分离与合并颜色通道的作用。用法如下：

Copy
r, g, b = img.split() # r,g,b 是各颜色通道的黑白图像
img1 = Image.merge('RGB', [b,g,r]) # 合并

注意各颜色通道图片需要大小相同，否则不能合并。

crop ()，copy () 与 paste ()

crop () 的作用是以矩形区域的方式对原图像进行裁剪。其用法如下：

Copy
img_crop = img.crop(box);

box：表示裁剪区域，默认为 None，表示拷贝原图像。
注意：box 是一个四元组，各参数分别表示被裁剪矩形区域的左上角 x、y 坐标和右下角 x，y 坐标。默认 (0, 0) 表示坐标原点，宽度的方向为 x 轴，高度的方向为 y 轴，每个像素点代表一个单位。

copy () 与 paste () 的作用是复制粘贴。copy () 方法较简单，下面主要介绍 paste () 方法：

Copy
large_image.paste(image, box=None, mask=None)

该函数的作用是将一张图片粘贴至另一张图片中。注意，粘贴后的图片模式将自动保持一致，不需要进行额外的转换。参数说明如下：

image：指被粘贴的图片；

box：指定图片被粘贴的位置或者区域，其参数值是长度为 2 或者 4 的元组序列，长度为 2 时，表示具体的某一点 (x, y)；长度为 4 则表示图片粘贴的区域，此时区域的大小必须要和被粘贴的图像大小保持一致。

mask：可选参数，为图片添加蒙版效果。

GIF 图像

GIF 图像的特殊性在于它是一种动态图像，其中往往包含较多的帧。因此它相比于 JPG，PNG 等格式有一些特殊方法。

save () 的参数

GIF 图像在使用 save () 保存时有更多的可用参数。部分参数如下：

save_all：保存全部帧，一般设为 True

append_images：在第一帧后接着的帧

duration：两帧的时间间隔，单位 ms

transparency：透明度，设置为 0 为透明

loop：循环次数，为 0 时永久循环

disposal：是否处理，即播放下一帧时是否清除上一帧；设置为 2 为处理

用例：

Copy
# 这里的 frame_list 存储了所有的帧信息
frame_list[0].save('example.gif', format='gif', save_all=True, append_images=frame_list[1:], duration=50, transparency=0, loop=0, disposal=2)

tell () 与 seek ()

访问 GIF 图像时在特定时刻只能访问一帧。使用 tell () 方法可以查看当前的帧数；使用 seek () 方法可以跳转到对应的帧。不过如果访问到不存在的帧，会产生 EOFError。常见用法如下：

Copy
try:
while True:
# 一些处理操作
img.seek(img.tell() + 1) # 下一帧
except EOFError:
pass

该用法可以遍历 GIF 图像的每一帧。

使用示例

我们以表情包 “滑稽”
为例来展示 Pillow 库的一些使用方式。
注：示例程序不一定是最优解。

表情包拼接

表情包拼接的大致思路是创建适合大小的背景，再将表情包粘贴到合适位置。代码如下：

Copy
# 本文件与 huaji.gif 在同一目录

from PIL import Image

img = Image.open('huaji.gif')
frame_list = [] # 存储最终 GIF 文件的帧
multiple = 9 # 放大倍数

# 遍历 gif 所有帧，当遍历到最后一帧之后就会抛出 EOFError, 使程序结束
try:
while True:
frame = img.convert('RGBA') # 转换为 RGBA 颜色格式
frame_large = Image.new('RGBA', (frame.width * multiple, frame.height * multiple),
'#00000000') # 创建边长为原先 multiple 倍的背景图
for i in range(multiple):
for j in range(multiple):
frame_large.paste(frame, (frame.width * i, frame.height * j)) # 在[i, j] 位置粘贴背景图

frame_list.append(frame_large) # 将当前帧加入帧列表
img.seek(img.tell() + 1) # 遍历下一帧
except EOFError: # 已读到最后一帧之后
pass

# save_all: 保存全部帧
# append_images: 接下来的图片
# duration: 两帧的时间间隔，单位 ms
# transparency: 透明
# loop: 循环次数，为 0 时永久循环
# disposal: 处理，即播放下一帧时清除上一帧

frame_list[0].save('huaji_enlarged.gif', format='gif', save_all=True,
append_images=frame_list[1:], duration=50, transparency=0, loop=0, disposal=2)

效果展示如下图：

表情包颜色修改

本程序中，表情包颜色修改的方式为对每个像素的颜色进行修改。代码如下：

Copy
# 本文件与 huaji.gif 在同一目录

from PIL import Image

# 将原颜色乘以目标颜色并返回
# RGBA 颜色以四元组 (r,g,b,a) 表示，r,g,b,a 均要混合
def to_color(color, target) -> tuple:
'Change to target color.'

temp = []
for i in range(len(color)):
temp.append(color[i] * target[i] // 255) # 混合颜色方式为乘以目标颜色后除以其最大值
return tuple(temp)

# 打开图片
img = Image.open('huaji.gif')
frame_list = []
green = (0, 255, 0, 255)

# 遍历所有帧并染色
try:
while True:
frame = img.convert('RGBA')
pixels = frame.load() # 原图片的各像素，将其修改后原图像也会修改
for i in range(frame.width):
for j in range(frame.height): # 修改单个像素颜色
pixels[i, j] = to_color(pixels[i, j], green)
frame_list.append(frame)
img.seek(img.tell() + 1) # 下一帧
except EOFError: # 已全部遍历
pass

# 保存图片
frame_list[0].save('huaji_green.gif', format='gif', save_all=True,
append_images=frame_list[1:], duration=50, transparency=0, loop=0, disposal=2)

效果展示：

表情包像素画

将 “表情包拼接” 与 “表情包颜色修改” 结合起来，就可以得到 “表情包像素画” 功能。表情包像素画的效果就是将图片的每个像素都按照位置替换为相应颜色的一个表情包。
出于图片文件大小及展示效果的考虑，基底图片的大小定为 40x40，替换用的表情包大小定为 20x20。代码如下：

Copy
# 本文件与 huaji.gif 在同一目录

from PIL import Image

# 将原颜色乘以目标颜色并返回
# RGBA 颜色以四元组 (r,g,b,a) 表示，r,g,b,a 均要混合
def to_color(color, target) -> tuple:
'Change to target color.'

temp = []
for i in range(len(color)):
temp.append(color[i] * target[i] // 255) # 混合颜色方式为乘以目标颜色后除以其最大值
return tuple(temp)

# 转换单个形状的颜色
def shape_to_color(original_image, color):
image = original_image.convert('RGBA') # 这里需要 convert 来复制新对象，
# 否则修改的对象是 original_image
shape_pixels = image.load()
for i in range(image.width):
for j in range(image.height):
shape_pixels[i, j] = to_color(shape_pixels[i, j], color)
return image

base_size = (40, 40)
shape_size = (20, 20)

img_base = Image.open('huaji.gif') # 基底图片
img_shape = Image.open('huaji.gif') # 用于替换的形状图片

shape_list = [] # 存储替换形状的各帧
frame_list = [] # 存储最终输出表情的各帧

# 获取替换形状各帧
try:
while True:
shape = img_shape.convert('RGBA').resize(shape_size)
shape_list.append(shape)
img_shape.seek(img_shape.tell() + 1)
except EOFError:
pass

# 生成像素画各帧
try:
while True:
frame = img_base.convert('RGBA').resize(base_size)
shape = shape_list[len(frame_list) % len(shape_list)] # 按循环顺序获取 shape 的一帧
frame_large = Image.new('RGBA', (frame.width * shape.width, frame.height * shape.height),
'#00000000')
pixels = frame.load()
for i in range(frame.width):
for j in range(frame.height):
new_shape = shape_to_color(shape, pixels[i, j]) # 转换单个形状颜色
frame_large.paste(new_shape, (i * new_shape.width, j * new_shape.width))
frame_list.append(frame_large) # 将当前帧加入帧列表
img_base.seek(img_base.tell() + 1) # 遍历下一帧
except EOFError:
pass

# 保存图片
frame_list[0].save('huaji_pixels.gif', format='gif', save_all=True,
append_images=frame_list[1:], duration=50, transparency=0, loop=0, disposal=2)

效果展示如下图：

参考资料

教程

Pillow（PIL）入门教程（非常详细）
Python 图像处理 Pillow 库 基础篇

文档

stable 版本：Pillow stable 版本
latest 版本：Pillow latest 版本