Using python and k-means to find dominant colors in images
charlesleifer.com
charlesleifer.com
See the effect here: http://cl.ly/KElb
Here's the Python I use to do it: https://github.com/samuelclay/NewsBlur/blob/master/utils/Ima...
from PIL import Image
import scipy
import scipy.cluster
from pprint import pprint
image = Image.open('logo.png')
NUM_CLUSTERS = 5
# Convert image into array of values for each point.
ar = scipy.misc.fromimage(image)
shape = ar.shape
# Reshape array of values to merge color bands.
if len(shape) > 2:
ar = ar.reshape(scipy.product(shape[:2]), shape[2])
# Get NUM_CLUSTERS worth of centroids.
codes, _ = scipy.cluster.vq.kmeans(ar, NUM_CLUSTERS)
# Pare centroids, removing blacks and whites and shades of really dark and really light.
original_codes = codes
for low, hi in [(60, 200), (35, 230), (10, 250)]:
codes = scipy.array([code for code in codes
if not ((code[0] < low and code[1] < low and code[2] < low) or
(code[0] > hi and code[1] > hi and code[2] > hi))])
if not len(codes): codes = original_codes
else: break
# Assign codes (vector quantization). Each vector is compared to the centroids
# and assigned the nearest one.
vecs, _ = scipy.cluster.vq.vq(ar, codes)
# Count occurences of each clustered vector.
counts, bins = scipy.histogram(vecs, len(codes))
# Show colors for each code in its hex value.
colors = [''.join(chr(c) for c in code).encode('hex') for code in codes]
total = scipy.sum(counts)
color_dist = dict(zip(colors, [count/float(total) for count in counts]))
pprint(color_dist)
# Find the most frequent color, based on the counts.
index_max = scipy.argmax(counts)
peak = codes[index_max]
color = ''.join(chr(c) for c in peak).encode('hex')k-means is not a mode seeking algorithm, I think. You are clustering your color space, but you're not even guaranteed to end up with colors that are very close to those in your image. With a high k you're getting actual colors in the image, but they're not really dominant anymore.
What about mean shift? It's based on one method of non-parametric density estimation. Another method is this: Some sort of histogramming, which is another method of non-parametric density estimation.
Also other colors spaces will pay off immensely.
I took a look at it a few weeks ago based on a recommendation here on HN and have been enjoying it ever since.
I'm in the process of trying to extend some very basic programming skills and refresh / extend equally basic math.
I started reading expecting something that would assume enough to fly right over in my head. Instead, I found a comfortable pace and very "practical" examples.
Every few pages is a delightful "Ohhhh...So that's how that works." demonstration of things that I've always wondered and theorized about, but never understood the mechanics of.
It's a great book, one that I expect will end up being pretty significant to me.
I like that the code is in python, which I find easier to read than the java examples in Algorithms of the Intelligent Web. Being a perl guy (and a perl shop), python feels pretty similar and it's easy to port anything I need. If the rest of the book is as good as the clustering chapter, it's going to be a fun treat for my brain!
Color theme generator for Xresources and more: https://gist.github.com/3946121
Reference to similar features in scipy: http://docs.scipy.org/doc/scipy/reference/cluster.vq.html
Old-school computer graphics w/kmeans: http://pragprog.com/magazines/2011-12/revisiting-graphics-ha...
There was also some discussion on using different color spaces than RGB to get better results, lab*, HSL, YUV, etc. And recommending using something like Numpy to make this shit hum.
http://en.wikipedia.org/wiki/Color_difference
http://stackoverflow.com/questions/1313/followup-finding-an-...
http://tylerneylon.com/a/imghist/ (and source) https://github.com/tylerneylon/imghist/blob/master/imghist.p...
That also includes color histograms.
Thoughts on actually using this:
* If a human is using the color output, it's fun to weigh the colors by cluster sizes. * To find human-perspective dominant colors, it helps to throw out very-light/dark pixels. * Dribbble does a very good job of this - look at any of their individual photo pages. * For production use, C would be much more appropriate at solving this problem (but python is more fun to use).
Another cool application of color analysis like this:
http://www.vijayp.ca/blog/2012/06/colours-in-movie-posters-s...
To expand on your points, based on my own experience:
* Rather than using the centroid to represent the cluster, pick a representative peak from the hue histogram. This tends to make things less muddy.
* Not only do I throw away extreme S/L values, I aggressively weigh everything by S * (0.5 - |0.5 - L|), so bright, saturated colours dominate. Black and white are usually very thin slivers, and I almost never have grey.
* As a last step, I convert to Lab space and merge any colours that are perceptually similar (dE < 5.0). This means that large gradients (like an unevenly lit surface) only occupy one sliver of the pie. Unfortunately, in my current implementation this conflicts with my first point above, so the colours I end up with aren't always in the original image -- something I need to fix.
Here's my result for one of your images: https://dl.dropbox.com/s/azl2bag84riugg8/imghist.png, notice how the orange has a disproportionally large weight because it is so saturated.
1. You determine the 'dominant' colours to be the centroids of your clusters. The centroid is the mean of the points within the cluster, this mean is not necessarily a colour that is in your image. If you, for example, take a picture divided into four different solid coloured squares, and use this to find the 3 dominant colours it will average 2 (or more) colours. (The same might happen for more complex images with a a lot of contrast).
2. When randomly initializing k-means there is a good chance you'll find one of the local optima, so running it more than once will return different colours. In general it is good practice to run it several times and choose the outcome with the lowest cost.
3. K-means can take a long time to converge; limit the amount of iterations it can do.
These things aside, very cool usage of k-means on image data!
Your next assignment: find similarly coloured photos using the right data structure. See my stack overflow ticket http://stackoverflow.com/questions/10555511/need-a-proper-da...
It’s not super performant, but the end result is awesome — after a few seconds, you and up with a picture what your webcam sees minus the moving things. For the curious:
http://sidnicious.github.com/longcamera/k-means/longcamera.h...
(I haven’t touched the code in a while, save for fixing a bug just now)
The solution to this is to convert your RGB colors to the CIELab color space (http://en.wikipedia.org/wiki/Lab_color_space) and use the Lab values for your 3-dimensional space.
One problem I ran into but never solved was ignoring the background color. For example in the second picture it might be more interesting to bring out the oranges of the tail-lights and the light-blues of the street lights, rather than just the dark blues of the roads. You could ignore the largest cluster, but that's not always necessarily the background color. Have you thought about this at all?
Either that, or be sure that the background is consistent every time you take the picture for sorting into the color bins. It may be cheaper than trying to create a catch-all solution.