Color quantization using k-means clustering

In this subsection, we will apply the k-means clustering algorithm to the problem of color quantization, which can be defined as the process of reducing the number of colors in an image. Color quantization is a critical point for displaying images on certain devices that can only display a limited number of colors (commonly due to memory restrictions). Therefore, a trade-off between the similarity and the reduction in the number of colors is usually necessary. This trade-off is established by setting the K parameter properly, as we will see in the next examples. 

In the k_means_color_quantization.py script, we perform the k-means clustering algorithm to perform color quantization. In this case, each element of the data is composed of 3 features, which correspond to the BG, and R values for each of the pixels of the image. Therefore, the key step is to transform the image into data this way:

data = np.float32(image).reshape((-1, 3))

Here, image is the image we previously loaded.

In this script, we performed the clustering procedure using several values of K (351020, and 40) in order to see how the resulting image changes. For example, if we want the resulting image with only 3 colors (K = 3), we must perform the following:

  1. Load the BGR image:
img = cv2.imread('landscape_1.jpg')
  1. Perform color quantization using the color_quantization() function:
color_3 = color_quantization(img, 3)
  1. Show both images in order to see the results. The color_quantization() function performs the color quantization procedure:
def color_quantization(image, k):
"""Performs color quantization using K-means clustering algorithm"""

# Transform image into 'data':
data = np.float32(image).reshape((-1, 3))
# print(data.shape)

# Define the algorithm termination criteria (maximum number of iterations and/or required accuracy):
# In this case the maximum number of iterations is set to 20 and epsilon = 1.0
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

# Apply K-means clustering algorithm:
ret, label, center = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

# At this point we can make the image with k colors
# Convert center to uint8:
center = np.uint8(center)
# Replace pixel values with their center value:
result = center[label.flatten()]
result = result.reshape(img.shape)
return result

In the previous function, the key point is to make use of cv2.kmeans() method.  Finally, we can build the image with k colors replacing each pixel value with their corresponding center value.  The output of this script can be seen in the next screenshot:

The previous script can be extended to include an interesting functionality, which shows the number of pixels assigned to each center value. This can be seen in the k_means_color_quantization_distribution.py script.

The color_quantization() function has been modified to include this functionality:

def color_quantization(image, k):
"""Performs color quantization using K-means clustering algorithm"""

# Transform image into 'data':
data = np.float32(image).reshape((-1, 3))
# print(data.shape)

# Define the algorithm termination criteria (the maximum number of iterations and/or the desired accuracy):
# In this case the maximum number of iterations is set to 20 and epsilon = 1.0
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

# Apply K-means clustering algorithm:
ret, label, center = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

# At this point we can make the image with k colors
# Convert center to uint8:
center = np.uint8(center)
# Replace pixel values with their center value:
result = center[label.flatten()]
result = result.reshape(img.shape)

# Build the 'color_distribution' legend.
# We will use the number of pixels assigned to each center value:
counter = collections.Counter(label.flatten())
print(counter)

# Calculate the total number of pixels of the input image:
total = img.shape[0] * img.shape[1]

# Assign width and height to the color_distribution image:
desired_width = img.shape[1]
# The difference between 'desired_height' and 'desired_height_colors'
# will be the separation between the images
desired_height = 70
desired_height_colors = 50

# Initialize the color_distribution image:
color_distribution = np.ones((desired_height, desired_width, 3), dtype="uint8") * 255
# Initialize start:
start = 0

for key, value in counter.items():
# Calculate the normalized value:
value_normalized = value / total * desired_width

# Move end to the right position:
end = start + value_normalized

# Draw rectangle corresponding to the current color:
cv2.rectangle(color_distribution, (int(start), 0), (int(end), desired_height_colors), center[key].tolist(), -1)
# Update start:
start = end

return np.vstack((color_distribution, result))

As you can see, we make use of collections.Counter() to count the number of pixels assigned to each center value:

counter = collections.Counter(label.flatten())

For example, if K = 3Counter({0: 175300, 2: 114788, 1: 109912}). Once the color-distribution image has been built, the final step is to concatenate both images:

np.vstack((color_distribution, result))

The output of this script can be seen in the next screenshot:

In the previous screenshot you can see the result of applying color quantization using k-means clustering algorithm varying the parameter k (351020, and 40). A bigger value of k means a more realistic image.