Python 中 Pillow 使用介绍

王 茂南 2020年9月9日07:48:22
评论
14109字阅读47分1秒
摘要这一篇简单介绍一下Pillow库的使用, 特别是介绍图像的palette与如何进行转换.

简介

这一篇会介绍一下Python中Pillow的简单使用. Pillow是在Python中常见的图像处理的库. 我们在这里会通过例子, 介绍一下常见的功能.

参考资料

 

Pillow常见功能介绍

我们使用如下的图片进行所有功能的测试.

Python 中 Pillow 使用介绍

打开图片并显示

  1. from PIL import Image
  2. im = Image.open("./test.jpg")
  3. im.rotate(45).show()

这里我们不仅打开了图片, 还对其进行旋转. 最终得到了如下的效果.

Python 中 Pillow 使用介绍

 

获取图像大小与获得缩略图

下面的代码可以将图片大小转换为128×128.

  1. from PIL import Image
  2. im = Image.open("./test.jpg")
  3. print(im.size) # (414, 338)
  4. # 获得缩略图
  5. im.thumbnail((128, 128), resample=Image.NEAREST)
  6. im.save("test_thumbnail.jpg", "JPEG")

 

从 Numpy 导入并保存

假设下面 A 是 numpy array,那么就可以按照下面来将其转换为图片:

  1. from PIL import Image
  2. im = Image.fromarray(A)
  3. im.save("your_file.jpeg")

 

图像大小的放缩-resize

我们可以使用 resize 对原始的图像进行放缩。下面的代码,可以将原始图像转换为指定的 width 和 height 大小的图像。

  1. img = img.resize((width, height), Image.ANTIALIAS)

 

图片的融合-blend

我们可以使用 pillow 来对两张图片进行融合(需要注意的是,这里 Image.blend 合并时候需要两个图片大小相等,且是对整个图片进行融合,如果想要有更加复杂的操作,可以使用 Image.composite,这个会在后面有讲到)。如下有两张图片,分别是一头牛的轮廓:

Python 中 Pillow 使用介绍

和按照这个牛生成的字符画,字符画的生成方式如下面链接所示,Python生成字符画

Python 中 Pillow 使用介绍

我们可以将上面的两张图融合在一起,这样可以更好看出字符画是什么动物,使用 blend 来进行融合,如下所示:

  1. img = Image.open('./niu_2.png')
  2. background = Image.open('niu_1.jpg')
  3. background = background.convert('RGBA') # 两个图片的格式需要相同
  4. img = Image.blend(background, img, 0.7)
  5. img.save('out_with_background.png')

最终的效果如下所示,可以看出这个时候效果会更加明显:

Python 中 Pillow 使用介绍

参考资料ValueError: images do not match when blending pictures in PIL

 

JPEG 与 PNG 之间的转换

首先是将 png 的图像转换为 jpeg

  1. from PIL import Image
  2. im = Image.open("Ba_b_do8mag_c6_big.png")
  3. rgb_im = im.convert('RGB')
  4. rgb_im.save('colors.jpg')

接着是将 jpeg 的图像转换为 png,只需要将上面的 RGB 替换为 RGBA 即可。

  1. im = im.convert("RGBA")

 

Image.Crop

使用Image.Crop来进行图像的裁剪. 图像的坐标如下图所示:

Python 中 Pillow 使用介绍

我们首先获得图像的长和宽, 之后通过除2, 获得图像的左上角.

  1. from PIL import Image, ImageOps
  2. img = Image.open('./profile_color.jpg')
  3. width, height = img.size
  4. new_img = img.crop((0, 0, width/2, height/2))
  5. new_img.show()

 

Image.putpalette

