Python - Finden Sie die dominante / häufigste Farbe in einem Bild

75

Ich suche nach einer Möglichkeit, mit Python die dominanteste Farbe / den dominantesten Ton in einem Bild zu finden. Entweder der durchschnittliche Farbton oder der häufigste von RGB reicht aus. Ich habe mir die Python Imaging-Bibliothek angesehen und konnte nichts in Bezug auf das, wonach ich gesucht habe, in ihrem Handbuch und auch kurz bei VTK finden.

Ich fand jedoch einen PHP - Skript , das tut , was ich brauche, hier (Anmeldung erforderlich zu Download). Das Skript scheint die Größe des Bildes auf 150 * 150 zu ändern, um die dominanten Farben hervorzuheben. Danach bin ich jedoch ziemlich verloren. Ich habe darüber nachgedacht, etwas zu schreiben, das die Größe des Bildes auf eine kleine Größe ändert und dann jedes zweite Pixel auf das Bild überprüft, obwohl ich mir vorstelle, dass dies sehr ineffizient wäre (obwohl die Implementierung dieser Idee als C-Python-Modul eine Idee sein könnte).

Nach all dem bin ich jedoch immer noch ratlos. Also wende ich mich an dich. Gibt es eine einfache und effiziente Möglichkeit, die dominierende Farbe in einem Bild zu finden?

Blue Peppers
quelle
Ich vermute, es ändert die Größe des Bildes, damit der Neuskalierungsalgorithmus einen Teil der Mittelwertbildung für Sie übernimmt.
Skurmedel

Antworten:

75

Hier ist Code, der das Cluster-Paket von Pillow and Scipy verwendet .

Der Einfachheit halber habe ich den Dateinamen als "image.jpg" fest codiert. Das Ändern der Bildgröße dient der Geschwindigkeit: Wenn Ihnen das Warten nichts ausmacht, kommentieren Sie den Größenänderungsaufruf aus. Wenn dieses Beispielbild von blauen Paprikaschoten verwendet wird , heißt es normalerweise, dass die dominierende Farbe # d8c865 ist, was ungefähr dem hellgelben Bereich links unten von den beiden Paprikaschoten entspricht. Ich sage "normalerweise", weil der verwendete Clustering-Algorithmus einen gewissen Grad an Zufälligkeit aufweist. Es gibt verschiedene Möglichkeiten, dies zu ändern, aber für Ihre Zwecke kann es gut passen. (Überprüfen Sie die Optionen für die Variante kmeans2 (), wenn Sie deterministische Ergebnisse benötigen.)

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))

Hinweis: Wenn ich die Anzahl der zu findenden Cluster von 5 auf 10 oder 15 erhöhe, wurden häufig grünliche oder bläuliche Ergebnisse erzielt. Angesichts des Eingabebildes sind dies auch vernünftige Ergebnisse ... Ich kann auch nicht sagen, welche Farbe in diesem Bild wirklich dominiert, also kann ich den Algorithmus nicht bemängeln!

Auch ein kleiner Bonus: Speichern Sie das verkleinerte Bild nur mit den N häufigsten Farben:

# 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')
Peter Hansen
quelle
2
Beeindruckend. Das ist großartig. Fast genau das, wonach ich gesucht habe. Ich sah scipy an und hatte das Gefühl, dass die Antwort irgendwo drin war: P Danke für Ihre Antwort.
Blue Peppers
1
Ich habe Ihren Code bearbeitet / aktualisiert. Vielen Dank für diese kompakte und gut funktionierende Lösung!
Simon Steinberger
1
@SimonSteinberger Danke für die Bearbeitung, und ich bin froh zu hören, dass es auch 7 Jahre später noch laufen und jemandem helfen kann! Es war ein lustiges Problem, daran zu arbeiten.
Peter Hansen
1
Dies hat mehrere Probleme mit Python 3.x. Zum Beispiel (1) .encode('hex')ist nicht mehr gültige Syntax , und (2) from PIL import Image Quelle
philshem
1
Danke @philshem. Ich glaube, ich habe es jetzt so geändert, dass es auch 3.x unterstützt. Einige gleichzeitig vorgenommene Änderungen lösten Verwerfungen und Warnungen auf, die entweder in 2.7 oder 3.7 gemeldet wurden (aber nicht unbedingt in beiden Fällen).
Peter Hansen
21

Versuchen Sie es mit Farbdieb . Es basiert auf PILund funktioniert fantastisch.

Installation

pip install colorthief

Verwendung

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

Es kann auch Farbpalette finden

palette = color_thief.get_palette(color_count=6)
Artem Bernatskyi
quelle
1
Fantastisches Modul
Dheeraj M Pai
16

Die Python Imaging Library verfügt über die Methode getcolors für Bildobjekte:

im.getcolors () => eine Liste von (Anzahl, Farbe) Tupeln oder Keine

Ich denke, Sie können immer noch versuchen, die Größe des Bildes vorher zu ändern und zu sehen, ob es besser funktioniert.

zvone
quelle
6

Wenn Sie immer noch nach einer Antwort suchen, hat Folgendes für mich funktioniert, wenn auch nicht besonders effizient:

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)
Tim S.
quelle
Für png müssen Sie dies leicht anpassen, um die Tatsache zu berücksichtigen, dass img.getpixel r, g, b, a zurückgibt (vier statt drei Werte). Oder es tat es trotzdem für mich.
Rossdavidh
Dies wiegt Pixel ungleichmäßig. Das letzte berührte Pixel trägt die Hälfte zum Gesamtwert bei. Das vorhergehende Pixel trägt die Hälfte dazu bei. Tatsächlich beeinflussen nur die letzten 8 Pixel den Durchschnitt.
Russell Borogove
Du hast recht - dummer Fehler. Einfach die Antwort bearbeitet - lass es mich wissen, wenn das funktioniert.
Tim S
6
Dies ist keine Antwort auf diese Frage. Die durchschnittliche Farbe ist nicht die dominierende Farbe in einem Bild.
Phani Rithvij
5

Sie können PIL verwenden, um die Bildgröße in jeder Dimension wiederholt um den Faktor 2 zu verkleinern, bis sie 1x1 erreicht. Ich weiß nicht, welchen Algorithmus PIL für das Downscaling um große Faktoren verwendet. Wenn Sie also in einer einzigen Größenänderung direkt zu 1x1 wechseln, gehen möglicherweise Informationen verloren. Es ist vielleicht nicht das effizienteste, aber es gibt Ihnen die "durchschnittliche" Farbe des Bildes.

Russell Borogove
quelle
5

Es ist nicht notwendig, k-means zu verwenden, um die dominante Farbe zu finden, wie Peter vorschlägt. Dies macht ein einfaches Problem zu kompliziert. Sie beschränken sich auch auf die Anzahl der ausgewählten Cluster, sodass Sie im Grunde eine Vorstellung davon benötigen, was Sie suchen.

Wie Sie erwähnt und von zvone vorgeschlagen, eine schnelle Lösung , um die häufigste / dominante Farbe zu finden , ist durch die Verwendung von Kissen Bibliothek. Wir müssen nur die Pixel nach ihrer Zählnummer sortieren.

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

Das einzige Problem besteht darin, dass die Methode getcolors()None zurückgibt, wenn die Anzahl der Farben mehr als 256 beträgt. Sie können damit umgehen, indem Sie die Größe des Originalbilds ändern.

Alles in allem ist es vielleicht nicht die genaueste Lösung, aber es erledigt die Arbeit.

mobiuscreek
quelle
Weißt du was? Sie geben die Funktion selbst zurück, obwohl Sie ihr einen Wert zuweisen, aber es ist keine gute Idee
Black Thunder
Sie haben absolut Recht und dafür habe ich den Namen der Funktion bearbeitet!
Mobiuscreek
Dies ist nicht sehr zuverlässig. (1) Sie sollten thumbnailanstelle der Größenänderung verwenden, um Zuschneiden oder Dehnen zu vermeiden. (2) Wenn Sie ein Bild mit 2 weißen Pixeln und 100 verschiedenen Ebenen schwärzlicher Pixel haben, erhalten Sie immer noch Weiß.
Pithikos
Einverstanden, aber ich wollte die Einschränkung vermeiden, die Granularität bei Verwendung vordefinierter Cluster oder einer Palette zu verringern. Je nach Anwendungsfall ist dies möglicherweise nicht wünschenswert.
Mobiuscreek
3

Um Peters Antwort zu ergänzen: Wenn PIL Ihnen ein Bild mit dem Modus "P" oder so ziemlich jedem Modus gibt, der nicht "RGBA" ist, müssen Sie eine Alpha-Maske anwenden, um es in RGBA zu konvertieren. Sie können das ziemlich einfach machen mit:

if im.mode == 'P':
    im.putalpha(0)
Samuel Clay
quelle
2

Unten finden Sie ein auf c ++ Qt basierendes Beispiel, um die vorherrschende Bildfarbe zu erraten. Sie können PyQt verwenden und dasselbe in Python-Äquivalente übersetzen.

#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();
}
Ankur Gupta
quelle
2

Sie können dies auf viele verschiedene Arten tun. Und Sie brauchen nicht wirklich scipy und k-means, da Pillow dies intern bereits für Sie erledigt, wenn Sie entweder die Bildgröße ändern oder das Bild auf eine bestimmte Palette reduzieren.

Lösung 1 : Ändern Sie die Bildgröße auf 1 Pixel.

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

Lösung 2 : Reduzieren Sie die Bildfarben auf eine Palette

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

Beide Lösungen liefern ähnliche Ergebnisse. Die letztere Lösung bietet Ihnen wahrscheinlich mehr Genauigkeit, da wir das Seitenverhältnis beim Ändern der Bildgröße beibehalten. Außerdem erhalten Sie mehr Kontrolle, da Sie das optimieren können pallete_size.

Pithikos
quelle
Dies ist auch sprunghaft schneller als jedes der oben genannten Scikit-Learn / Scipy-Bilder.
whlteXbread
0

Hier ist meine Anpassung basierend auf der Lösung von Peter Handsen. Es ist anders, weil es kmeans ++ verwendet, um anfängliche Cluster-Zentren auszuwählen, was bessere Ergebnisse zu liefern scheint. Sie können auch einen random_state für die deterministische Ausgabe hinzufügen.

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
Jakob
quelle