Python - Знайдіть домінуючий / найпоширеніший колір на зображенні


75

Я шукаю спосіб знайти найбільш домінуючий колір / тон у зображенні за допомогою python. Підійде або середній відтінок, або найпоширеніший з RGB. Я переглянув бібліотеку Python Imaging і не зміг знайти нічого, що стосується того, що я шукав, у їх керівництві, а також коротко у VTK.

Я тим не менш знайти PHP скрипт , який робить те , що мені потрібно, тут (Ввійти потрібно для завантаження). Здається, сценарій змінює розмір зображення до 150 * 150, щоб виявити домінуючі кольори. Однак після цього я досить загубився. Я розглядав можливість написати щось, що дозволило б змінити розмір зображення до невеликого розміру, а потім перевірити кожен другий піксель або близько того, чи не є це зображення, хоча, на мою думку, це було б дуже неефективно (хоча реалізація цієї ідеї як модуля C python може бути ідеєю).

Однак після всього цього я все ще тупий. Тож я звертаюся до вас, ТАК. Чи існує простий, ефективний спосіб знайти домінуючий колір на зображенні.


Я припускаю, що це змінює розмір зображення, щоб дозволити алгоритму масштабування зробити для вас усереднення.
Skurmedel

Відповіді:


76

Ось код, що використовує кластерний пакет Pillow і Scipy .

Для простоти я жорстко закодував ім'я файлу як "image.jpg". Зміна розміру зображення призначена для швидкості: якщо ви не проти зачекати, прокоментуйте виклик зміни розміру. При запуску на цьому зразку зображення синього перцю зазвичай кажуть, що домінуючим кольором є # d8c865, що приблизно відповідає яскраво-жовтуватій області внизу ліворуч від двох перців. Я кажу "зазвичай", оскільки алгоритм кластеризації має певний ступінь випадковості. Існують різні способи змінити це, але для ваших цілей це може підійти. (Перевірте параметри варіанту kmeans2 (), якщо вам потрібні детерміновані результати.)

from __future__ import print_function
import binascii
import struct
from PIL import Image
import numpy as np
import scipy
import scipy.misc
import scipy.cluster

NUM_CLUSTERS = 5

print('reading image')
im = Image.open('image.jpg')
im = im.resize((150, 150))      # optional, to reduce time
ar = np.asarray(im)
shape = ar.shape
ar = ar.reshape(scipy.product(shape[:2]), shape[2]).astype(float)

print('finding clusters')
codes, dist = scipy.cluster.vq.kmeans(ar, NUM_CLUSTERS)
print('cluster centres:\n', codes)

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

index_max = scipy.argmax(counts)                    # find most frequent
peak = codes[index_max]
colour = binascii.hexlify(bytearray(int(c) for c in peak)).decode('ascii')
print('most frequent is %s (#%s)' % (peak, colour))

Примітка: коли я розширюю кількість скупчень, щоб знайти з 5 до 10 або 15, це часто дає результати, які мають зеленуватий або синюватий відтінок. Враховуючи вхідне зображення, це теж розумні результати ... Я теж не можу сказати, який колір насправді є домінуючим у цьому зображенні, тому я не винен алгоритм!

Також невеликий бонус: збережіть зображення зменшеного розміру, використовуючи лише N найчастіших кольорів:

# bonus: save image using only the N most common colours
import imageio
c = ar.copy()
for i, code in enumerate(codes):
    c[scipy.r_[scipy.where(vecs==i)],:] = code
imageio.imwrite('clusters.png', c.reshape(*shape).astype(np.uint8))
print('saved clustered image')

2
Ого. Це чудово. Майже саме те, що я шукав. Я дивився на scipy і мав відчуття, що відповідь була десь там: P Дякую за вашу відповідь.
Blue Peppers

1
Я відредагував / оновив ваш код. Дякуємо за це компактне та добре працює рішення!
Саймон Штейнбергер

1
@SimonSteinberger Дякуємо за редагування, і я радий почути, що він все ще може працювати і допомагати комусь через 7 років! Над цим було весело працювати.
Пітер Хансен,

