[PYTHON] Fastly replace image colors with PIL / Pillow

When replacing colors with PIL / Pillow, the easiest way to come up with is to use Image.getpixel / Image.putpixel.

However, Image.getpixel / Image.putpixel is so slow that it takes a few seconds to process a single image, which is very slow. There is a fast replacement method using numpy, but it seems foolish to increase the number of dependent modules when replacing colors.

Therefore, I thought of a method to replace colors at high speed with only PIL / Pillow.

As a sample, let's replace the clothes color (255, 204, 0) in the image below with (48, 255, 48).

sample.png  result.png

The image is RGB 24-bit with no alpha, and the code is written in python2.

Separate images by color

First, use Image.split to break the image into bands for each color.

from __future__ import unicode_literals, print_function, absolute_import
from PIL import Image, ImageChops

img = Image.open("sample.png ")

r, g, b = img.split()

The contents of each RGB band are as follows.

r.png g.png b.png

Binarize for each specific color in Image.point

src_color = (255, 204, 0)

_r = r.point(lambda _: 1 if _ == src_color[0] else 0, mode="1")
_g = g.point(lambda _: 1 if _ == src_color[1] else 0, mode="1")
_b = b.point(lambda _: 1 if _ == src_color[2] else 0, mode="1")

Image.point is a method to convert a table. This time, lambda is used to generate an image that extracts only a specific value for each band.

The point is that mode = "1" is specified in the argument, and if "1" is specified in mode, an image with a color depth of 1 bit is generated. This is the process required for conversion in the ImageChops module described later.

The contents of each RGB band are as follows.

_r.png _g.png _b.png

AND-synthesize each band to generate a mask

The ImageChops module is a convenient module that can perform various compositing processes such as additive synthesis and multiplication compositing.

This time, AND composition is performed using ImageChops.logical_and. This method accepts only images with mode = "1".

mask = ImageChops.logical_and(_r, _g)
mask = ImageChops.logical_and(mask, _b)

You can create a mask that extracts pixels (that is, a specific color) that are 1 in all bands.

mask.png

All you have to do is use this mask and paint the color you want to replace.

dst_color = (48,255,48)
img.paste(Image.new("RGB", img.size, dst_color), mask=mask)

processing speed

I compared the processing with the case of using getpixel / putpixel.

In [27]: %time replace_put_pixel(img, src_color, dst_color)
Wall time: 234 ms

In [28]: %time replace_fast(img, src_color, dst_color)
Wall time: 1.96 ms

The getpixel / putpixel version has 234 ms, while this method has 1.96 ms. It's more than 100 times faster.

Recommended Posts

Fastly replace image colors with PIL / Pillow
Image processing with PIL (Pillow)
Image Processing with PIL
Create a dummy image with Python + PIL.
Generate many single-character images with Pillow (PIL)
Use PIL and Pillow with Cygwin Python
I tried playing with the image with Pillow
Easy image processing in Python with Pillow
Create an image composition app with Flask + Pillow
PIL installation with pip
PIL / Pillow cheat sheet
Image recognition with keras
Image processing with Python
Convert color space from RGB to CIELAB with PIL (Pillow)