使用Pillow处理图像

使用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.png

  上面的程式码创建一个加载了图像档案的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()

image.png

resize()方法返回一个图像,其宽度和高度完全匹配传入的值。这可能是你想要的,但有时你可能会发现这个函数返回的图像并不理想。这主要是因为该功能沒有考虑到图像的长宽的比例,所以你最終可能会看到一个图像,看起來被拉长或挤压。你可以从上面的程式码中看到新创建的图像:image_400.jpg。它看起来有点被挤压了。
图像缩图 (thumpnail)
如果要调整图像大小并保持其长宽的比例,则应该使用thumbnail()函数来调整它们的大小。这还需要表示縮略图最大宽度和最大高度的两个整数元组参数。

# 载入图像档
image = Image.open(TEST_IMAGE)

# 产生图像并保持长宽比
image.thumbnail((400, 400))

print('新的縮图大小: ', image.size)

plt.imshow(image); plt.show()

image.png

  以上将产生一个图像大小为(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()

image.png

将图像粘贴另一个图像上
  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()

image.png

在上面,我們加载两个图像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()

image.png

图像旋转 (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()
image.png

  在上面,我们将两张图像保存到磁盘上:一个以90度旋转,另一个以180度旋转。预设的情況下,旋转的图像会保持原始图像的尺寸。这意味著,除了180的倍数以外的角度,图像将被剪切和/或填充以适应原始尺寸。如果仔细观察上面的第一张图片,您会注意到其中一些已经被剪裁以适合原始图像的高度,并且其边缘已经用黑色背景(某些操作系統上的透明像素)填充以适应原始宽度。
下面的例子更清楚地展示了这一点。

# 载入图像档
image = Image.open(TEST_IMAGE)

# 逆时针的旋转图像18度
image_rot_18 = image.rotate(18)

plt.imshow(image_rot_18); plt.show()
image.png

要扩展让旋转图像的尺寸以适应整个图像,可以将第二个参数传递給rotate(),如下所示。

# 載入圖像檔
image = Image.open(TEST_IMAGE)

# 逆时针的旋转图像18度让图像扩大來包含新的图像
image_rot_18_expand = image.rotate(18, expand=True)

plt.imshow(image_rot_18_expand); plt.show()

image.png

现在图像的內容将完全可见,并且图像的尺寸将会增加以解決这个问题。
图像翻转 (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()

image.png

在图像上绘图
使用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()

image.png

  在这个例子中,我们用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()

image.png

顏色变换
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()
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • 一、 安装 参考文档:https://pillow.readthedocs.io/en/latest/instal...
    梦捷者阅读 1,052评论 0 2
  • 本文地址:https://www.jianshu.com/p/3740dec1f436 1. 简介 Python传...
    王南北丶阅读 3,500评论 4 8
  • 简介 PIL (Python Imaging Library) Python图像处理库,该库支持多种文件格式,提供...
    JackHCC阅读 3,799评论 0 0
  • 往事如烟?往事难以如烟。 许多往事历历在目,犹在眼前。 能记起来的,那地方,那环境,那人,那故事,那段情,仍如许的...
    雁韧阅读 363评论 3 16
  • 本周完成工作 完成画面点表的指导 下周工作 部门人员 报销 社保等规定的落实 部门规划和业务情况落实 学习。本周电...
    何思言阅读 281评论 1 0