[Python] How can I get the dominant color of an emoji image?

Discussão em 'Python' iniciado por Stack, Agosto 25, 2025.

  1. Stack

    Stack Membro Participativo

    My program turns an image into a sort of mosaic art of IOS emojis. I get the dominant color of every emoji, put it in a dictionary, and when I loop through the original image (downscaled) I match each pixel to the closest emoji from the dictionary. The final result always ends up with very wrong colors. Why would Homer Simpsons face be pink love hearts? The image and the final result are below. Keep in mind the image is downscaled to about 25px wide. I have tried ColorThief dominant color, K-Means clustering, Average color. dominant_colors function is where the problem is I think.

    Homer Homer made in emoji art, with wrong colors

    class EmojiArt:
    def __init__(self):

    self.directory = os.fsencode("emoji") # Directory
    self.emoji_colors = {}
    self.downscaled_img = None
    self.img = None

    # Initialize variables for when iterating through pixels
    self.height = 0
    self.width = 0
    self.depth = 0

    self.bgr_pixel = []
    self.rgb_pixel = []

    # Store each emoji name for the new image
    self.new_img_emoji_names = []

    self.canvas_width = None
    self.canvas_height = None
    self.emoji_size = 32

    def dominant_colors(self):
    """Method does not need to be called during program, it is only for initial setup. The dictionary is stored in dictionary.py"""
    # Loop through each file, get dominant colour and add to dictionary
    for file in os.listdir(self.directory):
    filename = os.fsdecode(file)
    if filename.endswith(".png"):
    # Open the emoji image with PIL
    image = Image.open(f"emoji\\{filename}")

    if image.mode != 'RGB':
    image = image.convert('RGB')

    ar = numpy.asarray(image)
    shape = ar.shape
    ar = ar.reshape(numpy.prod(shape[:2]), shape[2]).astype(float)

    kmeans = sklearn.cluster.MiniBatchKMeans(
    n_clusters=10,
    init="k-means++",
    max_iter=20,
    random_state=1000
    ).fit(ar)
    codes = kmeans.cluster_centers_

    vecs, *dist = scipy.cluster.vq.vq(ar, codes) # assign codes
    counts, *bins = numpy.histogram(vecs, len(codes)) # count occurrences

    colors = []
    for index in numpy.argsort(counts)[::-1]:
    colors.append(tuple([int(code) for code in codes[index]]))

    # Add the most dominant color to emoji_colors dict
    self.emoji_colors[filename] = colors[0] # Most dominant color

    print(self.emoji_colors)
    pyperclip.copy(self.emoji_colors) # Copy for easy pasting in dictionary.py

    def downscale_image(self, path, new_size, save_filename):
    """
    new_size = (width, height)
    save_filename = directory and filename
    """
    img = Image.open(path)
    img.thumbnail(new_size)
    img.save(save_filename)
    self.downscaled_img = img

    def iterate_pixels(self, image):
    self.img = cv2.imread(image)
    self.height, self.width, self.depth = self.img.shape

    # Loop through each pixel and get the rgb value of it
    for i in range(self.height):
    for j in range(self.width):
    self.bgr_pixel = self.img[i, j] # Gets [Blue, Green, Red] values
    self.rgb_pixel = self.bgr_pixel[::-1]

    # Unpack into 3 separate variables
    r, g, b = self.rgb_pixel

    self.rgb_pixel = (int(r), int(g), int(b)) # self.rgb_pixel is now a tuple and is formatted correctly

    # Retrieve closest emoji
    closest_emoji = self.find_closest_emoji(self.rgb_pixel)
    self.new_img_emoji_names.append(closest_emoji)

    print(f"Pixel {self.rgb_pixel} matches {closest_emoji}")

    def find_closest_emoji(self, rgb_pixel):

    # FIXED: Initialize variables inside the method
    closest_emoji = None
    min_distance = float('inf')

    r1, g1, b1 = rgb_pixel

    for emoji_filename, emoji_color in emoji_colors.items():
    r2, b2, g2 = emoji_color
    distance = math.sqrt((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2)

    if distance < min_distance:
    min_distance = distance
    closest_emoji = emoji_filename

    return closest_emoji

    def build_emoji_art(self):
    # Canvas dimensions
    self.canvas_width = self.downscaled_img.width * self.emoji_size
    self.canvas_height = self.downscaled_img.height * self.emoji_size
    print(self.canvas_width, self.canvas_height)

    # Create blank canvas
    canvas = Image.new("RGB", (self.canvas_width, self.canvas_height), (255, 255, 255))

    # Iterate through each pos in downscaled image
    for i in range(self.downscaled_img.height): # rows
    for j in range(self.downscaled_img.width): # columns

    # Get emoji name from list
    index = i * self.downscaled_img.width + j
    emoji_name = self.new_img_emoji_names[index]

    # Load and resize emoji
    emoji_img = Image.open(f"emoji/{emoji_name}")
    emoji_img = emoji_img.resize((self.emoji_size, self.emoji_size))

    # Convert to RGBA to handle transparency
    if emoji_img.mode != 'RGBA':
    emoji_img = emoji_img.convert('RGBA')

    # Calculate where to place emoji
    x = j * self.emoji_size
    y = i * self.emoji_size

    canvas.paste(emoji_img, (x, y), emoji_img)

    # Save image
    final_img_name = "homer_emoji_art.png"
    canvas.save(final_img_name)
    print(f"Emoji Art saved as {final_img_name}")


    I tried using lots of tutorials for getting dominant color but nothing gave me colors that are perceived by the eye when you stand back a few meters.

    Continue reading...

Compartilhe esta Página