在说这个函数之前, 我们首先介绍一下什么是一个图像的palette. Palette相当于是一个图像的所使用的颜色的集合, 类似于画家手中的颜色盘 (An image palette is a collection of colors that an image uses — similar to a painter's palette).

一个图像的 palette 表示我们在显示器上能看到的所有颜色, 只有在 16-256 色之间的图片会有palette. (An image palette is a subset of all the colors your monitor can display. Not all images have image palettes — only images with color depths between 16 and 256 colors have palettes that you can fine-tune and edit)

Images with a color depth of 16 million colors do not have an image palette because they can contain all the colors your computer can display. For these images, you can load an image palette to decrease the image's color depth to 256 colors (8-bit). (对于一些图片, 我们可以减少palette的格式)

下面我们自定义一个 palette, 然后将其应用到一张图片上. 我们会使用 putpalette 来完成. 下面是putpalette 的详细介绍.

Attaches a palette to this image. The image must be a "P" or "L" image, and the palette sequence must contain 768 integer values (这里是256色, 每一个颜色有RGB三通道, 所以一共是768个整数), where each group of three values represent the red, green, and blue values for the corresponding pixel index. Instead of an integer sequence, you can use an 8-bit string.

我们在这个 palette 里面只使用 4 种颜色, 对于的 RGB 分别是:

  • (0, 0, 0)
  • (102, 102, 102)
  • (176, 176, 176)
  • (255, 255, 255)
  1. from PIL import Image
  2. oldimage = Image.open("./test.jpg")
  3. palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255]
  4. newimage = Image.new('P', oldimage.size) # 创建一个和oldimage一样大的图像, 模式为P
  5. newimage.putpalette(palettedata * 64) # 指定palette, 这里需要将palette变换为768长度的list
  6. newimage.paste(oldimage, (0, 0) + oldimage.size) # 将原始图片粘贴过来
  7. newimage.show()

于是, 还是使用上面的图像, 我们可以将其转换为只包含四种颜色.

Python 中 Pillow 使用介绍

 

Image.quantize

除了上面的方式进行转换外, 我们可以使用 quantize 来完成相同的工作.

Convert the image to 'P' mode with the specified number of colors.

在这里我们的 palette 里面还是只使用 4 种颜色. 我们是直接用过 quantize 进行转换, 而不需要新建一个图像, 然后通过 paste 来完成。

  1. from PIL import Image
  2. image = Image.open("./test.jpg")
  3. palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255]
  4. palimage = Image.new('P', (16, 16))
  5. palimage.putpalette(palettedata * 64)
  6. newimage = image.quantize(palette=palimage)
  7. newimage.show()

最终的效果图如下所示:

Python 中 Pillow 使用介绍

 

Image.convert

同样的功能, 我们可以使用 Image.convert 来完成. 在这里会遇到 ImageCore 的相关问题. 下面做一些简单的说明.

The parts of PIL implemented in C are in the PIL._imaging module, also available as Image.core after you from PIL import Image. Current versions of Pillow give every PIL.Image.Image instance a member named im which is an instance of ImagingCore, a class defined within PIL._imaging. You can list its methods with help(oldimage.im), but the methods themselves are undocumented from within Python.

The convert method of ImagingCore objects is implemented in _imaging.c. It takes one to three arguments and creates a new ImagingCore object (called Imaging_Type within _imaging.c).

  • mode (required): mode string (e.g. "P")
  • dither (optional, default 0): PIL passes 0 or 1
  • paletteimage (optional): An ImagingCore with a palette

下面是完整的代码:

  1. def quantizetopalette(silf, palette, dither=False):
  2.     """Convert an RGB or L mode image to use a given P image's palette."""
  3.     silf.load()
  4.     # use palette from reference image
  5.     palette.load()
  6.     if palette.mode != "P":
  7.         raise ValueError("bad mode for palette image")
  8.     if silf.mode != "RGB" and silf.mode != "L":
  9.         raise ValueError(
  10.             "only RGB or L mode images can be quantized to a palette"
  11.             )
  12.     im = silf.im.convert("P", 1 if dither else 0, palette.im)
  13.     # the 0 above means turn OFF dithering
  14.     # Later versions of Pillow (4.x) rename _makeself to _new
  15.     try:
  16.         return silf._new(im)
  17.     except AttributeError:
  18.         return silf._makeself(im)
  19. if __name__ == "__main__":
  20.     # 读取图像
  21.     from PIL import Image
  22.     image = Image.open("./test.jpg")
  23.     palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255]
  24.     palimage = Image.new('P', (16, 16))
  25.     palimage.putpalette(palettedata * 64)
  26.     newimage = quantizetopalette(image, palimage, dither=False)
  27.     newimage.show()

