>>> from env_helper import info; info()
页面更新时间: 2023-06-24 11:21:12
运行环境:
Linux发行版本: Debian GNU/Linux 12 (bookworm)
操作系统内核: Linux-6.1.0-9-amd64-x86_64-with-glibc2.36
Python版本: 3.11.2
1.2. 用 Pillow
操作图像¶
既然知道了 Pillow
中颜色和坐标的工作方式,就让我们用 Pillow
来处理图像。图17-3中的图像将用于本章中所有 交互式环境的例子。你可以从
http://nostarch.com/automatestuff/ 下载它。
我的猫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('.')
Image.open()
函数的返回值是Image
对象数据类型, 它是
Pillow
将图像表示为为Python
值的方法。 可以调用
Image.open()
,传入文件名字符串,从一个 图像文件(任何格式)加载一个
Image
对象。通过 save()
方法,对Image
对象的所有更改都可以保
存到图像文件中(也是任何格式)。所有的旋转、调整大小、
裁剪、绘画和其他图像操作,都通过这个 Image
对象 上的方法调用来完成。
为了让本章的例子更简短,我假定你已导入了 Pillow
的 Image
模块,并将 Zophie
的图像保存在变量 catIm
中。要确保
zophie.png
文件在当前工作目录中,让 Image.open()
函数能找到它。否则,必须在 Image.open()
的
字符串参数中指定完整的绝对路径。
1.2.1. 处理 Image
数据类型¶
Image
对象有一些有用的属性,提供了加载的图像文件的
基本信息:它的宽度和高度、文件名和图像格式 (如 JPEG 、GIF或PNG)。
例如,在交互式环境中输入以下代码:
>>> from PIL import Image
>>> catIm = Image.open('zophie.png')
>>> catIm.size
(225, 300)
>>> width, height=catIm.size
>>> width
225
>>> height
300
>>> 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('purplelmage.png')
>>> im2 = Image.new('RGBA', (20, 20))
>>> im2.save('transparentImage.png')
这里,我们创建了一个 Image
对象,它有100像素宽、200像素高,带有紫色背景。 然后,该图像存入文件
purplelmage.png
中。 我们再次调用 Image.new()
,创建另一个
Image
对象,这次传入(20,20)作为大小,没有指定背景色。
如果未指定颜色参数,默认的颜色是不可见的黑色(0, 0, 0,
0),因此第二个图像具有透明背景, 我们将这个 20×20
的透明正方形存入
transparentlmage.png
。
1.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
从原始图像创建,如图所示。
新图像只有原始图像剪裁后的部分
1.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
。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 所示。
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
>>> catCopyTwo.save('tiled.png')
这里,我们将catIm
的高度的宽度保存在catIm Width
和
catIm Height
中。在 O
,我们得到了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
)
1.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()
方法的元组参数中只允许整数,这就是 为什么需要用 into
调用对两个除以2的值取整。
这个大小调整保持了相同比例的宽度和高度。但传入 resize()
的新宽度和高度不必与原始图像成比例。 sveltelm
变量保存了一个
Image
对象, 宽度与原始图像相同,但高度增加了 300像素,让 Zophie
显得更苗条。
请注意, resize()
方法不会在原图上修改 Image
对象,而是返回一个新的 Image
对象。
粘贴透明像素¶
通常透明像素像白色像素一样粘贴。如果要粘贴图像有透明像素,
就将该图像作为第三个参数传入,这样就不会粘贴一个不透明的
矩形。这个第三参数是“遮罩” Image
对象。遮罩是一个 Image
对象,其中 alpha
值是有效的,但红、绿、 蓝值将被忽略。遮罩告诉
paste()
函数哪些像素应该复制,
哪些应该保持透明。遮罩的高级用法超出了本书的范围,但如果
你想粘贴有透明像素的图像,就再传入 该 Image
对象作为第三个参数。
1.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 所示。
1.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')
在1中,我们得到一个新图像,这是一个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
。
当然,在图像上一次绘制一个像素不是很方便。如果需要绘制形状,就使用
ImageDraw
函数。