# HSV Dominant Colors

The purpose of this article is to show how to find dominant colors in an image by clustering its colors in the HSV space.

## Acknowledgements

This post has been inspired by the excellent work of Charles Leifer you can read here.

## Toolkit

Beside the usual tools such as OpenCV, matplotlib and numpy, this tutorial is going to make use of Scipy's kMeans implementation in order to clusterize the image pixels.
```%matplotlib inline
import cv2
from scipy.cluster.vq import vq, kmeans
from matplotlib import pyplot as plt
import numpy as np
import time as t
```

## Hue, Saturation and Value Color Space

One of the most common spaces in which the colors of an image are represented is RGB, a three dimensional cubic discrete space whose principal axes are Red, Green and Blue amount of color. This is the most reasonable way of representing colors for a computer, but unfortunately it does not preserve the way humans perceive colors. On the other hand, HSV color space is a half-cone in which the perceptive distance is preserved. An intuition is given in the following figure, while a comprehensive description can be found on Wikipedia.

## HSV Histograms

Let's load an image and plot its HSV Histograms.
```target_image = 'images/lenna.png'
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.show()
```
We can convert the image by using `cv2.cvtColor()` with the flag `cv2.COLOR_RGB2HSV`
```hsv_image = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
```
We can plot the histograms by selecting one column at the time from the image matrix representation.
```hue, sat, val = hsv_image[:,:,0], hsv_image[:,:,1], hsv_image[:,:,2]
```
We set up the figure to be 10x8 inches, with three plots inside by calling the method` plt.subplot(311) `. The code 311 stands for "make a 3 rows, 1 column figure; plot in the first cell of the figure".
```plt.figure(figsize=(10,8))
plt.subplot(311)                             #plot in the first cell
plt.title("Hue")
plt.hist(np.ndarray.flatten(hue), bins=180)
plt.subplot(312)                             #plot in the second cell
plt.title("Saturation")
plt.hist(np.ndarray.flatten(sat), bins=128)
plt.subplot(313)                             #plot in the third cell
plt.title("Luminosity Value")
plt.hist(np.ndarray.flatten(val), bins=128)
plt.show()
```

## Clustering

The following procedure performs the clustering operation via kmeans and returns an image representing the found clusters. For further detail, please refer to comments in the body of the function.
```def do_cluster(hsv_image, K, channels):
# gets height, width and the number of channes from the image shape
h,w,c = hsv_image.shape
# prepares data for clustering by reshaping the image matrix into a (h*w) x c matrix of pixels
cluster_data = hsv_image.reshape( (h*w,c) )
# grabs the initial time
t0 = t.time()
# performs clustering
codebook, distortion = kmeans(np.array(cluster_data[:,0:channels], dtype=np.float), K)
# takes the final time
t1 = t.time()
print "Clusterization took %0.5f seconds" % (t1-t0)

# calculates the total amount of pixels
tot_pixels = h*w
# generates clusters
data, dist = vq(cluster_data[:,0:channels], codebook)
# calculates the number of elements for each cluster
weights = [len(data[data == i]) for i in range(0,K)]

# creates a 4 column matrix in which the first element is the weight and the other three
# represent the h, s and v values for each cluster
color_rank = np.column_stack((weights, codebook))
# sorts by cluster weight
color_rank = color_rank[np.argsort(color_rank[:,0])]

# creates a new blank image
new_image =  np.array([0,0,255], dtype=np.uint8) * np.ones( (500, 500, 3), dtype=np.uint8)
img_height = new_image.shape[0]
img_width  = new_image.shape[1]

# for each cluster
for i,c in enumerate(color_rank[::-1]):

# gets the weight of the cluster
weight = c[0]

# calculates the height and width of the bins
height = int(weight/float(tot_pixels) *img_height )
width = img_width/len(color_rank)

# calculates the position of the bin
x_pos = i*width

# defines a color so that if less than three channels have been used
# for clustering, the color has average saturation and luminosity value
color = np.array( [0,128,200], dtype=np.uint8)

# substitutes the known HSV components in the default color
for j in range(len(c[1:])):
color[j] = c[j+1]

# draws the bin to the image
new_image[ img_height-height:img_height, x_pos:x_pos+width] = [color[0], color[1], color[2]]

# returns the cluster representation
return new_image
```
```# creates a new figure size 12x10 inches
plt.figure(figsize=(12,10))
# creates a 4-column subplot
plt.subplot(141)
# in the first cell draws the target image
plt.imshow(img)

# calculates clusters for
# * h
# * h and s
# * h, s and v

for i in range(1,4):
plt.subplot(141 + i)
plt.title("Channels: %i" % i)
new_image = do_cluster(hsv_image, 5, i)
new_image = cv2.cvtColor(new_image, cv2.COLOR_HSV2RGB)
plt.imshow(new_image)

plt.show()
```
```Clusterization took 0.76545 seconds
Clusterization took 4.80713 seconds
Clusterization took 8.77994 seconds
```

## Comparison with RGB

I would like to compare the results of this HSV implementation with RGB clustering proposed in artilce.

### Test 1

Consider the following image

The RGB dominant colors result

While in HSV the dominant colors result
```Clusterization took 3.60024 seconds
```

### Test 2

Consider the following image

The RGB dominant colors result

While in HSV the dominant colors result
```Clusterization took 1.86138 seconds
```

## Final Thoughts

There is a very non-surprisingly slight difference from the chromatic point of view but what makes my mind grind, on the other hand, is the different distribution of the colors in the clusters. Any ideas? Feel free to share your thoughts in the comment box below. Thank you for reading this article. Don't forget to take a look the RGB implementation by Charles Leifer