使用Pillow来进行图像处理
学习如何使用Python的Pillow函式库来处理图像。
介绍
Pillow是Python图像处理函式库(PIL)的一个分支。 PIL是一个函式库,提供了几个操作图像的标准程序。它是一个功能強大的函式库,但自2011年以来就沒有太多的更新,并且不支持Python 3。Pillow在PIL的基础下,为Python 3增加了更多功能和支持。它支持一系列图像文件格式,如PNG,JPEG,PPM ,GIF,TIFF和BMP。我们將看到如何在图像上執行各种操作,例如剪裁,調整大小,添加文本到图像,旋转,灰階转换。
Ps. 我喜欢OpenCV的速度与強大的图像处理功能,但是要在OpenCV中使用我們自己喜欢的字体似乎并不容易。
载入相关函式库
# 把一些警告的讯息暂时关掉
import warnings
warnings.filterwarnings('ignore')
# Utilities相关函式库
import os
# 图像处理/展現的相关函式库
import matplotlib.pyplot as plt
设定相关参数
# 专案的根目录路径
ROOT_DIR = os.getcwd()
# 训练/验证用的资料料目录
DATA_PATH = os.path.join(ROOT_DIR, "data")
# 測試用图像
TEST_IMAGE = os.path.join(DATA_PATH, "man.jpg")
图像物件 Image
Python Imaging Library中的一个关键类别是Image, 它定义在Image模组中。,这个类别的一个实例可以通过几种方式来创建:从图像档案加载图像,从头开始创建图像或者处理其他图像。我们將看到所有這些的使用方式。要从图像档案加载图像,我们使用Image模块中的open()函数將路径传递給图像类別。
from PIL import Image
# 载入图像档
image = Image.open(TEST_IMAGE)
# 儲存图像档并转换格式(jpg -> png)
image.save(os.path.join(DATA_PATH, "new_image.png"))
plt.imshow(image); plt.show()
上面的程式码创建一个加载了图像档案的Image类別的实例,并将其保存到一个新文件new_image.png。Pillow看到文件扩展名已被指定为PNG,所以它将其转换为PNG,然后将其保存到文件。你也可以提供第二個参数save()来直接指定文件格式。
这个image.save('new_image.png','PNG')将和前面的save()一样。通常沒有必要提供第二个参数,因为Pillow将从文件扩展名中決定使用的文件存储格式。
调整图像大小
要调整图像大小,可以调用resize()方法,传递一个两个整数的tuple參數,表示调整大小的图像的宽度和高度。该函数不会修改使用的图像,而是返回具有新維度的另一个图像。
# 载入图像档
image = Image.open(TEST_IMAGE)
# 调整大小
new_image = image.resize((400, 400))
print('原本圖像大小: ', image.size)
print('新的圖像大小: ', new_image.size)
plt.imshow(new_image); plt.show()
resize()方法返回一个图像,其宽度和高度完全匹配传入的值。这可能是你想要的,但有时你可能会发现这个函数返回的图像并不理想。这主要是因为该功能沒有考虑到图像的长宽的比例,所以你最終可能会看到一个图像,看起來被拉长或挤压。你可以从上面的程式码中看到新创建的图像:image_400.jpg。它看起来有点被挤压了。
图像缩图 (thumpnail)
如果要调整图像大小并保持其长宽的比例,则应该使用thumbnail()函数来调整它们的大小。这还需要表示縮略图最大宽度和最大高度的两个整数元组参数。
# 载入图像档
image = Image.open(TEST_IMAGE)
# 产生图像并保持长宽比
image.thumbnail((400, 400))
print('新的縮图大小: ', image.size)
plt.imshow(image); plt.show()
以上将产生一个图像大小为(311,400),它保持原始图像的宽高比(440, 565)。正如你可以看到的,这样的作法会让縮放的結果比较正常。
ps.请注意在PIL的Image.size回传的是 (width, height) 与一般使用OpenCV所回传的 (height, width, channels) 的资讯不太一样喔!
图像裁剪 (cropping)裁切图像時,图像內的矩形区域将被选中并保留,而区域外的所有其他区域都将被移除。使用Pillow库,可以使用Image类的crop()方法裁剪图像。该方法采用一个边界框(bounding box)来定义裁剪区域的位置和大小,并返回一个代表裁剪图像的Image對象。框的坐标是(left, upper, right, lower)或(x1, y1, x2, y2)。
# 载入图像档
image = Image.open(TEST_IMAGE)
# 定义要裁剪的边界框座标
x1 = 200; y1 = 50
x2 = 460; y2 = 320
bbox = (x1, y1, x2, y2)
# 进行裁剪
cropped_image = image.crop(bbox)
plt.imshow(cropped_image); plt.show()
将图像粘贴另一个图像上
Pillow函式库能夠将图像粘贴到另一个图像上。一些使用案例是通过在其图像添加水印來保护公开可用的图像,或是添加公司商标。使用paste()函数完成粘贴的动作。这个动作修改了Image对象,它不像我们目前看到的其他处理函数返回一个新的Image对象。因此,在执行粘贴之前,我們將首先复制我们的原始图像,以便我们可以用未修改的图像继续处理其他处理。
# 载入图像档
image = Image.open(TEST_IMAGE)
image1= Image.open(os.path.join(DATA_PATH, "thump.jpg"))
# 儲存图像档并转换格式(jpg -> png)
image1.save(os.path.join(DATA_PATH, "new_thump.png"))
# 载入浮水印图像档
logo = Image.open(os.path.join(DATA_PATH, "new_thump.png"))
# 修改成合适大小
logo.thumbnail((100, 100))
# 复制图像
image_copy = image.copy()
# 指定要粘贴的左上角座标
position = ((image_copy.width - logo.width), (image_copy.height - logo.height))
# 进行粘貼
image_copy.paste(logo, position)
plt.imshow(image_copy); plt.show()
在上面,我們加载两个图像Hugh_Jackman.jpg和brain.png,然后用copy()复制前者。我们希望将徽标图像粘贴到复制的图像上,我们希望将其放置在右下角,結果如上所示。但是結果跟我们期待的有一些差异, 那个粘贴上來的图像的背景让整个結果一整个low掉。预设的情況下,当你执行粘贴时,透明像素将被粘贴为实心像素,因此徽标周围的黑色(某些操作系統上的白色)框会被粘贴进去。大多数时候,这不是你想要的。你不能让你的水印覆盖底层图像的內容。我们宁愿透明的像素出现。为了达到这个目的,你需要把第三个参数传递給paste()函数。这个参数是透明度掩码/遮罩图像物件对象。蒙版是一个图像对象,其中的alpha值是重要的,但其綠色,紅色和蓝色值将被忽略。如果給出遮罩,則paste()仅更新由掩碼指示的区域。您可以使用1,L或RGBA图像作為遮罩。粘贴RGBA图像并將其用作遮罩将粘贴图像的不透明部分,但不粘贴透明背景。如果您修改粘贴如下所示,您应該有一个透明像素粘贴徽标。
# 载入图像档
image = Image.open(TEST_IMAGE)
# 载入浮水印图像档
logo = Image.open(os.path.join(DATA_PATH, "thumbup.png"))
# 修改成合适大小
logo.thumbnail((100, 100))
# 複製图像
image_copy = image.copy()
# 指定要粘贴的左上角座标
position = ((image_copy.width - logo.width), (image_copy.height - logo.height))
image_copy.paste(logo, position)
plt.imshow(image_copy); plt.show()
图像旋转 (Rotating)
你可以使用rotate()方法逆时针地旋转图像。这需要一个整数或浮点型参数来表示选择图像的角度,并返回旋转图像的新Image对象。
# 载入图像档
image = Image.open(TEST_IMAGE)
# 逆时针的旋转图像90度
image_rot_90 = image.rotate(90)
plt.imshow(image_rot_90); plt.show()
# 逆时针的旋转图像180度
image_rot_180 = image.rotate(180)
plt.imshow(image_rot_180); plt.show()
在上面,我们将两张图像保存到磁盘上:一个以90度旋转,另一个以180度旋转。预设的情況下,旋转的图像会保持原始图像的尺寸。这意味著,除了180的倍数以外的角度,图像将被剪切和/或填充以适应原始尺寸。如果仔细观察上面的第一张图片,您会注意到其中一些已经被剪裁以适合原始图像的高度,并且其边缘已经用黑色背景(某些操作系統上的透明像素)填充以适应原始宽度。
下面的例子更清楚地展示了这一点。
# 载入图像档
image = Image.open(TEST_IMAGE)
# 逆时针的旋转图像18度
image_rot_18 = image.rotate(18)
plt.imshow(image_rot_18); plt.show()
要扩展让旋转图像的尺寸以适应整个图像,可以将第二个参数传递給rotate(),如下所示。
# 載入圖像檔
image = Image.open(TEST_IMAGE)
# 逆时针的旋转图像18度让图像扩大來包含新的图像
image_rot_18_expand = image.rotate(18, expand=True)
plt.imshow(image_rot_18_expand); plt.show()
现在图像的內容将完全可见,并且图像的尺寸将会增加以解決这个问题。
图像翻转 (Flipping)
你也可以翻转图像来获得他们的鏡像版本。这是用transpose()函数完成的。你可以采用以下选项:
PIL.Image.FLIP_LEFT_RIGHT
PIL.Image.FLIP_TOP_BOTTOM
PIL.Image.ROTATE_90
PIL.Image.ROTATE_180
PIL.Image.ROTATE_270
PIL.Image.TRANSPOSE
# 載入圖像檔
image = Image.open(TEST_IMAGE)
# 左右互换
image_flip = image.transpose(Image.FLIP_LEFT_RIGHT)
plt.imshow(image_flip); plt.show()
# 上下互换
image_flip = image.transpose(Image.FLIP_TOP_BOTTOM)
plt.imshow(image_flip); plt.show()
在图像上绘图
使用Pillow函式库,你也可以使用ImageDraw模组来绘制图像。您可以绘制直线,点,橢圓,矩形,弧,二元图,和弦,pieslices,多边形,形状和文本。
from PIL import Image, ImageDraw, ImageFont
# 产生一个有4个顏色channels的空白图像
blank_image = Image.new('RGBA', (400, 300), 'white')
# 在blank_image图像上绘图
img_draw = ImageDraw.Draw(blank_image)
# 画一個矩形
img_draw.rectangle((70, 50, 270, 200), outline='red', fill='blue')
# 取得字型物件
fnt = ImageFont.truetype('comic.ttf', 40) # 修改你電腦上有的字型
# 放上文字訊息到图像上
img_draw.text((70, 250), 'Hello World', font=fnt, fill='green')
plt.imshow(blank_image); plt.show()
在这个例子中,我们用new()方法创建一个Image对象。这将返回一个沒有加载图像的Image对象。然后,我们添加一个矩形和一些文本的图像。
ps. 特別注意的字型的設定, PIL可以支持TrueType和OpenType字體(你必需指定字型的檔案完整目錄, 或是字型檑在作業系統的標準目錄裡!)。
from PIL import Image, ImageDraw, ImageFont
# 产生一个有4个顏色channels的空白图像
blank_image = Image.new('RGBA', (400, 300), 'white')
img_draw = ImageDraw.Draw(blank_image) # 在blank_image图像上绘图
# 在PIL要可以用rectangle來创一个四方形, 但是无法控制框线的粗細
img_draw.rectangle((70, 50, 270, 200), outline= None, fill='pink')
# 透過画线來画一个四方框的框线并控制粗細
img_draw.line([(70,50),(270,50),(270,200),(70,200),(70,50)], fill='red', width=4)
# 在PIL要画一个可以控制大小的图要透過以下的手法
r = 10 # 设定半徑
# 以圖的中心点(x,y)來计算框住圓的边界框座标[(x1,y1),(x2,y2)]
img_draw.ellipse((270-r,200-r, 270+r, 200+r), fill='orange')
# 画一个多边形
img_draw.polygon([(40, 40), (40, 80), (80, 60), (60, 40)], fill='green', outline=None)
plt.imshow(blank_image); plt.show()
顏色变换
Pillow函式库允许你使用convert()方法在不同的像素表示之间转换图像。它支持L(灰度),RGB和CMYK模式之間的转换。在下面的例子中,我们将图像从RGBA转换为L模式,这将彩色图像转换成灰階图像。
# 載入圖像檔
image = Image.open(TEST_IMAGE)
# 將彩色转换成灰階
greyscale_image = image.convert('L')
# 注意要注明cmat='gray'才能夠正确秀出灰階图像
plt.imshow(greyscale_image, cmap='gray')
plt.show()