同时, 这里我们可以选择是否要dither, 我们分别来看一下True和False时候的效果. 当dither=False的时候, 是没有散的色块的, 都是大的色块.

Python 中 Pillow 使用介绍

dither=True的时候, 得到的图像就是会有小的散点状.

Python 中 Pillow 使用介绍

 

转换为灰度图

上面提到了的convert, 还可以将图像转换为灰度图. 灰度图使用L来表示. 计算公式如下所示:

L = R * 299/1000 + G * 587/1000 + B * 114/1000
  1. from PIL import Image
  2. img = Image.open('test.jpg').convert('L')
  3. img.show()

 

Image.point

还有一个比较常用的函数就是point, 它可以依次对图像中的像素点进行处理. 我们看下面的一个例子.

  1. from PIL import Image
  2. img = Image.open('test.jpg').convert('L')
  3. img = img.point(lambda color: color > 100 and 255)
  4. img.show()

该段语句的作用就是, 对每个像素点进行判断, 如果大于100, 那么就是0, 否则就是255. 于是, 最终得到的图像如下所示:

Python 中 Pillow 使用介绍

参考资料, How to I use PIL Image.point(table) method to apply a threshold to a 256 gray image

 

不同的滤波

  1. from PIL import Image, ImageFilter
  2. im = Image.open('./test.jpg')
  3. # im = im.filter(ImageFilter.BLUR) # 模糊
  4. # im = im.filter(ImageFilter.SMOOTH_MORE) # 平滑
  5. im = im.filter(ImageFilter.CONTOUR) # 边缘提取
  6. im.show()

例如上面的滤波可以对图像进行边缘检测, 结果如下所示:

Python 中 Pillow 使用介绍

 

OpenCV 格式与 Pillow 格式互相转换

有一点需要牢记:

  • OpenCV 中图像的颜色空间是 BGR
  • Pillow 中的颜色空间是 RGB

OpenCV 格式到 Pillow 格式

图片通过 OpenCV 读取是 numpy 的格式,通过 Image.fromarray() 将其转换为 pillow 的格式:

  1. img = cv2.imread("./cat.png")
  2. img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  3. print(type(img)) # <class 'numpy.ndarray'>
  4. im_pil = Image.fromarray(img) # 转换为 pillow 格式
  5. print(type(im_pil)) # <class 'PIL.Image.Image'>

参考资料Convert opencv image format to PIL image format?

 

Pillow 格式到 OpenCV 格式

将 pillow 格式转换为 OpenCV 格式其实就是转换为 numpy 的格式。我们只需要使用 np.array(或是 np.asarray,这个节约内存) 即可。注意下面进行了颜色空间的转换(转换到了 OpenCV 使用的 BGR 颜色空间)

  1. pil_image  = Image.open('./cat.png').convert('RGB')
  2. open_cv_image = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR) # 转换为 opencv 的格式

参考资料Convert image from PIL to openCV format

 

图片的合并

有的时候我们需要将两张图片进行拼接(或是「横向拼接」,或是「纵向拼接」)。于是我们的想法是创建一个新的空白图片,将要拼接的图片粘贴上去即可:

首先导入需要使用的库:

  1. import numpy as np
  2. import copycopy
  3. from PIL import Image

