CJ H
CJ H

Reputation: 59

How to extract dominant colors using palettes on RGB images?

I'm trying to build a module that can extract dominant colors from an image. For example, I'm trying to extract four colors from this flag image:

enter image description here

The getpalette and getcolors methods return None, if I try to use them in RGB mode. (I've tried setting maxcolors, I still get None.)

paletted = img.convert('RGB', palette=Image.ADAPTIVE, colors=4)
print(paletted.getpalette())
print(paletted.getcolors(maxcolors=256))
# None
# None

If I convert to P mode, I can use these modules, but I lose the yellow.

paletted = img.convert('P', palette=Image.ADAPTIVE, colors=4)
paletted.show()

enter image description here

What am I doing wrong here?

Upvotes: 0

Views: 1781

Answers (1)

HansHirse
HansHirse

Reputation: 18905

First of all, the following doesn't work, or to be precise, is simply ignored:

paletted = img.convert('RGB', palette=Image.ADAPTIVE, colors=4)

From the documentation on Image.convert:

palette – Palette to use when converting from mode "RGB" to "P". Available palettes are WEB or ADAPTIVE.

You're converting from RGB to RGB, thus the palette and colors parameters are ignored.

Next issue, which also prevents the proper conversion to mode P: You have a JPG image with JPG artifacts. Your image contains a lot more colors than you might expect. From the documentation on Image.getcolors:

maxcolors – Maximum number of colors. If this number is exceeded, this method returns None. The default limit is 256 colors.

Increase maxcolors (the possible maximum number of different colors for some full 24 bit RGB image is 224), and check the four most prominent colors:

from PIL import Image

img = Image.open('47ckF.jpg')
n_dom_colors = 4
dom_colors = sorted(img.getcolors(2 ** 24), reverse=True)[:n_dom_colors]
print(dom_colors)
# [(135779, (0, 0, 0)), (132476, (0, 0, 254)), (109155, (254, 0, 0)), (2892, (251, 2, 0))]

You get solid black, nearly solid blue, nearly solid red, and a variant of red. Where's yellow? JPG artifacts! You have a ton of black-ish, blue-ish, and red-ish colors, which are all more prominent than the first yellow-ish color. For example, even setting n_dom_colors = 20 won't show you the first yellow-ish color.

How to achieve, what you might have in mind? Have a look at Image.quantize, which will give you a mode P image using color quantization:

from PIL import Image

img = Image.open('47ckF.jpg')
img = img.quantize(colors=4, kmeans=4).convert('RGB')
n_dom_colors = 4
dom_colors = sorted(img.getcolors(2 ** 24), reverse=True)[:n_dom_colors]
print(dom_colors)
# [(139872, (1, 0, 1)), (138350, (0, 0, 253)), (134957, (252, 1, 1)), (3321, (253, 240, 12))]

You get nearly solid black, nearly solid blue, nearly solid red, and a yellow variant!

For reference, the quantized and re-converted image:

Quantized

----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.16299-SP0
Python:        3.9.1
PyCharm:       2021.1.1
Pillow:        8.2.0
----------------------------------------

Upvotes: 4

Related Questions