返回介绍

17.2 用 Pillow 操作图像

发布于 2024-01-22 21:44:06 字数 10797 浏览 0 评论 0 收藏 0

既然知道了 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文