- 内容提要
- 作者简介
- 技术评审者简介
- 致谢
- 译者序 会编程的人不一样
- 前言
- 本书的读者对象
- 编码规范
- 什么是编程
- 本书简介
- 下载和安装 Python
- 启动 IDLE
- 如何寻求帮助
- 聪明地提出编程问题
- 小结
- 第一部分 Python 编程基础
- 第1章 Python 基础
- 第2章 控制流
- 第3章 函数
- 第4章 列表
- 第5章 字典和结构化数据
- 第6章 字符串操作
- 第二部分 自动化任务
- 第7章 模式匹配与正则表达式
- 第8章 读写文件
- 第9章 组织文件
- 第10章 调试
- 第11章 从 Web 抓取信息
- 第12章 处理 Excel 电子表格
- 第13章 处理 PDF 和 Word 文档
- 第14章 处理 CSV 文件和 JSON 数据
- 第15章 保持时间、计划任务和启动程序
- 第16章 发送电子邮件和短信
- 第17章 操作图像
- 第18章 用 GUI 自动化控制键盘和鼠标
- 附录A 安装第三方模块
- 附录B 运行程序
- 附录C 习题答案
17.2 用 Pillow 操作图像
既然知道了 Pillow 中颜色和坐标的工作方式,就让我们用 Pillow 来处理图像。图 17-3 中的图像将用于本章中所有交互式环境的例子。你可以从http://nostarch. com/automatestuff/下载它。
图17-3 我的猫Zophie。照片上看起来增加了10磅(对猫来说很多)
将图像文件Zophie.png放在当前工作目录中,你就可以将Zophie的图像加载到Python中,像这样:
>>> from PIL import Image >>> catIm = Image.open('zophie.png')
要加载图像,就从Pillow导入Image模块,并调用Image.open(),传入图像的文件名。然后,可以将加载图像保存在CatIm这样的变量中。Pillow的模块名称是PIL,这保持与老模块Python Imaging Library向后兼容,这就是为什么必须from PIL import Image,而不是from Pillow import Image。由于Pillow的创建者设计Pillow模块的方式,你必须使用from PIL import Image形式的import语句,而不是简单地import PIL。
如果图像文件不在当前工作目录,就调用os.chdir()函数,将工作目录变为包含图像文件的文件夹。
>>> import os >>> os.chdir('C:\\folder_with_image_file')
Image.open()函数的返回值是Image对象数据类型,它是Pillow将图像表示为Python值的方法。可以调用Image.open(),传入文件名字符串,从一个图像文件(任何格式)加载一个Image对象。通过save()方法,对Image对象的所有更改都可以保存到图像文件中(也是任何格式)。所有的旋转、调整大小、裁剪、绘画和其他图像操作,都通过这个Image对象上的方法调用来完成。
为了让本章的例子更简短,我假定你已导入了Pillow的Image模块,并将Zophie的图像保存在变量catIm中。要确保zophie.png文件在当前工作目录中,让Image.open()函数能找到它。否则,必须在Image.open()的字符串参数中指定完整的绝对路径。
17.2.1 处理Image数据类型
Image对象有一些有用的属性,提供了加载的图像文件的基本信息:它的宽度和高度、文件名和图像格式(如JPEG、GIF或PNG)。
例如,在交互式环境中输入以下代码:
>>> from PIL import Image >>> catIm = Image.open('zophie.png') >>> catIm.size ❶ (816, 1088) ❷ >>> width, height = catIm.size ❸ >>> width 816 ❹ >>> height 1088 >>> catIm.filename 'zophie.png' >>> catIm.format 'PNG' >>> catIm.format_description 'Portable network graphics' ❺ >>> catIm.save('zophie.jpg')
从Zophie.png得到一个Image对象并保存在catIm中后,我们可以看到该对象的size属性是一个元组,包含该图像的宽度和高度的像素数❶。我们可以将元组中的值赋给width和height变量❷,以便分别访问宽度❸和高度❹。filename属性描述了原始文件的名称。format和format_description属性是字符串,描述了原始文件的图像格式(format_description比较详细)。
最后,调用save()方法,传入'zophie.jpg’,将新图像以文件名zophie.jpg保存到硬盘上❺。Pillow看到文件扩展名是jpg,就自动使用JPEG图像格式来保存图像。现在硬盘上应该有两个图像,zophie.png和zophie.jpg。虽然这些文件都基于相同的图像,但它们不一样,因为格式不同。
Pillow 还提供了 Image.new()函数,它返回一个 Image 对象。这很像Image.open(),不过Image.new()返回的对象表示空白的图像。Image.new()的参数如下:
· 字符串'RGBA',将颜色模式设置为RGBA(还有其他模式,但本书没有涉及)。
· 大小,是两个整数元组,作为新图像的宽度和高度。
· 图像开始采用的背景颜色,是一个表示RGBA值的四整数元组。你可以用ImageColor.getcolor()函数的返回值作为这个参数。另外,Image.new()也支持传入标准颜色名称的字符串。
例如,在交互式环境中输入以下代码:
>>> from PIL import Image ❶ >>> im = Image.new('RGBA', (100, 200), 'purple') >>> im.save('purpleImage.png') ❷ >>> im2 = Image.new('RGBA', (20, 20)) >>> im2.save('transparentImage.png')
这里,我们创建了一个Image对象,它有100像素宽、200像素高,带有紫色背景❶。然后,该图像存入文件purpleImage.png中。我们再次调用Image.new(),创建另一个Image对象,这次传入(20, 20)作为大小,没有指定背景色❷。如果未指定颜色参数,默认的颜色是不可见的黑色(0,0,0,0),因此第二个图像具有透明背景,我们将这个20×20的透明正方形存入transparentImage.png。
17.2.2 裁剪图片
裁剪图像是指在图像内选择一个矩形区域,并删除矩形之外的一切。Image对象的crop()方法接受一个矩形元组,返回一个Image对象,表示裁剪后的图像。裁剪不是在原图上发生的,也就是说,原始的Image对象原封不动,crop()方法返回一个新的Image对象。请记住,矩形元组(这里就是要裁剪的区域)包括左列和顶行的像素,直至但不包括右列和底行的像素。
在交互式环境中输入以下代码:
>>> croppedIm = catIm.crop((335, 345, 565, 560)) >>> croppedIm.save('cropped.png')
这得到一个新的Image对象,是剪裁后的图像,保存在croppedIm中,然后调用croppedIm的save(),将裁剪后的图像存入cropped.png。新文件cropped.png从原始图像创建,如图17-4所示。
图17-4 新图像只有原始图像剪裁后的部分
17.2.3 复制和粘贴图像到其他图像
copy()方法返回一个新的Image对象,它和原来的Image对象具有一样的图像。如果需要修改图像,同时也希望保持原有的版本不变,这非常有用。例如,在交互式环境中输入以下代码:
>>> catIm = Image.open('zophie.png') >>> catCopyIm = catIm.copy()
catIm和catCopyIm变量包含了两个独立的Image对象,它们的图像相同。既然catCopyIm中保存了一个Image对象,你可以随意修改catCopyIm,将它存入一个新的文件名,而zophie.png没有改变。例如,让我们尝试用paste()方法修改catCopyIm。
paste()方法在Image对象调用,将另一个图像粘贴在它上面。我们继续交互式环境的例子,将一个较小的图像粘贴到catCopyIm。
>>> faceIm = catIm.crop((335, 345, 565, 560)) >>> faceIm.size (230, 215) >>> catCopyIm.paste(faceIm, (0, 0)) >>> catCopyIm.paste(faceIm, (400, 500)) >>> catCopyIm.save('pasted.png')
首先我们向crop()传入一个矩形元组,指定zophie.png中的一个矩形区域,包含Zophie的脸。这将创建一个Image对象,表示230×215的剪裁区域,保存在faceIm中。现在,我们可以将faceIm粘贴到catCopyIm。paste()方法有两个参数:一个“源”Image对象,一个包含x和y坐标的元组,指明源Image对象粘贴到主Image对象时左上角的位置。这里,我们在catCopyIm上两次调用paste(),第一次传入(0, 0),第二次传入(400, 500)。这将faceIm两次粘贴到catCopyIm:一次faceIm的左上角在(0, 0),一次faceIm的左上角在(400, 500)。最后,我们将修改后的catCopyIm存入pasted.png。pasted.png如图17-5所示。
图17-5 Zophie猫,包含两次粘贴她的脸
注意
尽管名称是copy()和paste(),但Pillow中的方法不使用计算机的剪贴板。
请注意,paste()方法在原图上修改它的Image对象,它不会返回粘贴后图像的Image对象。如果想调用paste(),但还要保持原始图像的未修改版本,就需要先复制图像,然后在副本上调用paste()。
假定要用Zophie的头平铺整个图像,如图17-6所示。可以用两个for循环来实现这个效果。继续交互式环境的例子,输入以下代码:
>>> catImWidth, catImHeight = catIm.size >>> faceImWidth, faceImHeight = faceIm.size ❶ >>> catCopyTwo = catIm.copy() ❷ >>> for left in range(0, catImWidth, faceImWidth): ❸ for top in range(0, catImHeight, faceImHeight): print(left, top) catCopyTwo.paste(faceIm, (left, top)) 0 0 0 215 0 430 0 645 0 860 0 1075 230 0 230 215 --snip-- 690 860 690 1075 >>> catCopyTwo.save('tiled.png')
这里,我们将catIm的高度的宽度保存在catImWidth和catImHeight中。在❶,我们得到了catIm的副本,并保存在catCopyTwo。既然有了一个副本可以粘贴,我们就开始循环,将faceIm粘贴到catCopyTwo。外层for循环的left变量从0开始,增量是faceImWidth(即230)❷。内层for循环的top变量从0开始,增量是faceImHeight(即215)❸。这些嵌套的for循环生成了left和top的值,将faceIm图像按照网格粘贴到Image对象catCopyTwo,如图17-6所示。为了看到嵌套循环的工作,我们打印了left和top。粘贴完成后,我们将修改后的catCopyTwo保存到tiled.png。
图17-6 嵌套的for循环与paste(),用于复制猫脸(可以称之为dupli-cat)
17.2.4 调整图像大小
resize()方法在Image对象上调用,返回指定宽度和高度的一个新Image对象。它接受两个整数的元组作为参数,表示返回图像的新高度和宽度。在交互式环境中输入以下代码:
❶ >>> width, height = catIm.size ❷ >>> quartersizedIm = catIm.resize((int(width / 2), int(height / 2))) >>> quartersizedIm.save('quartersized.png') ❸ >>> svelteIm = catIm.resize((width, height + 300)) >>> svelteIm.save('svelte.png')
这里,我们将catIm.size元组中的两个值赋给变量width和height❶。使用width和height,而不是catIm.size[0]和catIm.size[1],让接下来的代码更易读。
第一个resize()调用传入int(width / 2)作为新宽度,int(height / 2)作为新高度❷,所以resize()返回的Image对象具有原始图像的一半长度和宽度,是原始图像大小的四分之一。resize()方法的元组参数中只允许整数,这就是为什么需要用int()调用对两个除以2的值取整。
这个大小调整保持了相同比例的宽度和高度。但传入resize()的新宽度和高度不必与原始图像成比例。svelteIm变量保存了一个Image对象,宽度与原始图像相同,但高度增加了300像素❸,让Zophie显得更苗条。
请注意,resize()方法不会在原图上修改Image对象,而是返回一个新的Image对象。
粘贴透明像素
通常透明像素像白色像素一样粘贴。如果要粘贴图像有透明像素,就将该图像作为第三个参数传入,这样就不会粘贴一个不透明的矩形。这个第三参数是“遮罩”Image对象。遮罩是一个Image对象,其中alpha值是有效的,但红、绿、蓝值将被忽略。遮罩告诉paste()函数哪些像素应该复制,哪些应该保持透明。遮罩的高级用法超出了本书的范围,但如果你想粘贴有透明像素的图像,就再传入该Image对象作为第三个参数。
17.2.5 旋转和翻转图像
图像可以用rotate()方法旋转,该方法返回旋转后的新Image对象,并保持原始Image对象不变。rotate()的参数是一个整数或浮点数,表示图像逆时针旋转的度数。在交互式环境中输入以下代码:
>>> catIm.rotate(90).save('rotated90.png') >>> catIm.rotate(180).save('rotated180.png') >>> catIm.rotate(270).save('rotated270.png')
注意,可以连续调用方法,对rotate()返回的Image对象直接调用save()。第一个rotate()和save()调用得到一个逆时针旋转90度的新Image对象,并将旋转后的图像存入rotated90.png。第二和第三个调用做的事情一样,但旋转了180度和270度。结果如图17-7所示。
图17-7 原始图像(左)和逆时针旋转90度、180度和270度的图像
注意,当图像旋转90度或270度时,宽度和高度会变化。如果旋转其他角度,图像的原始尺寸会保持。在Windows上,使用黑色的背景来填补旋转造成的缝隙,如图17-8所示。在OS X上,使用透明的像素来填补缝隙。rotate()方法有一个可选的expand关键字参数,如果设置为True,就会放大图像的尺寸,以适应整个旋转后的新图像。例如,在交互式环境中输入以下代码:
>>> catIm.rotate(6).save('rotated6.png') >>> catIm.rotate(6, expand=True).save('rotated6_expanded.png')
第一次调用将图像旋转6度,并存入rotate.png(参见图17-8的左边的图像)。第二次调用将图像旋转6度,expand设置为True,并存入rotate6_expanded.png(参见图17-8的右侧的图像)。
图17-8 图像普通旋转6度(左),以及使用expand=True(右)
利用transpose()方法,还可以得到图像的“镜像翻转”。必须向transpose()方法传入Image.FLIP_LEFT_RIGHT或Image.FLIP_TOP_BOTTOM。在交互式环境中输入以下代码:
>>> catIm.transpose(Image.FLIP_LEFT_RIGHT).save('horizontal_flip.png') >>> catIm.transpose(Image.FLIP_TOP_BOTTOM).save('vertical_flip.png')
像rotate()一样,transpose()会创建一个新Image对象。这里我们传入Image.FLIP_ LEFT_RIGHT,让图像水平翻转,然后存入horizontal_flip.png。要垂直翻转图像,传入Image.FLIP_TOP_BOTTOM,并存入vertical_flip.png。结果如图17-9所示。
图17-9 原始图像(左),水平翻转(中),垂直翻转(右)
17.2.6 更改单个像素
单个像素的颜色可以通过getpixel()和putpixel()方法取得和设置。它们都接受一个元组,表示像素的x和y坐标。putpixel()方法还接受一个元组,作为该像素的颜色。这个顔色参数是四整数RGBA元组或三整数RGB元组。在交互式环境中输入以下代码:
❶ >>> im = Image.new('RGBA', (100, 100)) ❷ >>> im.getpixel((0, 0)) (0, 0, 0, 0) ❸ >>> for x in range(100): for y in range(50): ❹ im.putpixel((x, y), (210, 210, 210)) >>> from PIL import ImageColor ❺ >>> for x in range(100): for y in range(50, 100): ❻ im.putpixel((x, y), ImageColor.getcolor('darkgray', 'RGBA')) >>> im.getpixel((0, 0)) (210, 210, 210, 255) >>> im.getpixel((0, 50)) (169, 169, 169, 255) >>> im.save('putPixel.png')
在❶,我们得到一个新图像,这是一个100×100的透明正方形。对一些坐标调用getPixel()将返回(0,0,0,0),因为图像是透明的❷。要给图像中的像素上色,我们可以使用嵌套的for循环,遍历图像上半部分的所有像素❸,用putpixel()设置每个像素的顔色❹。这里我们向putpixel()传入RGB元组(210,210,210),即浅灰色。
假定我们希望图像下半部分是暗灰色,但不知道深灰色的RGB元组。putpixel()方法不接受'darkgray'这样的标准颜色名称,所以必须使用 ImageColor.getcolor()来获得'darkgray'的颜色元组。循环遍历图像的下半部分像素❺,向putpixel()传入ImageColor. getcolor()的返回值❻,你现在应该得到一个图像,上半部分是浅灰色,下半部分是深灰色,如图17-10所示。可以对一些坐标调用getPixel(),确认指定像素的颜色符合你的期望。最后,将图像存入putPixel.png。
图17-10 putPixel.png中的图像
当然,在图像上一次绘制一个像素不是很方便。如果需要绘制形状,就使用本章稍后介绍的ImageDraw函数。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论