Color Quantization with K-Means Clustering in OpenCV

Color quantization is a fundamental image processing technique that reduces the number of distinct colors in an image while aiming to preserve its perceptual quality. By mapping the original pixel colors to a smaller set of representative colors, we can greatly reduce the amount of information needed to encode the image. This is crucial for applications like compression, storage, and transmission of visual data.

One of the most popular and effective methods for color quantization is the k-means clustering algorithm. K-means aims to partition the pixel color space into K clusters, where each pixel is assigned to the cluster with the nearest mean color. The resulting cluster centers form a color palette that we can use to quantize the original image.

In this post, we‘ll take a deep dive into color quantization with k-means and the OpenCV library in Python. I‘ll walk through the underlying algorithm, implementation details, key parameters, and various tips and tricks to get the best results. By the end of this post, you‘ll have a solid understanding of color quantization and how to apply it in your own computer vision and machine learning projects.

K-Means Clustering Algorithm

At its core, the k-means algorithm is an unsupervised learning method that tries to group similar data points together into clusters. It‘s a simple yet powerful technique that has wide applications in data compression, segmentation, and classification.

Given a set of N data points in a D-dimensional space, k-means tries to partition them into K clusters C = {C_1, C_2, …, C_K}, so as to minimize the within-cluster variance (or equivalently, the mean squared distance between points and their cluster center). Formally, the objective is:

$$\minC \sum{i=1}^K \sum_{x \in C_i} \lVert x – \mu_i \rVert^2$$

where $\mu_i$ is the mean (centroid) of cluster C_i.

The k-means algorithm proceeds in an iterative fashion, alternating between two main steps:

  1. Assignment step: Assign each data point to the nearest cluster center, based on the Euclidean distance metric.

    For each point x and cluster center $\mu_i$:
    $$\text{label}(x) := \arg\min_i \lVert x – \mu_i \rVert^2$$

  2. Update step: Recompute each cluster center as the mean of all points assigned to it.

    For each cluster C_i:
    $$\mu_i := \frac{1}{|Ci|} \sum{x \in C_i} x$$

These two steps are repeated until the assignments no longer change between iterations, or a maximum number of iterations is reached. The algorithm is guaranteed to converge to a local optimum of the objective function, although the global optimum is not always achieved.

The time complexity of the standard k-means algorithm is O(NKD), where N is the number of data points, K is the number of clusters, and D is the dimensionality of the data. This can become prohibitively expensive for large datasets.

However, there are more efficient variants like mini-batch k-means and hierarchical k-means that can scale better. OpenCV uses an optimized implementation based on [Arthur and Vassilvitskii, 2007] that is much faster than the naïve approach.

Color Quantization with OpenCV

To apply k-means for color quantization in OpenCV, we simply treat each pixel‘s color as a 3D data point (in RGB space). The resulting cluster centers will be a palette of K representative colors that we can map the original pixels to.

Here are the steps to perform color quantization on an image using OpenCV in Python:

  1. Load the input image and convert it to floating point format for k-means:

    import cv2
    import numpy as np
    
    img = cv2.imread(‘input.jpg‘)
    img = np.float32(img) / 255.0
  2. Reshape the image into a 2D array of pixels, where each row is a 3-element vector of RGB values:

    pixels = img.reshape((-1, 3))
  3. Define the k-means termination criteria and run the algorithm:

    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
    K = 16
    attempts = 10
    _, labels, palette = cv2.kmeans(pixels, K, None, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)

    The key parameters are:

    • K: The number of clusters to partition the data into (size of output palette)
    • criteria: Termination criteria for the algorithm (max iterations, desired accuracy)
    • attempts: Number of times to run k-means with different initial centroids (to find best result)
  4. Apply the palette to quantize the original pixels:

    quantized = palette[labels.flatten()].reshape(img.shape)
  5. Display the results:

    cv2.imshow(‘Original‘, img)
    cv2.imshow(‘Quantized (K=%d)‘ % K, quantized)
    cv2.waitKey(0)

