Home > Blockchain >  Accurate color quantization of image to minimize color palette
Accurate color quantization of image to minimize color palette

Time:09-18

I'm trying to quantize an image keeping all primary colors in place and removing all minor colors such as "anti-aliasing" borders. E.g. the image below ultimately should be quantized to 3 colors whereas the number of actual colors in the original image is more than 30. All "anti-aliasing" border colors should be considered minors and eliminated upon quantization as well as "jpeg artifacts", which add more colors to the image because of over-optimization. Note: a source image could be either png or jpeg.

3 color example

For the quantization itself, I'm using PIL.quantize(...) with K as the number of colors to leave. And it works fairly well and keeps the palette perfectly matching to the original.

def color_quantize(path, K):
    image = cv2.imread(path, cv2.IMREAD_UNCHANGED)
    img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    im_pil = Image.fromarray(np.uint8(img))
    im_pil = im_pil.quantize(K, None, 0, None)
    return cv2.cvtColor(np.array(im_pil.convert("RGB")), cv2.COLOR_RGB2BGR) 

Thus, if I knew "K" (the number of primary colors) in advance, then I would use it for im_pil.quantize(...). Basically, I need a way to get that "K" number. Is there any way to determine the number of primary colors?

BTW, regarding the "jpeg artifacts" removal, I'm using img = cv2.bilateralFilter(img, 9, 75, 75) at the moment, which works quite well.

CodePudding user response:

You may want to try to analyze the histograms of the RGB channels to find out how many peaks they have, hopefully you will have a few big peaks, and some very small ones, then the number of big peaks should be your K.

CodePudding user response:

You could use enter image description here

Results

With n_clusters=4, here are the most dominant colors and percentage distribution

[ 28.59165576 114.71245821 197.21921791] 4.30%
[ 25.54783639 197.94045711 147.22737091] 17.94%
[197.75739373  25.78053504 143.31173478] 19.04%
[254.75762607 254.77925368 254.85116121] 58.72%

Visualization of each color cluster (white is invisible)

enter image description here

import cv2, numpy as np
from sklearn.cluster import KMeans

def visualize_colors(cluster, centroids):
    # Get the number of different clusters, create histogram, and normalize
    labels = np.arange(0, len(np.unique(cluster.labels_))   1)
    (hist, _) = np.histogram(cluster.labels_, bins = labels)
    hist = hist.astype("float")
    hist /= hist.sum()

    # Create frequency rect and iterate through each cluster's color and percentage
    rect = np.zeros((50, 300, 3), dtype=np.uint8)
    colors = sorted([(percent, color) for (percent, color) in zip(hist, centroids)])
    start = 0
    for (percent, color) in colors:
        print(color, "{:0.2f}%".format(percent * 100))
        end = start   (percent * 300)
        cv2.rectangle(rect, (int(start), 0), (int(end), 50), \
                      color.astype("uint8").tolist(), -1)
        start = end
    return rect

# Load image and convert to a list of pixels
image = cv2.imread('1.png')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
reshape = image.reshape((image.shape[0] * image.shape[1], 3))

# Find and display most dominant colors
cluster = KMeans(n_clusters=4).fit(reshape)
visualize = visualize_colors(cluster, cluster.cluster_centers_)
visualize = cv2.cvtColor(visualize, cv2.COLOR_RGB2BGR)
cv2.imshow('visualize', visualize)
cv2.waitKey()

CodePudding user response:

I've ended up with the following function to determine the number for dominant colors:

def get_dominant_color_number(img, threshold):
    # remove significant artifacts
    img = cv2.bilateralFilter(img, 9, 75, 75) 

    # resize image to make the process more efficient on 250x250 (without antialiasing to reduce color space)
    thumbnail = cv2.resize(img, (250, 250), None)

    # convert to HSV color space 
    imghsv = cv2.cvtColor(thumbnail, cv2.COLOR_BGR2HSV).astype("float32")
    (h, s, v) = cv2.split(imghsv)

    # quantize saturation and value to merge close colors
    v = (v // 30) * 30
    s = (s // 30) * 30

    imghsv = cv2.merge([h,s,v])
    thumbnail = cv2.cvtColor(imghsv.astype("uint8"), cv2.COLOR_HSV2BGR)

    (unique, counts) = np.unique(thumbnail.reshape(-1, thumbnail.shape[2]), return_counts=True, axis = 0)

    # calculate frequence of each color and sort them 
    freq = counts.astype("float")
    freq /= freq.sum()
    count_sort_ind = np.argsort(-counts)
    
    # get frequent colors above the specified threshold
    n = 0
    dominant_colors = []
    
    for (c) in count_sort_ind:
        n  = 1;
        dominant_colors.append(unique[c])
        if (freq[c] <= threshold):
           break

    return (dominant_colors, n)

# -----------------------------------------------------------

img = cv2.imread("File.png", cv2.IMREAD_UNCHANGED)
channels = img.shape[2]
if channels == 4:
   trans_mask = img[:,:,3] == 0
   img[trans_mask] = [254, 253, 254, 255]
   img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)

(dom_colors, dom_color_num) = get_dominant_color_number(img, .0045)

For the threshold ".0045" it gives an acceptable result. Yet, it still looks a bit "artificial".

  • Related