Reduce the number of Colors of an image using Uniform Quantization

Reducing the number of colors in an image is also called Color quantization. It’s commonly used for generating GIF images which currently supports only 256 colors. The general idea is, group similar colors in an image into regions, replace them with the color which closely resembles or represents the region. This color is also called the representative color.
 
Lets understand the simplest color reduction algorithm called the Uniform Quantization.

Algorithm

A single color pixel represented by an RGB tuple can take values between 0-255 each. The steps involved in uniform quantization are:
 
  1. Find 3 color regions by dividing the color spaces R,G,B into equal parts.
  2. For each pixel, assign its R, G and B components to the corresponding color region.
  3. Find the color that represents the region by taking the average of all colors in it.
  4. Go through the pixels in the image and replace the color with the representative color of the region.
For example, the color space red is divided into regions R(0-31), R(32-63), R(64-95), R(96-127), R(128-159), R(160-191), R(192-223), R(224-255). If a color C is (3,34,189), it will go into region R(0-31), G(32-63) and B(160-191). Now if the average of all the Red colors falling in region R(0-31) is 16, Green G(32-63) is 48 and Blue B(160-191) is 178, then the color  C = (3,34,189) will become C = (16, 48, 178) after quantization.

Implementation

import matplotlib.pyplot as plt
import numpy as np
from skimage.io import imread, imsave

car = imread('resources/auto.jpg')

def get_region_index(color_value):
    # colors divided into 8 regions for each color space
    eight_regions = [[0,31], [32,63], [64,95], [96,127], [128,159], [160,191], [192,223], [224,255]]
    for index, region_value in enumerate(eight_regions):
        if color_value >= region_value[0] and  color_value <= region_value[1]:
            return index
        
r_region_mappings = [[],[],[],[],[],[],[],[]]
g_region_mappings = [[],[],[],[],[],[],[],[]]
b_region_mappings = [[],[],[],[],[],[],[],[]]    

# loop through all pixels and the put the colors into the respective color regions
for rows in car:
    for pixel in rows:
        red = pixel[0]
        green = pixel[1]
        blue = pixel[2]
        #find the index where the color is supposed to go and add it
        r_region_mappings[get_region_index(red)].append(red)
        g_region_mappings[get_region_index(green)].append(green)
        b_region_mappings[get_region_index(blue)].append(blue)
        
# find the color that represents each region
r_representative_color_per_region = [0,0,0,0,0,0,0,0]
g_representative_color_per_region = [0,0,0,0,0,0,0,0]
b_representative_color_per_region = [0,0,0,0,0,0,0,0]

# find the average of all colors in the regions to find the representative color
for index in range(8):
    r_representative_color_per_region[index] = np.mean(r_region_mappings[index]).astype(int)
    g_representative_color_per_region[index] = np.mean(g_region_mappings[index]).astype(int)
    b_representative_color_per_region[index] = np.mean(b_region_mappings[index]).astype(int)

# now replace all colors in the image with their uniform quantized representative colors
new_car = np.copy(car)
for rindex, rows in enumerate(car):
    for cindex, pixel in enumerate(rows):
        red = pixel[0]
        green = pixel[1]
        blue = pixel[2]
        new_car[rindex, cindex][0] = r_representative_color_per_region[get_region_index(red)]
        new_car[rindex, cindex][1] = g_representative_color_per_region[get_region_index(green)]
        new_car[rindex, cindex][2] = b_representative_color_per_region[get_region_index(blue)]


imsave('uniform_quantized.jpg', new_car)

Jupyter Notebook

You can understand the entire process from my notebook attached below.

Below image shows my sample image reduced to 512 color palette.

original non quantizedOriginal uniform quantized image512 Colors

Bonus:

You can also divide the regions into 4*4*4 and get 64 color palette or 8*8*4 and get a 256 color palette.