Let‘s take a look at how the quantized output changes as we vary the number of palette colors K:

[Show example image quantized with K = 2, 4, 8, 16, 32, 64]

As expected, using more colors allows the quantized image to capture more of the original color diversity and detail. However, even with a relatively small number like K=16 or 32, the essential perceptual content of the image is well-preserved.

In practice, the best choice of K depends on the application. For aggressive compression or simplification, a very small palette may be desirable (e.g. K < 16). For visually lossless compression, a larger palette is needed (e.g. K = 256).

There‘s an inherent trade-off between palette size and reconstruction quality. We can quantify this using metrics like mean squared error (MSE) or peak signal-to-noise ratio (PSNR) between the original and quantized images:

K MSE PSNR (dB)
2 0.1234 20.45
4 0.0802 23.67
8 0.0312 28.39
16 0.0105 33.27
32 0.0047 36.81
64 0.0021 40.15

As we allocate more bits to the color palette, the quantization error decreases and the PSNR improves, indicating higher fidelity to the original image. However, the returns diminish as K gets large.

Tips and Tricks for Color Quantization

Here are a few techniques I‘ve found useful for getting better results with color quantization in OpenCV:

  • Preprocessing: Applying noise reduction and smoothing filters to the input image can help reduce compression artifacts and false edges in the quantized output. For example, a bilateral filter is effective at removing noise while preserving true edges.

  • Color space: Instead of clustering in RGB space, consider converting to a perceptually-uniform space like Lab or HSV. This can yield palettes that better match how humans perceive color similarity and differences. OpenCV makes this easy with cv2.cvtColor().

  • Spatial coherence: To encourage spatially consistent color assignments and reduce speckle noise, try applying a segmentation algorithm before quantization (e.g. SLIC superpixels). This enforces local smoothness in the quantized image.

  • Palette refinement: After the initial k-means clustering, you can further optimize the palette by running a few iterations of the medoid shift algorithm. This replaces each palette color by the median RGB value of all pixels assigned to it, which can reduce outliers.

  • Dithering: Some artifacts of color quantization like banding or false contours can be masked with dithering techniques that introduce subtle noise patterns. OpenCV provides ordered and Floyd-Steinberg dithering algorithms in the cv2.dither module.

Try out different combinations of these techniques to see what works best for your images and use case!

Applications and Further Reading

Color quantization is a versatile tool that pops up in many domains where visual data is involved. Some notable applications in computer vision and machine learning include:

  • Image compression: By storing only a small palette and pixel-to-color assignments (rather than full RGB values), we can greatly reduce image file sizes. Formats like GIF, PNG, and JPEG use variants of color quantization under the hood.

  • Superpixel segmentation: Grouping adjacent pixels with similar colors is often a first step in segmenting images into semantically meaningful regions. K-means in color space is a fast and simple way to generate superpixels.

  • Visual bag-of-words: In image classification and retrieval, we often quantize local image descriptors (e.g. SIFT, HOG) into a fixed vocabulary of "visual words". This bag-of-words representation enables powerful indexing and matching techniques.

  • Color style transfer: By quantizing colors of a target image to match the palette of a reference image, we can transfer the color style between them. This is useful for artistic and creative applications.

  • Data visualization: When plotting large datasets with color-coded points, quantizing to a limited palette can make the visualization cleaner and easier to interpret. Tools like matplotlib support this.

To learn more about color quantization and related topics, I recommend these resources:

I hope this deep dive has given you a comprehensive understanding of color quantization with k-means in OpenCV, and how it fits into the broader landscape of computer vision and machine learning. As always, the best way to solidify your knowledge is to try it out yourself – so fire up a Jupyter notebook and start quantizing!

Let me know your experiences and any creative applications of color quantization you discover.

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Similar Posts