[PYTHON] PIL / Pillow cheat sheet

PIL / Pillow is a compact and fast image library for Python. We have summarized frequently used processes (updated from time to time)

Difference between PIL and Pillow

There is basically no reason to use PIL, Pillow has a bugfix for resizing filters and is of higher quality.

About Pillow speed

Pillow is tuned very fast and always runs faster than its similar library, ImageMagick. However, getpixel / putpixel is very slow, so don't use it for anything other than image generation. There is also a faster pillow-simd. It seems to be about 4 to 5 times faster than the original Pillow.

pillow-simd https://github.com/uploadcare/pillow-simd

reference

https://python-pillow.org/pillow-perf/

Image mode list

mode Description
1 Used for 1-bit mask, logical operation is possible
L 8bit grayscale
P Palette mode
RGB 8bit x 3
RGBA 8bit x 4 transparency(alpha)With
CMYK 8bit x 4 Commonly used for printing
YCbCr Often used for 8bit x 3 video
HSV 8bit x 3 pillow only
RGBa Multiply RGB value by alpha channel
LA Multiply the L value by the alpha channel
I 32bit integer
F 32bit floating point

Resize filter

filter Downscaling quality Upscaling quality performance
Image.NEAREST ⭐⭐⭐⭐⭐
Image.BOX ⭐⭐⭐⭐
Image.BILINEAR ⭐⭐⭐
Image.HAMMING ⭐⭐ ⭐⭐⭐
Image.BICUBIC ⭐⭐⭐ ⭐⭐⭐ ⭐⭐
Image.LANCZOS ⭐⭐⭐⭐ ⭐⭐⭐⭐

Image module

Grayscale

img.convert("L")

mono.png

Grayscale considering alpha value

alpha.convert("LA")

alpha.pngla.png

By the way, convert ('L') does not consider the alpha value.

alpha.convert("L")

alpha.pngl.png

HSV conversion

img.convert("HSV")

Only pillow can be converted to HSV color space. It is composed of three components: Hue, Saturation, and Value. The following is an example of shifting the color wheel.

h, s, v = img.convert("HSV").split()
_h = ImageMath.eval("(h + 128) % 255", h=h).convert("L")
Image.merge("HSV", (_h, s, v)).convert("RGB")

hsv.png

CIE XYZ conversion

CIE XYZ is a color space adjusted so that the Euclidean distance between colors is the same as the difference perceived by humans.

rgb2xyz = (
    0.412453, 0.357580, 0.180423, 0,
    0.212671, 0.715160, 0.072169, 0,
    0.019334, 0.119193, 0.950227, 0
)
img.convert("RGB", rgb2xyz)

Binarization

gray = img.convert("L")                     #Convert to grayscale
gray.point(lambda x: 0 if x < 230 else x)   #If the value is 230 or less, it will be 0.

bin.png

Brighten / darken the image

img.point(lambda x: x * 1.5)    # 1.Make it 5 times brighter
img.point(lambda x: x * 0.5)    # 1 /Darken to 2

ligter.pngdarker.png

Sepia

Convert the image to grayscale and then sepia it.

gray = img.convert("L")
Image.merge(
    "RGB",
    (   
        gray.point(lambda x: x * 240 / 255),
        gray.point(lambda x: x * 200 / 255),
        gray.point(lambda x: x * 145 / 255)
    )
)

cepia.png

Gamma correction

Gamma correction can also be converted at a very high speed by using a look-up table. Assuming that src = input color, γ = gamma value, and g = gain value, the gamma correction formula is as follows.

dst = \biggl(\frac{src}{255}\biggr)^{1/γ} \times g \times 255
def gamma_table(gamma_r, gamma_g, gamma_b, gain_r=1.0, gain_g=1.0, gain_b=1.0):
    r_tbl = [min(255, int((x / 255.) ** (1. / gamma_r) * gain_r * 255.)) for x in range(256)]
    g_tbl = [min(255, int((x / 255.) ** (1. / gamma_g) * gain_g * 255.)) for x in range(256)]
    b_tbl = [min(255, int((x / 255.) ** (1. / gamma_b) * gain_b * 255.)) for x in range(256)]
    return r_tbl + g_tbl + b_tbl

img.point(gamma_table(1.2, 0.5, 0.5))

gamma.png

Speeding up point used in loops

