Python - Find dominant/most common color in an image

Viewed 91171

I'm looking for a way to find the most dominant color/tone in an image using python. Either the average shade or the most common out of RGB will do. I've looked at the Python Imaging library, and could not find anything relating to what I was looking for in their manual, and also briefly at VTK.

I did however find a PHP script which does what I need, here (login required to download). The script seems to resize the image to 150*150, to bring out the dominant colors. However, after that, I am fairly lost. I did consider writing something that would resize the image to a small size then check every other pixel or so for it's image, though I imagine this would be very inefficient (though implementing this idea as a C python module might be an idea).

However, after all of that, I am still stumped. So I turn to you, SO. Is there an easy, efficient way to find the dominant color in an image.

11 Answers

Try Color-thief. It is based on Pillow and works awesome.

Installation

pip install colorthief

Usage

from colorthief import ColorThief
color_thief = ColorThief('/path/to/imagefile')
# get the dominant color
dominant_color = color_thief.get_color(quality=1)

It can also find color pallete

palette = color_thief.get_palette(color_count=6)

You can do this in many different ways. And you don't really need scipy and k-means since internally Pillow already does that for you when you either resize the image or reduce the image to a certain pallete.

Solution 1: resize image down to 1 pixel.

def get_dominant_color(pil_img):
    img = pil_img.copy()
    img = img.convert("RGBA")
    img = img.resize((1, 1), resample=0)
    dominant_color = img.getpixel((0, 0))
    return dominant_color

Solution 2: reduce image colors to a pallete

def get_dominant_color(pil_img, palette_size=16):
    # Resize image to speed up processing
    img = pil_img.copy()
    img.thumbnail((100, 100))

    # Reduce colors (uses k-means internally)
    paletted = img.convert('P', palette=Image.ADAPTIVE, colors=palette_size)

    # Find the color that occurs most often
    palette = paletted.getpalette()
    color_counts = sorted(paletted.getcolors(), reverse=True)
    palette_index = color_counts[0][1]
    dominant_color = palette[palette_index*3:palette_index*3+3]

    return dominant_color

Both solutions give similar results. The latter solution gives you probably more accuracy since we keep the aspect ratio when resizing the image. Also you get more control since you can tweak the pallete_size.

It's not necessary to use k-means to find the dominant color as Peter suggests. This overcomplicates a simple problem. You're also restricting yourself by the amount of clusters you select so basically you need an idea of what you're looking at.

As you mentioned and as suggested by zvone, a quick solution to find the most common/dominant color is by using the Pillow library. We just need to sort the pixels by their count number.

from PIL import Image

    def find_dominant_color(filename):
        #Resizing parameters
        width, height = 150,150
        image = Image.open(filename)
        image = image.resize((width, height),resample = 0)
        #Get colors from image object
        pixels = image.getcolors(width * height)
        #Sort them by count number(first element of tuple)
        sorted_pixels = sorted(pixels, key=lambda t: t[0])
        #Get the most frequent color
        dominant_color = sorted_pixels[-1][1]
        return dominant_color

The only problem is that the method getcolors() returns None when the amount of colors is more than 256. You can deal with it by resizing the original image.

In all, it might not be the most precise solution but it gets the job done.

My solution

Here's my adaptation based on Peter Hansen's solution.

import scipy.cluster
import sklearn.cluster
import numpy
from PIL import Image

def dominant_colors(image):  # PIL image input

    image = image.resize((150, 150))      # optional, to reduce time
    ar = numpy.asarray(image)
    shape = ar.shape
    ar = ar.reshape(numpy.product(shape[:2]), shape[2]).astype(float)

    kmeans = sklearn.cluster.MiniBatchKMeans(
        n_clusters=10,
        init="k-means++",
        max_iter=20,
        random_state=1000
    ).fit(ar)
    codes = kmeans.cluster_centers_

    vecs, _dist = scipy.cluster.vq.vq(ar, codes)         # assign codes
    counts, _bins = numpy.histogram(vecs, len(codes))    # count occurrences

    colors = []
    for index in numpy.argsort(counts)[::-1]:
        colors.append(tuple([int(code) for code in codes[index]]))
    return colors                    # returns colors in order of dominance