将两个图像水平方向进行拼接,其中 ratio 表示拼接的时候留白,可以在 Image.new 中的 color 调整留白的颜色:

  1. def get_concat_h(im1, im2, ratio=0.05):
  2.     """水平方向将两个 image 进行合并
  3.     """
  4.     _im1 = copycopy.deepcopy(im1)
  5.     _im2 = copycopy.deepcopy(im2)
  6.     if type(_im1).__module__ == np.__name__:
  7.         _im1 = Image.fromarray(np.uint8((1-_im1)*255), 'L') # L 表示「灰度图」
  8.     if type(_im2).__module__ == np.__name__:
  9.         _im2 = Image.fromarray(np.uint8((1-_im2)*255), 'L')
  10.     width = int(ratio*_im2.width)
  11.     dst = Image.new(mode='L', size=(width + _im1.width + _im2.width, _im1.height), color=(0))
  12.     dst.paste(_im1, (0, 0))
  13.     dst.paste(_im2, (width + _im1.width, 0))
  14.     return dst

将两个图像垂直方向进行拼接。其中 ratio 表示拼接的时候留白,可以在 Image.new 中的 color 调整留白的颜色:

  1. def get_concat_v(im1, im2, ratio=0.1):
  2.     """垂直方向将两个 image 进行合并
  3.     """
  4.     _im1 = copycopy.deepcopy(im1)
  5.     _im2 = copycopy.deepcopy(im2)
  6.     if type(_im1).__module__ == np.__name__:
  7.         _im1 = Image.fromarray(np.uint8((1-_im1)*255), 'L')
  8.     if type(_im2).__module__ == np.__name__:
  9.         _im2 = Image.fromarray(np.uint8((1-_im2)*255), 'L')
  10.     height = int(ratio*_im2.height)
  11.     dst = Image.new('L', (_im1.width, height + _im1.height + _im2.height), (255))
  12.     dst.paste(_im1, (0, 0))
  13.     dst.paste(_im2, (0, height + _im1.height))
  14.     return dst

现在我们希望把一张图片变成四分,左右拼接,接着垂直拼接:

  1. _tmp_1 = get_concat_v(example_data, example_data)
  2. _tmp_2 = get_concat_v(example_data, example_data)
  3. _tmp = get_concat_h(_tmp_1, _tmp_2)
  4. _tmp.save('._tmp.jpg')

最终的效果如下所示:

Python 中 Pillow 使用介绍

参考资料Concatenate images with Python, Pillow

 

合并两张图片-Image.composite

Pillow 中,我们可以使用 Image.composite 来合并两张图片。需要注意的是,Image.composite 需要两张相同大小的图像。

这一小节的内容,主要参考自链接,Composite two images according to a mask image with Python, Pillow

Image.composite 的简单使用

我们想要将以下的两张图像进行简单的合并:

Python 中 Pillow 使用介绍 Python 中 Pillow 使用介绍

我们首先导入这两张图片,并创建一个相同大小的 mask,这个遮罩就是一个灰色的图像。这里 mask 会对 im1 进行遮挡,接着和 im2 进行合并(如果把 mask 的 color 设置为 0,那么就只有 im2 的图像了):

  1. im1 = Image.open('./lena.jpg')
  2. im2 = Image.open('./rocket.jpg').resize(im1.size)
  3. mask = Image.new("L", im1.size, 128) # 创建遮罩
  4. im = Image.composite(im1, im2, mask)
  5. im.show()

上面的 mask 如果显示出来,则如下图所示:

Python 中 Pillow 使用介绍

最终上面的两幅图像合并的效果如下所示:

Python 中 Pillow 使用介绍

上面的操作和我们直接使用 Image.blend 的效果是一样的,如下所示:

  1. im = Image.blend(im1, im2, 0.5)

 

创建特定形状的 mask

