Reputation: 59
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:
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()
What am I doing wrong here?
Upvotes: 0
Views: 1781
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
orADAPTIVE
.
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:
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
PyCharm: 2021.1.1
Pillow: 8.2.0
----------------------------------------
Upvotes: 4