What are the differences/improvements?

It's (subjectively) more accurate

It's using the kmeans++ to pick initial cluster centers which gives better results. (kmeans++ may not be the fastest way to pick cluster centers though)

It's faster

Using sklearn.cluster.MiniBatchKMeans is significantly faster and gives very similar colors to the default KMeans algorithm. You can always try the slower sklearn.cluster.KMeans and compare the results and decide whether the tradeoff is worth it.

It's deterministic

I am using a random_state to get consistent ouput (I believe the original scipy.cluster.vq.kmeans also has a seed parameter). Before adding a random state I found that certain inputs could have significantly different outputs.

Benchmarks

I decided to very crudely benchmark a few solutions.

Method Time (100 iterations)
Peter Hansen (kmeans) 58.85
Artem Bernatskyi (Color Thief) 61.29
Artem Bernatskyi (Color Thief palette) 15.69
Pithikos (PIL resize) 0.11
Pithikos (palette) 1.68
Mine (mini batch kmeans) 6.31

To add to Peter's answer, if PIL is giving you an image with mode "P" or pretty much any mode that isn't "RGBA", then you need to apply an alpha mask to convert it to RGBA. You can do that pretty easily with:

if im.mode == 'P':
    im.putalpha(0)

This is a complete script with a function compute_average_image_color().

Just copy and past it, and change the path of your image.

My image is img_path='./dir/image001.png'

#AVERANGE COLOR, MIN, MAX, STANDARD DEVIATION
#SELECT ONLY NOT TRANSPARENT COLOR


from PIL import Image
import sys
import os
import os.path
from os import path
import numpy as np
import math 



def compute_average_image_color(img_path):

    if not os.path.isfile(img_path):
        print(path_inp_image, 'DONT EXISTS, EXIT')
        sys.exit()

    
    #load image
    img = Image.open(img_path).convert('RGBA')
    img = img.resize((50,50))  # Small optimization


    #DEFINE SOME VARIABLES
    width, height = img.size
    r_total = 0
    g_total = 0
    b_total = 0
    count = 0
    red_list=[]
    green_list=[]
    blue_list=[]
    
    
    #READ AND CHECK PIXEL BY PIXEL
    for x in range(0, width):
        for y in range(0, height):
            r, g, b, alpha = img.getpixel((x,y))
            
            if alpha !=0:
                red_list.append(r)
                green_list.append(g)
                blue_list.append(b)
            
                r_total += r
                g_total += g
                b_total += b
                count += 1

            
    #CALCULATE THE AVRANGE COLOR, MIN, MAX, ETC             
    average_color=(round(r_total/count), round(g_total/count), round(b_total/count))
    print(average_color)
    
    red_list.sort()
    green_list.sort()
    blue_list.sort()

    
    red_min_max=[]
    green_min_max=[]
    blue_min_max=[]


    
    
    red_min_max.append(min(red_list))
    red_min_max.append(max(red_list))
    green_min_max.append(min(green_list))
    green_min_max.append(max(red_list))
    blue_min_max.append(min(blue_list))
    blue_min_max.append(max(blue_list))
    
    print('red_min_max: ', red_min_max)
    print('green_min_max: ', green_min_max)
    print('blue_min_max: ', blue_min_max)



    #variance and standard devietion
    red_stddev=round(math.sqrt(np.var(red_list)))
    green_stddev=round(math.sqrt(np.var(green_list)))
    blue_stddev=round(math.sqrt(np.var(blue_list)))

    print('red_stddev: ', red_stddev)
    print('green_stddev: ', green_stddev)
    print('blue_stddev: ', blue_stddev)






img_path='./dir/image001.png'
compute_average_image_color(img_path)





Related