1
це має кілька проблем з python 3.x. Ні , наприклад, (1) .encode('hex')є неприпустимим синтаксисом , і (2) from PIL import Image джерело
philshem

1
Дякую @philshem. Я вважаю, що я змінив його на підтримку 3.x, а також зараз. Деякі зміни, внесені одночасно, усунули припинення використання та попередження, про які повідомлялося або у 2,7, або в 3,7 (але не обов’язково для обох).
Пітер Хансен,

24

Спробуйте Color-thief . Він заснований PILі працює надзвичайно.

Встановлення

pip install colorthief

Використання

from colorthief import ColorThief
color_thief = ColorThief('/path/to/imagefile')
# get the dominant color
dominant_color = color_thief.get_color(quality=1)

Тут також можна знайти кольорову палітру

palette = color_thief.get_palette(color_count=6)

1
Фантастичний модуль
Dheeraj M Pai

16

Бібліотека зображень Python має метод getcolors для об’єктів Image:

im.getcolors () => список кортежів (кількість, колір) або None

Я думаю, ви все ще можете спробувати змінити розмір зображення до цього і перевірити, чи він працює краще.


6

Якщо ви все ще шукаєте відповідь, ось що мені вдалося, хоч і не дуже ефективно:

from PIL import Image

def compute_average_image_color(img):
    width, height = img.size

    r_total = 0
    g_total = 0
    b_total = 0

    count = 0
    for x in range(0, width):
        for y in range(0, height):
            r, g, b = img.getpixel((x,y))
            r_total += r
            g_total += g
            b_total += b
            count += 1

    return (r_total/count, g_total/count, b_total/count)

img = Image.open('image.png')
#img = img.resize((50,50))  # Small optimization
average_color = compute_average_image_color(img)
print(average_color)

Для png вам потрібно трохи налаштувати це, щоб врахувати той факт, що img.getpixel повертає r, g, b, a (чотири значення замість трьох). Або це все одно зробило для мене.
rossdavidh

Це важить пікселі нерівномірно. Останній доторкнутий піксель становить половину загальної величини. Піксель раніше сприяє половині цього. Фактично лише останні 8 пікселів вплинуть на середнє значення.
Рассел Борогове

Ви маєте рацію - дурна помилка. Щойно відредагував відповідь - дайте мені знати, якщо це спрацює.
Тім Сі

6
Це не відповідь на це питання. Середній колір не є домінуючим кольором на зображенні.
Phani Rithvij

5

Ви можете використовувати PIL для повторного зменшення розміру зображення у 2 рази в кожному вимірі, поки воно не досягне 1х1. Я не знаю, який алгоритм PIL використовує для зменшення масштабу великими факторами, тому перехід безпосередньо до 1x1 за один розмір може втратити інформацію. Це може бути не найефективніше, але воно дасть вам «середній» колір зображення.


5

Необов’язково використовувати к-засоби, щоб знайти домінуючий колір, як пропонує Петро. Це ускладнює просту проблему. Ви також обмежуєтесь кількістю вибраних кластерів, тому в основному вам потрібно уявлення про те, на що ви дивитесь.

Як ви вже згадували і як запропонував zvone, швидким рішенням знайти найбільш поширений / домінуючий колір є використання бібліотеки Pillow . Нам просто потрібно відсортувати пікселі за їх числом.

from PIL import Image

    def find_dominant_color(filename):
        #Resizing parameters
        width, height = 150,150
        image = Image.open(filename)
        image = image.resize((width, height),resample = 0)
        #Get colors from image object
        pixels = image.getcolors(width * height)
        #Sort them by count number(first element of tuple)
        sorted_pixels = sorted(pixels, key=lambda t: t[0])
        #Get the most frequent color
        dominant_color = sorted_pixels[-1][1]
        return dominant_color

Єдина проблема полягає в тому, що метод getcolors()повертає None, коли кількість кольорів перевищує 256. Ви можете впоратися з цим, змінивши розмір вихідного зображення.

Загалом, це може бути не найточніше рішення, але воно робить роботу.


Знаєш, що? Ви повертаєте саму функцію, хоча і присвоюєте їй значення, але це не найкраща ідея
Black Thunder

Ви абсолютно праві, і для цього я відредагував назву функції!
mobiuscreek

Це не дуже надійно. (1) вам слід використовувати thumbnailзамість зміни розміру, щоб уникнути обрізання або розтягування, (2) якщо у вас є зображення з 2 білими пікселями та 100 різними рівнями чорнуватого пікселя, ви все одно отримаєте білий.
Пітікос

Погодився, але я хотів уникнути застереження щодо зменшення деталізації при використанні заздалегідь визначених кластерів або палітри. Залежно від випадку використання, це може бути небажаним.
mobiuscreek

3

Щоб додати до відповіді Пітера, якщо PIL дає вам зображення з режимом "P" або майже будь-яким режимом, який не є "RGBA", то вам потрібно застосувати альфа-маску, щоб перетворити його в RGBA. Ви можете зробити це досить легко за допомогою:

if im.mode == 'P':
    im.putalpha(0)

2

Нижче наведено приклад на основі c ++ Qt, який дозволяє вгадати переважаючий колір зображення. Ви можете використовувати PyQt і перекласти його в еквівалент Python.

#include <Qt/QtGui>
#include <Qt/QtCore>
#include <QtGui/QApplication>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    QPixmap pixmap("logo.png");
    QImage image = pixmap.toImage();
    QRgb col;
    QMap<QRgb,int> rgbcount;
    QRgb greatest = 0;

    int width = pixmap.width();
    int height = pixmap.height();

    int count = 0;
    for (int i = 0; i < width; ++i)
    {
        for (int j = 0; j < height; ++j)
        {
            col = image.pixel(i, j);
            if (rgbcount.contains(col)) {
                rgbcount[col] = rgbcount[col] + 1;
            }
            else  {
                rgbcount[col] = 1;
            }

            if (rgbcount[col] > count)  {
                greatest = col;
                count = rgbcount[col];
            }

        }
    }
    qDebug() << count << greatest;
    return app.exec();
}

2

Ви можете зробити це різними способами. І вам насправді не потрібні scipy та k-засоби, оскільки внутрішньо Pillow вже робить це за вас, коли ви або змінюєте розмір зображення, або зменшуєте зображення до певної палітри.

Рішення 1 : зменште розмір зображення до 1 пікселя.

def get_dominant_color(pil_img):
    img = pil_img.copy()
    img.convert("RGB")
    img.resize((1, 1), resample=0)
    dominant_color = img.getpixel((0, 0))
    return dominant_color

Рішення 2 : зменшіть кольори зображення до палети

def get_dominant_color(pil_img, palette_size=16):
    # Resize image to speed up processing
    img = pil_img.copy()
    img.thumbnail((100, 100))

    # Reduce colors (uses k-means internally)
    paletted = img.convert('P', palette=Image.ADAPTIVE, colors=palette_size)

    # Find the color that occurs most often
    palette = paletted.getpalette()
    color_counts = sorted(paletted.getcolors(), reverse=True)
    palette_index = color_counts[0][1]
    dominant_color = palette[palette_index*3:palette_index*3+3]

    return dominant_color

Обидва рішення дають схожі результати. Останнє рішення надає вам, мабуть, більшу точність, оскільки ми зберігаємо пропорції при зміні розміру зображення. Крім того, ви отримуєте більше контролю, оскільки ви можете налаштувати pallete_size.


Це також стрибає швидше, ніж будь-яке із зображень scikit-learn / scipy вище.
whlteXbread

0

Ось моя адаптація на основі рішення Пітера Гансена. Це інакше, оскільки він використовує kmeans ++ для вибору початкових центрів кластера, що, здається, дає кращі результати. Також ви можете додати random_state для детермінованого виводу.

import scipy.cluster
import sklearn.cluster
import numpy
from PIL import Image

def dominant_colors(image):  # PIL image input

    num_clusters = 10

    image = image.resize((150, 150))      # optional, to reduce time
    ar = numpy.asarray(image)
    shape = ar.shape
    ar = ar.reshape(numpy.product(shape[:2]), shape[2]).astype(float)

    kmeans = sklearn.cluster.KMeans(
        n_clusters=num_clusters,
        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]]))
    return colors                    # returns colors in order of dominance
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.