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.
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:
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)
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".