2025, Oct 22 16:31
Почему топ‑10 по RGB‑гистограмме не равно доминирующим цветам
Сравниваем два подхода к топовым цветам: точные RGB‑тройки по гистограмме и доминирующие через KMeans. Примеры кода с Pillow, numpy и sklearn. Поясняем разницу.
Два скрипта могут казаться решающими одну и ту же задачу, но давать совершенно разные результаты. Именно это происходит, когда вы сравниваете «топ‑10 цветов» из исходной RGB‑гистограммы с «доминирующими цветами», которые выдают некоторые онлайн‑сервисы. В первом случае считаются точные тройки пикселей. Во втором изображение обычно сжимается в палитру из 10 цветов с помощью кластеризации. Это разные вопросы.
Постановка задачи
Вы открываете изображение в Pillow, перебираете все цвета и оставляете десять самых частых. Прогон того же изображения через онлайн‑сервис дает совсем другой список. Возникает подозрение, что PIL может обрабатывать цвета иначе.
Воспроизводимый код для подсчета точных цветов
Ниже фрагмент, который берет неотсортированный список из getcolors, поддерживает текущий «топ‑10» по частоте, а затем сортирует этот короткий список по числу вхождений. Логика намеренно считает точные RGB‑тройки; близкие оттенки не объединяются.
from PIL import Image
pic = Image.open(image_url)
color_bag = pic.getcolors(maxcolors=100000)  # каждый уникальный цвет с его частотой
best_ten = color_bag[:10]
for entry in color_bag[9:]:
    tally = entry[0]
    current_counts = []
    for chosen in best_ten:
        current_counts.append(chosen[0])
    if tally > min(current_counts):
        replace_at = current_counts.index(min(current_counts))
        best_ten[replace_at] = entry
ranked_best = sorted(best_ten, key=lambda item: item[0], reverse=True)
print(ranked_best)
print(best_ten)
Откуда на самом деле берется расхождение
Результаты не совпадают, потому что отвечают на разные вопросы. Подсчет точных троек возвращает самые частые значения пикселей на изображении, и это легко могут быть множество близких оттенков одного и того же серого. Онлайн‑сервис, напротив, ведет себя как извлечение палитры: он выбирает десять репрезентативных цветов, которые обобщают изображение целиком, а не десять самых повторяющихся точных RGB‑значений. Такое поведение соответствует кластеризации KMeans по значениям пикселей.
Есть и отдельная деталь: getcolors возвращает неотсортированный список, и код, который поддерживает «скользящий» топ‑k, как выше, зависит от этого порядка. Иначе говоря, что на вход, то и на выход, если вы полагаетесь на исходную упорядоченность. Но даже если порядок исправить, список по частотам все равно не совпадет с палитрой на основе кластеров — цели разные.
Два подхода к «топовым цветам» — в зависимости от цели
Если цель — буквально «10 самых частых точных RGB‑троек», достаточно простой гистограммы. Если же нужна «десятка цветов, которые вы бы оставили при сжатии изображения до палитры из 10», тогда вам нужны центры кластеров KMeans.
Точные частоты с помощью numpy + Counter:
from collections import Counter
import numpy as np
from PIL import Image
pixels = np.asarray(Image.open('image.jpg'))
freqs = Counter(tuple(v) for v in pixels.reshape(-1, 3))
print(freqs.most_common(10))
Палитра на основе кластеров с использованием KMeans:
from sklearn.cluster import KMeans
import numpy as np
from PIL import Image
pixels = np.asarray(Image.open('image.jpg'))
km = KMeans(n_clusters=10, random_state=0, n_init="auto")
km.fit(pixels.reshape(-1, 3))
print(km.cluster_centers_)
Центры кластеров — это барицентры групп пикселей и они не обязаны совпадать с реальными значениями пикселей. Это нормально. Кроме того, преобразование массива H×W×3 в форму N×3 является простым представлением при непрерывном размещении данных, поэтому этот шаг фактически бесплатен в типичных случаях.
Почему это важно
Выбор метода меняет результат. Строгая гистограмма может поместить в топ‑10 несколько почти одинаковых темных оттенков, если сцена ими доминирует. Палитра KMeans обычно включает более светлые тона и редкие, но визуально значимые цвета, потому что она обобщает изображение, группируя похожие цвета и представляя каждую группу ее центром. Если вы собираетесь квантизовать изображение до 10 цветов, центры кластеров — верная цель. Если вы проверяете точное повторное использование цветов, правильно применять гистограмму.
Выводы
Сначала уточните, что именно вы называете «топовыми цветами». Если речь о самых частых точных RGB‑значениях — считайте частоты. Если о доминирующих цветах палитры — используйте KMeans. Не ждите совпадения результатов: подходы целенаправленно оптимизируют разное. И если вы полагаетесь на getcolors, не думайте, что выход уже отсортирован — явно сортируйте, если вашей логике нужна стабильность.