K-means algorithm
Assume that we have to following image:
We can represent the image in 3D-space as follows:
Suppose that we want to find eight dominant colors in the image. We could create a histogram and take the eight most common values. This would be fine, but often, very similar colors would repeat. Instead, we can try to find eight centroids in the image and find clusters of points beloning to each centroid (say, a point belongs to a centroid
if
for some radius
). All points not belonging to a centroid will be penalized using some heuristic. This is basically the K-means clustering algorithm.
Usage
The algorithm can be used to generate set of dominant colors to be used for instance in the terminal. Running the above algorithm on the image, we get
or as list
['#321331', '#e1a070', '#621d39', '#e05a40', '#9ed8aa', '#8b3860', '#f7d188', '#ab3031']
with some minor adjustment
{ "color": [ "#ab3031","#621d39","#e05a40","#e1a070","#9ed8aa","#521f50","#8b3860","#f7d188","#ab5a5b","#623b4b","#e08c7b","#e1b99c","#c4d8c8","#715171","#8b5770","#f7e4c0" ], "foreground": "#c5c8c6", "background": "#282a2e" }
In iTerm, it might look like this
This can be achieved using the following code:
#!/usr/bin/env python import sys, os from sklearn.cluster import KMeans from PIL import Image nbrcentroids = 10 # Constant increase in colors beta = 10 # Multplicative factor in background gamma = 0.4 rgb2hex = lambda rgb: '#%s' % ''.join(('%02x' % min(p + beta, 255) for p in rgb)) darken = lambda rgb : (p * gamma for p in rgb) def getcentroids(filename, n=8): img = Image.open(filename) img.thumbnail((100, 100)) # Run K-means algorithm on image kmeans = KMeans(init='k-means++', n_clusters=n) kmeans.fit(list(img.getdata())) # Get centroids from solution rgbs = [map(int, c) for c in kmeans.cluster_centers_] return rgbs def set_colors_gnome(centroids): centroids = sorted(centroids, key=lambda rgb: sum(c**2 for c in rgb)) prefix = 'gsettings set org.pantheon.terminal.settings ' # Set background and foreground os.system(prefix + 'background \"%s\"' % rgb2hex(darken(centroids[0]))) os.system(prefix + 'foreground \"%s\"' % rgb2hex(centroids[-1])) # Set ANSI colors colors = ':'.join(rgb2hex(centroid) for centroid in centroids[1:-1]) os.system(prefix + 'palette \"' + colors + ':' + colors + '\"') def bar(mode): write = sys.stdout.write for i in range(0, nbrcentroids): write('\033[0;3%dmBAR ' % i) write('\n') centroids = getcentroids(sys.argv[1], n=nbrcentroids) set_colors_gnome(centroids) bar(0);
It is on Github too!