很早之前,Python 有一个图像处理库叫做 Python Imaging Library(简称:PIL),但之后不再维护了,而 Pillow 则接手继续维护,所以,你会发现使用 Pilow 的时候是从 PIL 导入类或函数的。本文是 Pillow 的零基础入门教程,让你轻松学会用 Pillow 处理图像的方法。
pip install Pillow
使用 open() 函数从文件中加载图像。
from PIL import Image
im = Image.open('images/test/hopper.jpg')
im
print(f'format: {im.format}')
print(f'size: {im.size}')
print(f'mode: {im.mode}')
format: JPEG size: (128, 128) mode: RGB
一旦有了 Image 类的实例,你就可以使用该类定义的方法来操作图像。例如,让我们显示刚才加载的图像。
im.show()
show() 方法首先将图像保存到一个临时文件中,然后调用系统默认打开图像的程序来加载这个临时文件。
from PIL import Image
fname = 'images/test/hopper.png'
im_png = Image.open(fname)
im_png.save(f'{fname.split(".")[0]}.jpg')
from PIL import Image
thumbnail_size = (64, 64) # 指定缩略图的大小
fname = 'images/test/hopper.jpg'
im = Image.open(fname)
if thumbnail_size:
im.thumbnail(thumbnail_size)
else:
im.thumbnail((im.width//2, im.height//2))
im
from PIL import Image
fname = 'images/test/hopper.jpg'
im = Image.open(fname)
box = (0, 0, 64, 64)
region = im.crop(box)
region
Pillow 定义图像左上角坐标为 (0, 0),crop() 方法接受一个四元组参数,对应裁剪的坐标 (左, 上, 右, 下)。例如,(0, 0, 64, 64) 表示裁剪矩形左上角的坐标是 (0, 0),右下角的坐标是 (64, 64)。那么,最终裁剪的子图大小就是 (64-0, 64-0)。
region = region.transpose(Image.Transpose.ROTATE_180) # 将图像旋转180°
box = (64, 64, 128, 128) # 指定粘贴覆盖的区域
im.paste(region, box) # 粘贴图像
im
使用 paste() 方法粘贴图像。贴图的大小必须和给定粘贴区域的大小一致,且不能超出给定底图的区域。
from PIL import Image
size = (128, 128)
im_1 = Image.new('RGBA', size, (255, 0, 0, 255))
im_2 = Image.new('RGBA', size, (0, 0, 255, 255))
im_3 = Image.new('RGBA', size, (0, 255, 0, 255))
im_3.paste(im_1, (0, size[1]//3))
im_3.paste(im_2, (0, size[1]//3*2))
im_3
from PIL import Image
fname = 'images/test/hopper.jpg'
im = Image.open(fname)
r, g, b = im.split()
rgb = Image.merge('RGB', (r, g, b))
bgr = Image.merge('RGB', (b, g, r))
# 绘图
import matplotlib.pyplot as plt
im_list = [r, g, b, rgb, bgr]
plt.figure(figsize=(15, 3))
for i, j in enumerate(im_list):
plt.subplot(1, 5, i+1)
plt.title(['r', 'g', 'b', 'rgb', 'bgr'][i])
plt.axis('off')
plt.imshow(j, cmap=plt.cm.gray)
plt.show()
split() 方法用于划分图像波段。对于单波段图像,返回它本身。
from PIL import Image
im = Image.new('RGB', (128, 128), 'cyan')
im_resied = im.resize((64, 64))
im_rotated = im.rotate(45)
# 绘图
import matplotlib.pyplot as plt
im_list = [im, im_resied, im_rotated]
plt.figure(figsize=(9, 3))
for i, j in enumerate(im_list):
plt.subplot(1, 3, i+1)
plt.title(['original', 'resized', 'rotated'][i])
# plt.axis('off') # 为了对比 resize 前后图片的大小,我们这里就不隐藏坐标轴了
plt.imshow(j, cmap=plt.cm.gray)
plt.show()
from PIL import Image
fname = 'images/test/hopper.jpg'
im = Image.open(fname)
im_h_flip = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
im_v_flip = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
# 绘图
import matplotlib.pyplot as plt
im_list = [im, im_h_flip, im_v_flip]
plt.figure(figsize=(9, 3))
for i, j in enumerate(im_list):
plt.subplot(1, 3, i+1)
plt.title(['original', 'flip horizontally', 'flip vertically'][i])
plt.axis('off')
plt.imshow(j, cmap=plt.cm.gray)
plt.show()
使用 transpose() 方法可以实现图像的水平翻转和垂直翻转。
Pillow 库允许使用 convert() 方法在不同的像素表示之间转换图像。
from PIL import Image
fname = 'images/test/hopper.jpg'
im = Image.open(fname)
im_L = im.convert('L')
# 绘图
import matplotlib.pyplot as plt
im_list = [im, im_L]
plt.figure(figsize=(10, 5))
for i, j in enumerate(im_list):
plt.subplot(1, 2, i+1)
plt.title(['original_RGB', 'converted_Gray'][i])
plt.axis('off')
plt.imshow(j, cmap=plt.cm.gray)
plt.show()
该库支持每个格式与 L
和 RGB
的相互转换,但如何和其他格式进行转换,一般采用 RGB 做中间媒介。
Pillow 库提供了大量的的用于图像增强的方法和模块。
ImageFilter 模块中封装了很多用于图像滤波的类,包括高斯模糊,边缘增强,细节滤波等等。
from PIL import ImageFilter
fname = 'images/test/hopper.jpg'
im = Image.open(fname)
im_out = im.filter(ImageFilter.DETAIL) # 细节滤波,使图像中的细节更加明显
# 绘图
import matplotlib.pyplot as plt
im_list = [im, im_out]
plt.figure(figsize=(10, 5))
for i, j in enumerate(im_list):
plt.subplot(1, 2, i+1)
plt.title(['original', 'after detail filtering'][i])
plt.axis('off')
plt.imshow(j)
plt.show()
point() 方法用于批量转换图像像素点。该方法接受一个 lambda 函数作为参数,图像中所有像素点都根据该函数进行处理。例如,下方这个例子就是通过 point() 方法对图像的对比度进行增强。
from PIL import Image
fname = 'images/test/hopper.jpg'
im = Image.open(fname)
im_out = im.point(lambda _: _*1.25)
# 绘图
import matplotlib.pyplot as plt
im_list = [im, im_out]
plt.figure(figsize=(10, 5))
for i, j in enumerate(im_list):
plt.subplot(1, 2, i+1)
plt.title(['original', 'after contrast enhanced'][i])
plt.axis('off')
plt.imshow(j)
plt.show()
要想获得更高级的图像增强,你可以使用 ImageEnhance 模块中定义的类。通过那些类,你可以快速地对图像的亮度、对比度、色彩平衡以及锐化程度进行调节。
from PIL import Image, ImageEnhance
fname = 'images/test/hopper.jpg'
im = Image.open(fname)
enh = ImageEnhance.Brightness(im).enhance(2)
# 绘图
import matplotlib.pyplot as plt
im_list = [im, enh]
plt.figure(figsize=(10, 5))
for i, j in enumerate(im_list):
plt.subplot(1, 2, i+1)
plt.title(['original', '100% more brightness'][i])
plt.axis('off')
plt.imshow(j)
plt.show()
Pillow 库也包含对图像序列(动画格式)的一些基本支持。支持的序列格式文件包括:GIF、TIFF和FLI等等。
from PIL import Image
fname = 'images/test/digit.gif'
im = Image.open(fname)
im_list = [im.copy()]
try:
while True:
im.seek(im.tell()+1)
im_list.append(im.copy())
except EOFError:
pass
# 绘图
import matplotlib.pyplot as plt
plt.figure(figsize=(9, 3))
for i, j in enumerate(im_list):
plt.subplot(1, 3, i+1)
plt.title(str(i+1))
plt.axis('off')
plt.imshow(j)
plt.show()
除使用 seek() 方法读取图像序列外,ImageSequence 模块还提供了 for 循环迭代遍历图像序列的方法。
from PIL import Image, ImageSequence
fname = 'images/test/digit.gif'
im = Image.open(fname)
im_list = []
for frame in ImageSequence.Iterator(im):
im_list.append(frame.copy())
# 绘图
import matplotlib.pyplot as plt
plt.figure(figsize=(9, 3))
for i, j in enumerate(im_list):
plt.subplot(1, 3, i+1)
plt.title(str(i+1))
plt.axis('off')
plt.imshow(j)
plt.show()
此外,你也可以通过 Image 模块定义的属性 n_frames 来控制 for 循环的次数。
from PIL import Image
fname = 'images/test/digit.gif'
im = Image.open(fname)
im_list = []
for i in range(im.n_frames):
im.seek(i)
im_list.append(im.copy())
# 绘图
import matplotlib.pyplot as plt
plt.figure(figsize=(9, 3))
for i, j in enumerate(im_list):
plt.subplot(1, 3, i+1)
plt.title(str(i+1))
plt.axis('off')
plt.imshow(j)
plt.show()
from PIL import Image, PSDraw
with Image.open("images/test/hopper.jpg") as im:
with open('images/test/hopper.ps', 'wb') as fp:
# 创建文档
ps = PSDraw.PSDraw(fp)
# 开始文档
ps.begin_document("hopper")
# 插入图片
ps.image(box, im, 75)
ps.rectangle((1 * 72, 2 * 72, 7 * 72, 10 * 72))
# 插入标题
ps.setfont("HelveticaNarrow-Bold", 36)
ps.text((3 * 72, 4 * 72), "hopper")
# 结束文档
ps.end_document()
我们通常使用 Image.open(filename) 的方式来打开一张图片。其实,Image.open() 还可以作为上下文管理器。如果一切顺利,返回一个 PIL.Image.Image 对象,否则抛出 OSError 异常。
from PIL import Image
with Image.open('images/test/hopper.jpg') as im:
im.show()
from PIL import Image
with open('images/test/hopper.jpg', 'rb') as fp:
im = Image.open(fp)
im.show()
from PIL import Image, TarIO
fp = TarIO.TarIO('images/test/hopper.tar', "hopper.jpg")
im = Image.open(fp)
im
例如,将所有 PNG 格式的图片都转换成低质量的 JPEG 格式保存在工作目录中。
import glob
from PIL import Image
def compress_image(source_path, dest_path):
with Image.open(source_path) as img:
if img.mode != "RGB":
img = img.convert("RGB")
img.save(dest_path, "JPEG", optimize=True, quality=80)
if __name__=='__main__':
for path in glob.glob("*.png"):
compress_image(path, path[:-4] + ".jpg")
draft() 方法允许操作一个打开但尚未加载的图像,使其尽可能与给定模式和大小匹配,这是通过重新配置图像解码器来实现的。但仅适用于 JPEG 和 MPO 文件。
from PIL import Image
fname = 'images/test/hopper.jpg'
with Image.open(fname) as im:
print("original =", im.mode, im.size)
im.draft("L", (64, 64))
print("draft =", im.mode, im.size)
im.show()
original = RGB (128, 128) draft = L (64, 64)
一幅图像可能由一个或多个波段组成,只要它们具有相同的尺寸和深度。例如,PNG 图像可能有 R、G、B 和 A 四个波段,分别表示红色、绿色、蓝色和 alpha 透明值。
from PIL import Image
im = Image.open('images/test/hopper.jpg')
print(im.getbands())
print(list(im.getdata(0))[:10]) # 数据太多,这里只展示前10个像素点的值
r, g, b = im.split()
r = im.getchannel('R')
r
('R', 'G', 'B') [24, 18, 16, 22, 25, 23, 19, 17, 28, 29]
band
参数表示波段的索引,默认是 None
,返回所有波段。channel
参数可接受一个整型的索引值,或大写的通道名称。from PIL import Image
im = Image.open('images/test/hopper.jpg')
print(im.mode)
r = im.getchannel('R')
print(r.mode)
RGB L
你可以通过 size 属性读取图像大小。这是一个二元组,包含以像素位单位的水平和垂直大小,即图像的宽和高。
from PIL import Image
im = Image.open('images/test/hopper.jpg')
im.size
(128, 128)
Pillow 使用笛卡尔坐标系,左上角坐标为 (0, 0)。坐标通常作为2元组传递给类或函数。矩形表示为4元组,前两个元素是左上角点的坐标,后两个元素代表右下角点的坐标。
调色板模式(p)使用调色板为每个像素定义实际颜色。
对于可能将多个输入像素映射到单个输出像素的几何操作,Pillow 提供了多种不同的重采样滤波器。