HSV Dominant ColorsThe purpose of this article is to show how to find dominant colors in an image by clustering its colors in the HSV space.
AcknowledgementsThis post has been inspired by the excellent work of Charles Leifer you can read here.
ToolkitBeside 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 SpaceOne 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 HistogramsLet's load an image and plot its HSV Histograms.
target_image = 'images/lenna.png' img = cv2.imread(target_image) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.imshow(img) plt.show()
We can convert the image by using
cv2.cvtColor()with the flag
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.subplots_adjust(hspace=.5) 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()
ClusteringThe 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 img_width = new_image.shape # for each cluster for i,c in enumerate(color_rank[::-1]): # gets the weight of the cluster weight = c # 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, color, color] # 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 RGBI would like to compare the results of this HSV implementation with RGB clustering proposed in artilce.
Test 1Consider the following image
The RGB dominant colors result
Clusterization took 3.60024 seconds
Test 2Consider the following image
Clusterization took 1.86138 seconds