It is better not to pass lambda to ʻImage.point in the loop, it is faster to expand it in advance because the argument passed to point` is just a conversion table.

for ...:
   img.point(lambda x: x * 100)

#The lower process is equal to the upper process but faster
table = [x * 100 for x in range(256)] * len(img.getbands())
for ...:
    img.point(table)

getbbox ʻImage.getbboxreturns the smallest non-zero value in the image, an image with all zeros returnsNone`.

Margin cut of alpha component

alpha = Image.open("alpha.png ")

crop = alpha.split()[-1].getbbox()
alpha.crop(crop)

範囲を選択_003.png範囲を選択_004.png

Same pixel check

If the two images are the same ʻImageChops.difference returns all 0 images, so if getbboxisNone`, it can be judged to be the same.

ImageChops.difference(img1, img2).getbbox() is None

resize

img.resize((128, 128), Image.LANCZOS)

resize.png

thumbnail

Thumbnails maintain aspect ratio, unlike resizing. Note that for some reason thumbnail is a destructive method, it's ʻImage.copy` so it's a good idea to make a duplicate.

img.thumbnail((128, 128), Image.LANCZOS)
img.size
# (128, 79)

thumb.png

rotation

Specifying True for the argument ʻexpand` expands the image if it grows when rotated.

img.rotate(90, expand=True)

rotate90.png

Mosaic processing

Mosaic processing can be reduced and enlarged with ʻImage.LINEAR`, but if you reduce it after applying Gaussian blur, it will be a soft mosaic.

