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).
The image is RGB 24-bit with no alpha, and the code is written in python2.
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.
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.
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.
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)
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