上面我们是对全局图片做了合并,有的时候我们需要我们的这周为特殊的形状。如下所示我们创建一个在人脸部分有空白的遮罩:

  1. im1 = Image.open('./lena.jpg')
  2. im2 = Image.open('./rocket.jpg').resize(im1.size)
  3. # 创建 mask
  4. mask = Image.new("L", im1.size, 0) # 默认为黑
  5. draw = ImageDraw.Draw(mask)
  6. draw.ellipse((140, 50, 260, 170), fill=255) # 中间部分为白
  7. mask.show()
  8. # 进行合并
  9. im = Image.composite(im1, im2, mask)
  10. im.show()

遮罩的形状如下图所示:

Python 中 Pillow 使用介绍

最终两张图片合并的效果如下图所示,这个 mask 会首先和 im1 进行合并,然后在和 im2 进行合并,所以最终只会把人脸部分显示出来:

Python 中 Pillow 使用介绍

我们还可以对 mask 的边缘部分进行处理,加入一些透明度:

  1. mask_blur = mask.filter(ImageFilter.GaussianBlur(10))
  2. im = Image.composite(im1, im2, mask_blur)

最终的效果如下所示:

Python 中 Pillow 使用介绍

 

图片上添加文字

有的时候, 我们需要在图片上加上文字, 这个时候就需要使用 ImageDraw 来完成。下面看一个简单的例子, 我们使用 ImageDraw 将导入的图片当作背景,接着写入文字的内容。

  1. from PIL import Image, ImageDraw, ImageFont
  2. im = Image.open('./test.jpg')
  3. # initialise the drawing context with
  4. # the image object as background
  5. draw = ImageDraw.Draw(im)
  6. font_fname = './legofy/times.ttf'
  7. font_size = 50
  8. font = ImageFont.truetype(font_fname, font_size)
  9. (x, y) = (0, 0) # 文字显示位置
  10. message = "Hello World!"
  11. color = 'rgb(0, 0, 0)' # black color
  12. # draw the message on the background
  13. draw.text((x, y), message, fill=color, font=font)
  14. im.show()

最终的图片效果如下图所示:

Python 中 Pillow 使用介绍

有的时候, 我们的文字过长, 图片里面一行是放不下的, 我们需要进行换行, 于是我们需要对输入的一段文字, 按照长度进行划分. 首先我们定义一个函数, 用来对文字的长度进行划分.

  1. def text_wrap(text, font, max_width):
  2.     lines = []
  3.     # If the width of the text is smaller than image width
  4.     # we don't need to split it, just add it to the lines array
  5.     # and return
  6.     if font.getsize(text)[0] <= max_width:
  7.         lines.append(text)
  8.     else:
  9.         # split the line by spaces to get words
  10.         words = text.split(' ')
  11.         i = 0
  12.         # append every word to a line while its width is shorter than image width
  13.         while i < len(words):
  14.             line = ''
  15.             while i < len(words) and font.getsize(line + words[i])[0] <= max_width:
  16.                 line = line + words[i] + " "
  17.                 i += 1
  18.             if not line:
  19.                 line = words[i]
  20.                 i += 1
  21.             # when the line gets longer than the max width do not append the word, 
  22.             # add the line to the lines array
  23.             lines.append(line)
  24.     return lines

这里函数的输入分别是:

  • text: 要分割的文字;
  • font: 文字的大小, 字体, 用来计算一行可以放多少文字;
  • max_width: 这里一般是图片的宽度;

接着我们定义绘图的函数, 借助上面的 text_wrap, 对较长的文字进行分割之后, 我们使用循环进行绘制. 这里只需要依次改变 y 轴的位置即可. 于是我们定义了 draw_text 函数, 如下所示.

  1. def draw_text(text):
  2.     # open the background file
  3.     img = Image.open('./test.jpg')
  4.     draw = ImageDraw.Draw(img)
  5.     # size() returns a tuple of (width, height) 
  6.     image_size = img.size
  7.     # create the ImageFont instance
  8.     font_file_path = './legofy/fonts/times.ttf'
  9.     font = ImageFont.truetype(font_file_path, size=50)
  10.     # get shorter lines (自动进行分割)
  11.     lines = text_wrap(text, font, image_size[0])
  12.     print(lines) # ['This could be a single line text ', 'but its too long to fit in one. ']
  13.     x = 10
  14.     y = 20
  15.     color = 'rgb(0,0,0)'
  16.     line_height = font.getsize('hg')[1] # 获得行间距
  17.     for line in lines:
  18.         # draw the line on the image
  19.         draw.text((x, y), line, fill=color, font=font)
  20.         # update the y position so that we can use it for next line
  21.         y = y + line_height