#Jagged mosaic
img.resize([x // 8 for x in img.size]).resize(img.size)

#Apply Gaussian blur for a soft mosaic
gimg = img.filter(ImageFilter.GaussianBlur(4))
gimg.resize([x // 8 for x in img.size]).resize(img.size)

mozaic2.pngmozaic.png

Alpha blend

Image.blend(img,effect_img, 0.5)

B9BSxGZmEQpmAAAAAElFTkSuQmCC.pngkL+ySM465ToAlAAAAAElFTkSuQmCC.pngblend.png

Subtractive color

img.quantize(4)    #Color reduction to 4 colors

quantize.png

Paste the alpha image

To paste an image with alpha, specify the image with alpha in the argument'mask'of ʻImage.paste`.

img.paste(alpha, mask=alpha)

paste.png

Count the colors used

ʻImage.getcolors`, which counts the colors used, cannot count more than 255 colors with no arguments. For images that use more than 255 colors, it is safe to pass the number of pixels as an argument. [^ 1]

[^ 1]: 2017-02-07 Fixed It was'Image.getcount', but it is correctly'Image.getcolors', I will fix it.

img.getcolors(img.size[0] * img.size[1])

histogram

Returns a list of image color histograms. Since each band is returned in succession, 256 x 3 = 768 elements are returned in RGB mode.

img.histogram()

Color replacement

There is no method to replace the color, if you want to replace the color please refer to the article below.

Replace image colors fast with PIL / Pillow

ImageOps module

Negative / positive reversal

ImageOps.invert(img)

nega.png

Flip left / right / flip up / down

ImageOps.mirror(img)    #Flip horizontal
ImageOps.flip(img)      #flip upside down

mirror.pngflip.png

Colorization

Colors a grayscale image with a pixel value of 0 to black and a pixel value of 255 to white.

gray = ImageOps.grayscale(img)
ImageOps.colorize(gray, black=(0, 0, 0), white=(255, 255, 0))

mono.png → colorize.png

Posterize

Reduces the bit depth of the image to the value of the argument to simplify the color.

ImageOps.posterize(img, 2)

posterize.png

Solarize

Inverts all pixel values above the threshold. I don't know where to use it.

ImageOps.solarize(img, 128)

solarize.png

Equalize

Equalize the histogram of the image. Apply nonlinear mapping to the input image to create a uniform distribution of grayscale values in the output image.

ImageOps.equalize(img)

equalize.png

ImageChops module

The ʻImageChops` module is a module for manipulating channels.

B9BSxGZmEQpmAAAAAElFTkSuQmCC.pngkL+ySM465ToAlAAAAAElFTkSuQmCC.png

The left is the image to be affected and the right is the image for effects. In this chapter, we will use these two images as samples.

Dodge (Linear) / Subtraction

ImageChops.add(img, effect_img)         # img + effect_img
ImageChops.subtract(img, effect_img)    # img - effect_img

wE7Grw7M8iQMgAAAABJRU5ErkJggg==.pngX+81Pf98mz2EwAAAABJRU5ErkJggg==.png

mod operation

ImageChops.add_modulo(img, effect_img)         # img + effect_img % MAX
ImageChops.subtract_modulo(img, effect_img)    # img - effect_img % MAX

addm.pngsubm.png

Multiply / screen

ImageChops.multiply(img, effect_img)
ImageChops.screen(img, effect_img)

8yo3kAAAAASUVORK5CYII=.pngP8BtLqhlqZTLrgAAAAASUVORK5CYII=.png

Comparison (bright) / comparison (dark)

ImageChops.lighter(img, effect_img)
ImageChops.darker(img, effect_img)

X+81Pf98mz2EwAAAABJRU5ErkJggg==.pngwDMatqtY+79MgAAAABJRU5ErkJggg==.png

Absolute value of difference

ImageChops.difference(img, effect_img)

diff.png

offset

ImageChops.offset(img, 100, 100)

offset.png

ImageFilter module

Performs a convolution (convolution operation). Various image conversions are performed by rearranging the matrix called the kernel.

Parameters Description
size Kernel size
scale Divide by this value after matrix operation
offset Add by this value after matrix operation
kernel Convolution matrix

reference

https://github.com/python-pillow/Pillow/blob/6e7553fb0f12025306b2819b9b842adf6b598b2e/PIL/ImageFilter.py

ImageFilter.BLUR

img.filter(ImageFilter.BLUR)

# size: (5, 5),
# scale: 16,
# offset: 0,
# kernel:(
#     1,  1,  1,  1,  1,
#     1,  0,  0,  0,  1,
#     1,  0,  0,  0,  1,
#     1,  0,  0,  0,  1,
#     1,  1,  1,  1,  1
# )

bluer.png

ImageFilter.DETAIL

img.filter(ImageFilter.DETAIL)

# size: (3, 3), 
# scale: 6,
# offset: 0,
# kernel: (
#     0, -1,  0,
#     -1, 10, -1,
#     0, -1,  0
# )

detail.png

ImageFilter.SHAPEN

img.filter(ImageFilter.SHARPEN)

# size: (3, 3),
# scale: 16,
# offset: 0,
# kernel: (
#     -2, -2, -2,
#     -2, 32, -2,
#     -2, -2, -2
# )

shapen.png

ImageFilter.CONTOUR

img.filter(ImageFilter.CONTOUR)

# size: (3, 3),
# scale: 1,
# offset: 255,
# kernel: (
#     -1, -1, -1,
#     -1,  8, -1,
#     -1, -1, -1
# )

contour.png

ImageFilter.EDGE_ENHANCE / ImageFilter.EDGE_ENHANCE_MORE

img.filter(ImageFilter.EDGE_ENHANCE)

# size: (3, 3),
# scale: 2,
# offset: 0,
# kernel: (
#     -1, -1, -1,
#     -1, 10, -1,
#     -1, -1, -1
# )

img.filter(ImageFilter.EDGE_ENHANCE_MORE)

# size: (3, 3),
# scale: 1,
# offset: 0,
# kernel: (
#     -1, -1, -1,
#     -1,  9, -1,
#     -1, -1, -1
# )

edge.pngedge_more.png

ImageFilter.EMBOSS

img.filter(ImageFilter.EMBOSS)

# size: (3, 3),
# scale: 1,
# offset: 128,
# kernel: (
#     -1,  0,  0,
#     0,  1,  0,
#     0,  0,  0
# )

emboss.png

ImageFilter.FIND_EDGES

img.filter(ImageFilter.FIND_EDGES)

# size: (3, 3),
# scale: 1,
# offset: 0,
# kernel: (
#     -1, -1, -1,
#     -1,  8, -1,
#     -1, -1, -1
# )

edge.png

ImageFilter.SMOOTH / ImageFilter.SMOOTH_MORE

img.filter(ImageFilter.SMOOTH)
# size: (3, 3),
# scale: 13,
# offset: 0,
# kernel: (
#     1,  1,  1,
#     1,  5,  1,
#     1,  1,  1
# )
# 

img.filter(ImageFilter.SMOOTH_MORE)
# size: (5, 5),
# scale: 100,
# offset: 0,
# kernel: (
#     1,  1,  1,  1,  1,
#     1,  5,  5,  5,  1,
#     1,  5, 44,  5,  1,
#     1,  5,  5,  5,  1,
#     1,  1,  1,  1,  1
# )

smooth.pngsmooth_more.png

Gaussian blur

Gaussian Blurにより画面の平滑化します。

img.filter(ImageFilter.GaussianBlur(1.0))
img.filter(ImageFilter.GaussianBlur(1.5))
img.filter(ImageFilter.GaussianBlur(3.0))

gb10.pnggb15.pnggb30.png

Expansion / contraction

MaxFilter is called Dilation and MinFilter is called Erosion.

img.filter(ImageFilter.MinFilter())
img.filter(ImageFilter.MaxFilter())

min.pngmax.png

reference

Expansion / contraction / opening / closing

Median filter

MedianFilter is often used to remove noise, and its contours are less blurred than Gaussian filters.

img.filter(ImageFilter.MedianFilter())

noise.pngmedian.png

reference

Noise reduction

Mode filter

Selects the most frequently used pixel value in a box of the specified size. Pixel values that occur only once or twice are ignored. ~~ I don't know where to use it. See ~~ Make your photos pictorial with Pillow's Mode Filter.

img.filter(ImageFilter.ModeFilter(5))

rank.png

Image Enhance module

Color balance adjustment

enhancer = ImageEnhance.Color(img)
enhancer.enhance(0.0)    #Black and white
enhancer.enhance(0.5)    #  ↕
enhancer.enhance(1.0)    #The original image

col0.pngcol05.pngsample.png

Contrast adjustment

enhancer = ImageEnhance.Contrast(img)
enhancer.enhance(0.0)    #Gray image
enhancer.enhance(0.5)    #  ↕
enhancer.enhance(1.0)    #The original image

con0.pngcon5.pngsample.png

Brightness adjustment

enhancer = ImageEnhance.Brightness(img)
enhancer.enhance(0.0)    #Black image
enhancer.enhance(0.5)    #  ↕
enhancer.enhance(1.0)    #The original image

br0.pngbr5.pngsample.png

Sharpness adjustment

enhancer = ImageEnhance.Sharpness(img)
enhancer.enhance(0.0)    #Blurred image
enhancer.enhance(0.5)    #  ↕
enhancer.enhance(1.0)    #The original image
enhancer.enhance(1.5)    #  ↕
enhancer.enhance(2.0)    #Sharp image

sha0.pngsample.pngsample.png

ImageMath module

The ʻImageMath` module is a module that allows you to write operations between pixels as if they were numerical operations. If you master it, you will be able to easily write complicated image processing.

--Only single band can be calculated, multi band is processed after splitting with Image.split --The range of values when calculating with float is 0.0 to 255.0 instead of 0.0 to 1.0. --The mode of the image being calculated will be "I" (int) or "F" (float), and finally the mode will be converted to "L". --Various operations (+,-, \ *, /, * \ *,%) are not pixel processing but whole image operation --The operation is performed for each Image, not for each pixel, and an Image is generated for each operation. --You can also pass callable objects such as lambda instead of operations

Since it can only process single bands, it is troublesome when converting images such as RGB, so it is a good idea to prepare the following helper function.

def _blend_f(img1, img2, func):
    blend_eval = "convert(func(float(a), float(b)), 'L')"
    bands = [
        ImageMath.eval(
            blend_eval,
            a=a,
            b=b,
            func=func
        )
        for a, b in zip(img1.split(), img2.split())
    ]
    return Image.merge(img1.mode, bands)

reference

Implement drawing modes such as PhotoShop at high speed with PIL / Pillow

overlay

def _over_lay(a, b):
    _cl = 2 * a * b / 255
    _ch = 2 * (a + b - a * b / 255) - 255
    return _cl * (a < 128) + _ch * (a >= 128)

_blend_f(img, effect_img, _over_lay)

overlay.png

Soft light

def _soft_light(a, b):
    _cl = (a / 255) ** ((255 - b) / 128) * 255
    _ch = (a / 255) ** (128 / b) * 255
    return _cl * (b < 128) + _ch * (b >= 128)

_blend_f(img, effect_img, _soft_light)

softlight.png

Hard light

def _hard_light(a, b):
    _cl = 2 * a * b / 255
    _ch = 2.0 * (a + b - a * b / 255.0) - 255.0
    return _cl * (b < 128) + _ch * (b >= 128)

_blend_f(img, effect_img, _hard_light)

hardlight.png

Use Photoshop drawing mode

I am creating a module called "Image4Layer" that implements the drawing mode of Photoshop.

https://github.com/pashango2/Image4Layer

Installation is easy with pip, pillow (PIL) must be pre-installed to run.

$pip install image4layer

It's easy to use, it's an example of compositing in color-dodge mode.

from PIL import Image
from image4layer import Image4Layer

source = Image.open("ducky.png ")
backdrop = Image.open("backdrop.png ")

Image4Layer.color_dodge(backdrop, source)

color_dodge.png

GIF writing

You can write multiple GIFs (GIF animations).

im.save(out, save_all=True, append_images=[im1, im2, ...])

This is an example of creating a simple animated GIF.

imgs = []
for i in range(100):
    imgs.append(img.point(lambda x: x * (1.0 - (i/100))))
    
img.save("anime.gif", save_all=True, append_images=imgs, loop=True)

animation.gif

reference

http://pillow.readthedocs.io/en/4.0.x/handbook/image-file-formats.html?highlight=seek#saving

Conversion to QImagae

Converting PyQt to QImage uses the ʻImageQt` module.

ImageQt.ImageQt(img)

If you are using PySide, the following method is recommended.

from PySide.QtGui import *
import io

img_buffer = io.BytesIO()
base.save(img_buffer, "BMP")
qimage = QImage()
qimage.loadFromData(img_buffer.getvalue(), "BMP")

It may seem like a wasteful process, but Pillow / PySide will take care of the troublesome parts such as RGB → BGR conversion and Y-axis inversion problem.

reference

Mutual conversion between PIL.Image and PyQt4.QtGui.QImage

PSNR

An index value that compares PSNR with two images. Currently, SSIM is better than PSNR, but PSNR is also often used. The higher the value, the better the image quality, and the standard quality is PSNR between 30 and 50 when measuring the degree of compression deterioration.

The formula is as follows: MSE is the mean square error and MAX is 255.

PSNR = 10 \times \log 10\frac{MAX^2}{MSE}

The function to find PSNR is as follows. You can find PSNR at high speed by using the ʻImageStat` module.

def psnr(img1, img2):
    diff_img = ImageChops.difference(img1, img2)
    stat = ImageStat.Stat(diff_img)
    mse = sum(stat.sum2) / len(stat.count) / stat.count[0]
    return 10 * math.log10(255 ** 2 / mse)

In addition, it is currently difficult to obtain SSIM at high speed with PIL / Pillow, so it is better to use the pyssim module or OpenCV.

reference

[Peak signal-to-noise ratio-Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%94%E3%83%BC%E3%82%AF%E4%BF%A1%E5%8F % B7% E5% AF% BE% E9% 9B% 91% E9% 9F% B3% E6% AF% 94)

Line art extraction

gray = img.convert("L")
gray2 = gray.filter(ImageFilter.MaxFilter(5))
senga_inv = ImageChops.difference(gray, gray2)
senga = ImageOps.invert(senga_inv)

senga.png

I referred to the method of here, which is very wonderful.

Recommended Posts

PIL / Pillow cheat sheet
Curry cheat sheet
SQLite3 cheat sheet
pyenv cheat sheet
conda command cheat sheet
Linux command cheat sheet
ps command cheat sheet
Spark API cheat sheet
Python3 cheat sheet (basic)
PySpark Cheat Sheet [Python]
Python sort cheat sheet
Go language cheat sheet
tox configuration file cheat sheet
Image processing with PIL (Pillow)
numpy memory reuse cheat sheet
[Python3] Standard input [Cheat sheet]
Data Science Cheat Sheet (Python)
Slack API attachments cheat sheet
Python Django Tutorial Cheat Sheet
scikit learn algorithm cheat sheet
Apache Beam Cheat Sheet [Python]
Google Test / Mock personal cheat sheet
Continuation Passing Style (CPS) Cheat Sheet
pillow
Python cheat sheet (for C ++ experienced)
Python Computation Library Cheat Sheet ~ itertools ~
Curry cheat sheet [Description example list version]
Generate many single-character images with Pillow (PIL)
Use PIL and Pillow with Cygwin Python
AtCoder cheat sheet in python (for myself)
Blender Python Mesh Data Access Cheat Sheet
Mathematical Optimization Modeler (PuLP) Cheat Sheet (Python)
A brief description of pandas (Cheat Sheet)
Fastly replace image colors with PIL / Pillow