Search code examples
pythonopencvmachine-learningimage-processingk-means

How to extract the specific colors from an image


I need some support in adding details to my code. In my code, I am just using 1 image (see attachment, called "Photo1").

When I run the whole code, it will give me the following output:

,"Dominant Colors:
['#263f6c', '#4c5d85', '#d3d6d8', '#9db1c6', '#7388a7']"

I don't want the output as codes, I want to know exactly which colors are the dominant colors (example: red, blue, black) and return it in a column called "extracted colors"

How can I add these details in my code?

import cv2
import numpy as np
from PIL import Image
from sklearn.cluster import KMeans

image_path = "Photo1.jpg"
num_colors = 5
num_clusters = 5

def get_dominant_colors(image_path, num_colors=10, num_clusters=5):
    image = Image.open(image_path)
    image = image.resize((200, 200))
    image = image.convert('RGB')
    img_array = np.array(image)
    pixels = img_array.reshape(-1, 3)
    kmeans = KMeans(n_clusters=num_clusters, random_state=0)
    labels = kmeans.fit_predict(pixels)
    centers = kmeans.cluster_centers_
    color_counts = {}
    for label in np.unique(labels):
        color = tuple(centers[label].astype(int))
        color_counts[color] = np.count_nonzero(labels == label)
    sorted_colors = sorted(color_counts.items(), key=lambda x: x[1], reverse=True)
    dominant_colors = [color for color, count in sorted_colors[:num_colors]]
    color_occurrences = [count for color, count in sorted_colors[:num_colors]]
    dominant_colors_hex = ['#%02x%02x%02x' % color for color in dominant_colors]
    return dominant_colors_hex, color_occurrences

dominant_colors, color_occurrences = get_dominant_colors(image_path, num_colors, num_clusters)

print("Dominant Colors:")
print(dominant_colors)

palette_height = 100
palette_width = 100 * num_colors
palette = np.zeros((palette_height, palette_width, 3), dtype=np.uint8)

start_x = 0
for color_hex in dominant_colors:
    color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (1, 3, 5))
    end_x = start_x + 100
    palette[:, start_x:end_x] = color_rgb
    start_x = end_x

palette_image = Image.fromarray(palette)
palette_bgr = cv2.cvtColor(np.array(palette_image), cv2.COLOR_RGB2BGR)

cv2.imshow("Palette", palette_bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()

enter image description here


Solution

  • One of the possibilities is to use webcolors package. I modified your code to find the name of the closest matching color on the color palette:

    import cv2
    import numpy as np
    from PIL import Image
    from sklearn.cluster import KMeans
    import webcolors
    from webcolors import CSS3_HEX_TO_NAMES, hex_to_rgb
    
    image_path = "Photo1.png"
    num_colors = 5
    num_clusters = 5
    
    def get_dominant_colors(image_path, num_colors=10, num_clusters=5):
        image = Image.open(image_path)
        image = image.resize((200, 200))
        image = image.convert('RGB')
        img_array = np.array(image)
        pixels = img_array.reshape(-1, 3)
        kmeans = KMeans(n_clusters=num_clusters, random_state=0)
        labels = kmeans.fit_predict(pixels)
        centers = kmeans.cluster_centers_
        color_counts = {}
        for label in np.unique(labels):
            color = tuple(centers[label].astype(int))
            color_counts[color] = np.count_nonzero(labels == label)
        sorted_colors = sorted(color_counts.items(), key=lambda x: x[1], reverse=True)
        dominant_colors = [color for color, count in sorted_colors[:num_colors]]
        color_occurrences = [count for color, count in sorted_colors[:num_colors]]
        dominant_colors_hex = ['#%02x%02x%02x' % color for color in dominant_colors]
        return dominant_colors_hex, color_occurrences
    
    dominant_colors, color_occurrences = get_dominant_colors(image_path, num_colors, num_clusters)
    
    def closest_color(hex):
        colors = {}
        for key, name in CSS3_HEX_TO_NAMES.items():
            r_c, g_c, b_c = hex_to_rgb(key)
            rd = (int(hex[1:3], 16) - r_c) ** 2
            gd = (int(hex[3:5], 16) - g_c) ** 2
            bd = (int(hex[5:7], 16) - b_c) ** 2
            colors[(rd + gd + bd)] = name
        return colors[min(colors.keys())]
    print("Dominant colors: ")
    
    for col in dominant_colors:
        col_name = closest_color(col)
        print(col_name)
    
    palette_height = 100
    palette_width = 100 * num_colors
    palette = np.zeros((palette_height, palette_width, 3), dtype=np.uint8)
    
    start_x = 0
    for color_hex in dominant_colors:
        color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (1, 3, 5))
        end_x = start_x + 100
        palette[:, start_x:end_x] = color_rgb
        start_x = end_x
    
    palette_image = Image.fromarray(palette)
    palette_bgr = cv2.cvtColor(np.array(palette_image), cv2.COLOR_RGB2BGR)
    
    cv2.imshow("Palette", palette_bgr)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    You can swap the color mapping from CSS3 to HTML4, for example. It includes names for the 16 base colors Here you can find more. Just replace the line

    for key, name in CSS3_HEX_TO_NAMES.items():
    

    with

    for key, name in HTML4_HEX_TO_NAMES.items():