最终, 我们来测试一下最后的效果.

  1. draw_text("This could be a single line text but its too long to fit in one.")

最终图像的效果如下所示:

Python 中 Pillow 使用介绍

参考资料

 

添加水印

添加文字水印

这里我们利用前面介绍的 ImageDraw 在图片上添加指定的文字,来添加文字水印。

  1. from PIL import Image, ImageDraw, ImageFont
  2. def add_num_watermark(img,content):
  3.     """加文字水印
  4.     """
  5.     draw = ImageDraw.Draw(img)
  6.     myfont = ImageFont.truetype('./STKAITI.TTF', size=40)
  7.     fillcolor = "#ff0000" 
  8.     width, height = img.size # 获得图片大小
  9.     content_len = len(content) # 获得文字的长度
  10.     draw.text((width-content_len*50, height-50), content, font=myfont, fill=fillcolor)
  11.     img = img.convert("RGB") # 转换为 RGB, 用来保存为 jpeg 格式
  12.     img.save('result_num.jpg', 'jpeg')
  13.     return 0

我们使用下面的图像来进行测试(原始图片是没有下面的图片水印的):

Python 中 Pillow 使用介绍

我们使用下面的方式来在图像上添加「文艺数学君」的水印:

  1. content = '文艺数学君'
  2. image = Image.open('./62.png')
  3. add_num_watermark(image, content)

最终生成的效果如下图所示,可以看到图片的右下角有文字水印:

Python 中 Pillow 使用介绍

 

添加图片水印

所谓添加图片水印就是将想要的「水印图片」与原始图像进行合并。主要有以下的几个步骤:

步骤一:创建一个和原始图像一样大小的图像,并在右下角放置水印图像;

Python 中 Pillow 使用介绍

步骤二:创建一个 mask,只有在图像水印部分有灰度值(相当于透明度),其他位置都是黑色:

Python 中 Pillow 使用介绍

步骤三:将上面创建的两个图片,与原始图像进行合并即可。下面是完整的代码:

  1. def add_pic_waterMark(im,mark):
  2.     """加图片水印
  3.     """
  4.     im = im.convert('RGBA')
  5.     # 创建 layer, 原始图像大小, 但是右下角有水印
  6.     mask_width, mask_height = mark.size # 获取水印的大小
  7.     layer = Image.new('RGBA', im.size, (0, 0, 0, 0))
  8.     layer.paste(mark, (im.size[0] - mask_width, im.size[1] - mask_height)) # 放置水印, 左下角位置
  9.     layer.show()
  10.     # 创建 mask, 只把「水印」部分显示, 同时加一些透明度
  11.     mask = Image.new('L', im.size, 0)
  12.     draw = ImageDraw.Draw(mask)
  13.     draw.polygon([
  14.                     (im.size[0] - mask_width, im.size[1] - mask_height),
  15.                     (im.size[0] - mask_width, im.size[1]),
  16.                     (im.size[0], im.size[1]),
  17.                     (im.size[0], im.size[1] - mask_height)
  18.                 ], fill=77)
  19.     mask.show()
  20.     # 图像合并
  21.     out = Image.composite(layer, im, mask)
  22.     out = out.convert("RGB")
  23.     out.save('result_pic.jpg', 'jpeg')
  24.     return 0

 

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南
  • 本文由 发表于 2020年9月9日07:48:22
  • 转载请务必保留本文链接:https://mathpretty.com/12731.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: