pip install pillow
Pillow
中最重要的就是 Image
类了,导入方法如下:
from PIL import Image
假设当前工作目录下有一个 pics
文件夹,其中存放着 1.png
图片,若要打开它,只需
img = Image.open('pics/1.png')
如果打开成功,则上述语句将返回 Image
对象,我们可以使用 show()
方法来查看该图片
img.show()
我们还可以查看它的格式(后缀名)、图片尺寸(宽 × 高,单位是像素)和图片的模式:
img.format
# 'PNG'
img.size
# (605, 618)
img.mode
# 'RGBA'
以上结果说明,1.png
的格式是 PNG,宽 605,高 618,且图片模式为 RGBA
。
这里有必要介绍一下常见的四种图片模式:
模式 | 描述 |
---|---|
1 | 1 位像素(取值范围 0 - 1),0表示黑,1 表示白,单色通道 |
L | 8 位像素(取值范围 0 - 255),灰度图,单色通道 |
RGB | 3 × 8 位像素,真彩色,三色通道,每个通道的取值范围 0 - 255 |
RGBA | 4 × 8 位像素,四色通道(真彩色+透明通道) |
除了打开现有的图片之外,我们还可以创建图片,格式如下:
Image.new(mode, size, color)
mode
代表图片模式,size
为图片尺寸,color
代表图片的颜色,默认值为 0 代表黑色。此外,color
还支持 (R, G, B) 三元组数字格式,颜色的十六进制值和颜色的英文单词。
具体示例如下:
img = Image.new(mode='RGB', size=(100, 100), color=(123, 71, 66))
img.show()
img = Image.new(mode='RGB', size=(100, 100), color='#CD96CD')
img.show()
img = Image.new(mode='RGB', size=(100, 100), color='cyan')
img.show()
可能读者已经想到了,既然能创建图片,那又该如何保存呢?
保存图片的格式如下:
Image.save(fp, format=None)
其中 fp
为路径字符串,包含了要保存的文件名。format
为要保存的格式,当不指定文件格式时,它会以默认的图片格式来存储(根据提供的后缀名来自动选择格式);如果指定图片格式,则会以指定的格式存储图片。
例如,我们想将刚创建好的图片保存当当前工作目录下:
img = Image.new(mode='RGB', size=(100, 100), color='#CD96CD')
img.save('./mypic.png')
既然 Image.open()
可以用来打开图片,Image.save()
能够用来保存图片,那我们先打开一个 bmp
格式的图片,再将其保存为 png
格式的,不就可以实现格式转换了?
# 创建一个bmp图像用于转换
img = Image.new(mode='RGBA', size=(100, 100), color='#CD96CD')
img.save('./before.bmp')
# 转换格式
img = Image.open('before.bmp')
img.save('./after.png')
上述代码的确可以运行成功,即 bmp 成功转换成了 png。但需要注意的是,并非所有的图片格式都可以用 save()
进行转换,例如将 png 转换成 jpg:
img = Image.new(mode='RGBA', size=(100, 100), color='#CD96CD')
img.save('./before.png')
img = Image.open('before.png')
img.save('./after.jpg')
最终会报错:OSError: cannot write mode RGBA as JPEG'
。这是因为 png 和 jpg 图像模式不一致导致的。其中 png 是四通道 RGBA 模式,而 jpg 是三通道 RGB 模式。因此要想实现图片格式的转换,就要先将 png 转变为三通道 RGB 模式。
img = Image.new(mode='RGBA', size=(100, 100), color='#CD96CD')
img.save('./before.png')
img = Image.open('before.png')
img.convert('RGB') # 改变模式
img.save('./after.jpg')
常见图片后缀名,图片格式以及图片模式的整理:
extension name | format | mode |
---|---|---|
png | PNG | RGBA, RGB |
jpg | JPEG | RGB |
bmp | BMP | RGBA, RGB |
这一小节我们会尝试对下方的图片进行缩放:
首先读取图片并查看相应信息:
img = Image.open('./pics/1.jpg')
img.size
# (1024, 640)
img.mode
# 'RGB'
可以看出该图片是三通道的且尺寸为 1024 × 640 1024\times640 1024×640。接下来我们用 resize
对其进行缩放:
img = img.resize((500, 500)) # resize会返回一个新的Image对象
img.show()
基于此,我们还可以批量地修改图片尺寸。例如当前工作目录下有一个文件夹 pics
,里面仅存放着待修改尺寸的图片,我们期望修改尺寸后的图片保存到 pics_new
文件夹中。
def resize_all(new_shape, path, new_path):
if not os.path.exists(new_path):
os.mkdir(new_path)
for filename in os.listdir(path):
img = Image.open(os.path.join(path, filename))
img_new = img.resize(new_shape)
img_new.save(os.path.join(new_path, filename))
new_shape = (500, 500)
path = './pics'
new_path = './pics_new'
resize_all(new_shape, path, new_path)
我们可以将图片中的某一部分(区域)裁剪下来,做适当的处理后重新粘贴回去。
在 Pillow
中,区域的定位方式如下:
对于 2.3 节中的图片,其左上角顶点的坐标为 ( 0 , 0 ) (0,0) (0,0),右下角顶点的坐标为 ( 1024 , 640 ) (1024,640) (1024,640)。
如果我们要想裁剪它的左半部分,则对应区域的左上角的坐标为 ( 0 , 0 ) (0,0) (0,0),但右下角的坐标变成了 ( 512 , 640 ) (512,640) (512,640),我们把这两个元组按序排列成一个元组: ( 0 , 0 , 512 , 640 ) (0,0,512,640) (0,0,512,640),它对应了一个裁剪区域:
path = './pics/1.jpg'
img = Image.open(path)
box = (0, 0, 512, 640)
region = img.crop(box)
region.show()
需要注意的是 crop
操作并不会影响到原来的 Image
对象。
我们将裁剪下来的这部分旋转180度后重新拼接回去:
region = region.transpose(Image.ROTATE_180)
img.paste(region, box)
img.show()
我们可以将一张彩图的R、G、B三个通道分离开来,分别产生三个 Image
对象:
path = './pics/1.jpg'
img = Image.open(path)
r, g, b = img.split()
其中 r, g, b
分别代表一个 Image
对象。我们可以重新组合这些颜色对象来达到不同的效果:
img = Image.merge('RGB', (b, g, r))
img.show()
我们还可以合并两张图片,前提是它们的模式 mode
和尺寸 size
要相同。
假设 pics
文件夹下有两张待合并的图片 1.jpg
和 2.jpg
,则一个可能的例子如下:
img_1 = Image.open('./pics/1.jpg')
img_2 = Image.open('./pics/2.jpg')
# 确保尺寸和模式相同
img_2 = img_2.resize(img_1.size)
img_1 = img_1.convert('RGB')
img_2 = img_2.convert('RGB')
# 颜色通道分离
r1, g1, b1 = img_1.split()
r2, g2, b2 = img_2.split()
# 一种可能的组合
img_3 = Image.merge('RGB', (r2, g1, b2))
img_3.show()
除此之外,Image
类还提供了 blend()
方法用来混合 RGBA
模式的图片,这里不作介绍。
我们可以对 Image
对象调用 transpose()
方法来实现简单的翻转和旋转操作:
img = Image.open('./pics/1.jpg')
img = img.transpose(Image.FLIP_LEFT_RIGHT) # 左右翻转
img = img.transpose(Image.FLIP_TOP_BOTTOM) # 上下翻转
img = img.transpose(Image.ROTATE_90) # 逆时针转90°
img = img.transpose(Image.ROTATE_180) # 转180°
img = img.transpose(Image.ROTATE_270) # 逆时针转270°
img = img.transpose(Image.TRANSPOSE) # 转置(类似于矩阵转置)
有些时候,我们需要对图像旋转任意角度,这时候需要用到 rotate()
方法,例如逆时针旋转45°:
img = img.rotate(45)
img.show()
如果不想让填充色为黑色,可以使用 fillcolor
参数进行更改:
img = img.rotate(45, fillcolor='green')
当然 rotate()
还有许多其他参数,这里不作过多介绍。
PIL
中的 ImageFilter
模块包含了许多预定的增强过滤器。
from PIL import ImageFilter
我们可以对图像进行模糊处理:
img = Image.open('./pics/1.jpg')
img_blur = img.filter(ImageFilter.BLUR)
img_blur.show()
img_cont = img.filter(ImageFilter.CONTOUR)
img_cont.show()
边缘检测:
img_edge = img.filter(ImageFilter.FIND_EDGES)
img_edge.show()
生成平滑图像:
img_smo = img.filter(ImageFilter.SMOOTH)
img_smo.show()
Image
与 ndarray
的互相转化我们可以将 ndarray
转化为 Image
对象,其中 ndarray
的 size 必须满足 H × W × C H\times W\times C H×W×C, H H H 是图像的高度, W W W 是图像的宽度, C C C 是通道个数,且数据类型须为 np.uint8
。
pic = np.zeros((600, 1200, 3), dtype=np.uint8)
pic[:, :600] = [255, 0, 0]
pic[:, 600:] = [0, 0, 255]
img = Image.fromarray(pic)
img.show()
我们当然也可以将 Image
转化为 ndarray
后输出:
img = Image.open('./pics/1.jpg')
pic = np.array(img)
pic.shape
# (640, 1024, 3)
以下这段代码说明 pic[:, :, 0]
对应 R 通道,pic[:, :, 1]
对应 G 通道,pic[:, :, 2]
对应 B 通道:
img = Image.open('./pics/1.jpg')
pic = np.array(img)
pic_r = pic[:, :, 0]
pic_g = pic[:, :, 1]
pic_b = pic[:, :, 2]
r = Image.fromarray(pic_r)
g = Image.fromarray(pic_g)
b = Image.fromarray(pic_b)
img_new = Image.merge('RGB', (r, g, b))
img_new.show() # 结果与原图相同
事实上, H × W × C H\times W\times C H×W×C 的数组观察起来不方便, C × H × W C\times H\times W C×H×W 形状的数组更加符合直觉,我们可以这样调整:
def transform(img):
pic = np.array(img)
new_pic = np.moveaxis(pic, -1, 0)
return new_pic
img = Image.open('./pics/1.jpg')
pic = transform(img)
pic.shape
# (3, 640, 1024)
pic
# array([[[234, 234, 234, ..., 234, 234, 234],
# [234, 234, 234, ..., 234, 234, 234],
# [234, 234, 234, ..., 234, 234, 234],
# ...,
# [234, 234, 234, ..., 234, 234, 234],
# [234, 234, 234, ..., 234, 234, 234],
# [234, 234, 234, ..., 234, 234, 234]],
#
# [[227, 227, 227, ..., 227, 227, 227],
# [227, 227, 227, ..., 227, 227, 227],
# [227, 227, 227, ..., 227, 227, 227],
# ...,
# [227, 227, 227, ..., 227, 227, 227],
# [227, 227, 227, ..., 227, 227, 227],
# [227, 227, 227, ..., 227, 227, 227]],
#
# [[219, 219, 219, ..., 219, 219, 219],
# [219, 219, 219, ..., 219, 219, 219],
# [219, 219, 219, ..., 219, 219, 219],
# ...,
# [219, 219, 219, ..., 219, 219, 219],
# [219, 219, 219, ..., 219, 219, 219],
# [219, 219, 219, ..., 219, 219, 219]]], dtype=uint8)