Jak porównać dwa kolory pod kątem podobieństwa / różnicy


171

Chcę zaprojektować program, który pomoże mi ocenić pomiędzy 5 predefiniowanymi kolorami, który jest bardziej podobny do koloru zmiennego iz jakim procentem. Chodzi o to, że nie wiem, jak to zrobić ręcznie, krok po kroku. Dlatego jeszcze trudniej jest wymyślić program.

Więcej szczegółów: Kolory pochodzą z fotografii tub z żelem o różnych kolorach. Mam 5 rurek w różnych kolorach, z których każda jest reprezentatywna dla 1 z 5 poziomów. Chcę zrobić zdjęcia innych próbek i ocenić na komputerze, do jakiego poziomu należy ta próbka, porównując kolory, i chcę to wiedzieć również z procentem przybliżenia. Chciałbym program, który robi coś takiego: http://www.colortools.net/color_matcher.html

Jeśli możesz mi powiedzieć, jakie kroki mam podjąć, nawet jeśli są to rzeczy do przemyślenia i wykonania ręcznie. Byłoby to bardzo pomocne.


1
Dokonałem niewielkiej zmiany w tekście, zmieniając portugalskie słowo na to, co uważam za poprawne angielskie odpowiedniki ... zmień je z powrotem, jeśli się pomylę.
Beska

13
Istnieje artykuł wikipedii o różnicy kolorów: en.wikipedia.org/wiki/Color_difference
Ocaso Protal

4
To powinno być interesujące: stevehanov.ca/blog/index.php?id=116 Bada obliczanie różnicy w trzech różnych modelach kolorów.
Vlad

Hej @OcasoProtal, to świetny link dzięki za udostępnienie. A do PO ciekawe pytanie.
Percepcja

Postaraj się również zminimalizować wszelkie potencjalne zmienności fotograficzne ... więcej szczegółów w odpowiedzi poniżej.
Beska

Odpowiedzi:


130

Zapoznaj się z artykułem Wikipedii na temat różnicy kolorów, aby uzyskać informacje na temat odpowiednich potencjalnych klientów. Zasadniczo chcesz obliczyć metrykę odległości w jakiejś wielowymiarowej przestrzeni kolorów. Ale RGB nie jest „percepcyjnie jednolity”, więc metryka odległości Euclidean RGB sugerowana przez Vadima nie będzie pasować do odległości między kolorami postrzeganej przez człowieka. Po pierwsze, L a b * ma być percepcyjnie jednolitą przestrzenią kolorów, a metryka deltaE jest powszechnie stosowana. Ale są bardziej wyrafinowane przestrzenie kolorów i bardziej wyrafinowane formuły deltaE, które zbliżają się do ludzkiej percepcji.

Będziesz musiał dowiedzieć się więcej o przestrzeniach kolorów i oświetlaczach, aby dokonać konwersji. Ale dla szybkiej formuły, która jest lepsza niż metryka Euklidesa RGB, zrób to: załóżmy, że twoje wartości RGB znajdują się w przestrzeni kolorów sRGB, znajdź formuły konwersji sRGB na L a b *, przekonwertuj kolory sRGB na L a b *, i oblicz deltaE między dwiema wartościami L a b *. Nie jest to kosztowne obliczeniowo, to tylko niektóre nieliniowe formuły i niektóre mnożenia i dodania.


11
+1 dla „deltaE”, jest to najbardziej znormalizowana metoda porównawcza i istnieją adaptacje wzoru deltaE dla różnych przypadków użycia.
Martin Hennings

9
Formuły konwersji można znaleźć tutaj: brucelindbloom.com/index.html?Equations.html
Guillermo Gutiérrez

4
Lub, jeśli pracujesz w Rubim, sprawdź colorklejnot, który implementuje deltaE wśród innych operacji koloru.
Mike Jarema


46

Pomysł, który jako pierwszy przyszedł mi do głowy (przepraszam, jeśli jest głupi). Można założyć trzy składowe kolorów współrzędnych punktów, a następnie obliczyć odległość między punktami.

FE

Point1 has R1 G1 B1
Point2 has R2 G2 B2

Odległość między kolorami wynosi

d=sqrt((r2-r1)^2+(g2-g1)^2+(b2-b1)^2)

Procent to

p=d/sqrt((255)^2+(255)^2+(255)^2)

28
Jeśli używamy przestrzeni kolorów RGB, różnica między 2 kolorami nie jest taka sama, jak to, jak ludzie postrzegają różnicę. Ale tak, podstawowa idea jest wszędzie taka sama - musielibyśmy po prostu zmapować ją do innej przestrzeni kolorów (myślę, że laboratorium)
Voo

6
@Voo: Zgadzam się, HSV / HSL / LAB byłyby znacznie lepszymi przestrzeniami kolorów niż (s) RGB do dopasowywania podobieństwa na podstawie odległości.
Jon Purdy,

4
To dobry sposób na powiedzenie ci, jak różne SĄ dwa kolory, ale kiepsko powie ci, jak różne będą POSTRZEGANE. Ludzkie oczy są dalekie od doskonałości: jesteśmy bardziej wrażliwi na zieleń niż czerwień lub błękit, nasze postrzeganie jasności jest logarytmiczne itp. OP nigdy nie określił, którego chce; ale zobacz tutaj algorytm specjalnie dostosowany do ludzkiego wzroku.
BlueRaja - Danny Pflughoeft

+ To także mój pierwszy pomysł.
ST3

9
Kolejnym problemem jest tutaj 255, 0, 0 to ta sama odległość od 0, 255, 0, jak to jest 0, 0, 255.

27

właściwie szedłem tą samą ścieżką kilka miesięcy temu. nie ma idealnej odpowiedzi na pytanie (które zostało tu zadane kilka razy), ale jest jedna bardziej wyrafinowana odpowiedź niż sqrt (rr) itp. i łatwiejsza do zaimplementowania bezpośrednio za pomocą RGB bez przechodzenia do wszelkiego rodzaju alternatywnych przestrzeni kolorów. Znalazłem tutaj tę formułę , która jest niedrogim przybliżeniem dość skomplikowanej rzeczywistej formuły (przez CIE, która jest W3C koloru, ponieważ jest to niedokończona misja, można tam znaleźć starsze i prostsze równania różnicy kolorów). powodzenia

Edycja: dla potomnych, oto odpowiedni kod C:

typedef struct {
     unsigned char r, g, b;
} RGB;

double ColourDistance(RGB e1, RGB e2)
{
    long rmean = ( (long)e1.r + (long)e2.r ) / 2;
    long r = (long)e1.r - (long)e2.r;
    long g = (long)e1.g - (long)e2.g;
    long b = (long)e1.b - (long)e2.b;
    return sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8));
}

ta metoda zadziałała dla mnie. Pomogło mi to znaleźć najbliższy kolor z listy nazw kolorów.
faisalbhagat

23

Wartość koloru ma więcej niż jeden wymiar, więc nie ma wewnętrznego sposobu na porównanie dwóch kolorów. Musisz określić dla swojego przypadku użycia znaczenie kolorów, a tym samym jak najlepiej je porównać.

Najprawdopodobniej będziesz chciał porównać właściwości barwy, nasycenia i / lub jasności kolorów w przeciwieństwie do składników czerwonego / zielonego / niebieskiego. Jeśli masz problem ze znalezieniem sposobu, w jaki chcesz je porównać, weź kilka próbek kolorów i porównaj je w myślach, a następnie spróbuj uzasadnić / wyjaśnić sobie, dlaczego są podobne / różne.

Kiedy już wiesz, jakie właściwości / składniki kolorów chcesz porównać, musisz dowiedzieć się, jak wydobyć te informacje z koloru.

Najprawdopodobniej wystarczy przekonwertować kolor ze zwykłej reprezentacji RedGreenBlue na HueSaturationLightness, a następnie obliczyć coś w rodzaju

avghue = (color1.hue + color2.hue)/2
distance = abs(color1.hue-avghue)

Ten przykład poda prostą wartość skalarną wskazującą, jak daleko od siebie znajdują się gradienty / odcień kolorów.

Zobacz HSL i HSV w Wikipedii .


2
Z rzeczy, które pamiętam z moich wykładów o tych rzeczach, przekonwertowałbym obraz na przestrzeń kolorów Lab, a nie HSV / HSL. Jakieś powody, dla których warto wybrać ten?
Voo

Nie. Najbardziej znane mi są RGB i HSL, więc wybrałem HSL tylko po to, by podkreślić, że „domyślny” RGB nie jest jedyną opcją - tak naprawdę zależy od aplikacji. Dziękuję za poinformowanie mnie o przestrzeni kolorów Lab.
Supr

1
W każdym razie dałem Ci +1, ponieważ podstawową zasadą jest tutaj „właściwa” odpowiedź (przekonwertuj przestrzeń kolorów, która jednolicie obsługuje postrzeganą różnicę, a następnie wykonaj porównanie). Nie jestem pewien, która przestrzeń byłaby najlepsza - wszystkie te różne przestrzenie kolorów są mylące jak diabli imo;)
Voo

21

Jeśli masz dwa Colorobiekty c1i c2można tylko porównać z każdą wartość RGB c1ze skutecznością c2.

int diffRed   = Math.abs(c1.getRed()   - c2.getRed());
int diffGreen = Math.abs(c1.getGreen() - c2.getGreen());
int diffBlue  = Math.abs(c1.getBlue()  - c2.getBlue());

Te wartości możesz po prostu podzielić przez ilość nasycenia różnicowego (255), a otrzymasz różnicę między nimi.

float pctDiffRed   = (float)diffRed   / 255;
float pctDiffGreen = (float)diffGreen / 255;
float pctDiffBlue   = (float)diffBlue  / 255;

Po czym możesz po prostu znaleźć średnią różnicę kolorów w procentach.

(pctDiffRed + pctDiffGreen + pctDiffBlue) / 3 * 100

Co dałoby różnicę w procentach między c1a c2.


Jeszcze 2 pomniejsze rzeczy: <b> 1 </b> pctDiffRed = diffRed / 255;da ci 0, chyba że rzucisz gdzieś na pływak. <b> 2 </b> Musisz pomnożyć gdzieś przez 100, aby uzyskać procent.
vaughandroid

18
Może to nie dawać najlepszej „widocznej” różnicy, ponieważ ludzkie oko inaczej postrzega zmiany koloru. Biorąc to pod uwagę, domyślam się, że właśnie tego szuka, ponieważ prawdopodobnie szuka równie wymiernej różnicy, a nie dostrzeganej różnicy. Pomyślałem, że mogę to tutaj wziąć pod uwagę na wypadek, gdyby było to istotne.
Beska

14

Jedną z najlepszych metod porównywania dwóch kolorów na podstawie ludzkiej percepcji jest CIE76. Różnica nazywa się Delta-E. Kiedy jest mniejsza niż 1, ludzkie oko nie może rozpoznać różnicy.

Istnieje wspaniała klasa narzędzi koloru ColorUtils (kod poniżej), która zawiera metody porównawcze CIE76. Napisał go Daniel Strebel z Uniwersytetu w Zurychu.

Z ColorUtils.class korzystam z metody:

static double colorDifference(int r1, int g1, int b1, int r2, int g2, int b2)

r1, g1, b1 - wartości RGB pierwszego koloru

r2, g2, b2 - wartości RGB drugiego koloru, który chcesz porównać

Jeśli pracujesz z Androidem, możesz uzyskać następujące wartości:

r1 = Color.red(pixel);

g1 = Color.green(pixel);

b1 = Color.blue(pixel);


ColorUtils.class, Daniel Strebel, Uniwersytet w Zurychu:

import android.graphics.Color;

public class ColorUtil {
public static int argb(int R, int G, int B) {
    return argb(Byte.MAX_VALUE, R, G, B);
}

public static int argb(int A, int R, int G, int B) {
    byte[] colorByteArr = {(byte) A, (byte) R, (byte) G, (byte) B};
    return byteArrToInt(colorByteArr);
}

public static int[] rgb(int argb) {
    return new int[]{(argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF};
}

public static int byteArrToInt(byte[] colorByteArr) {
    return (colorByteArr[0] << 24) + ((colorByteArr[1] & 0xFF) << 16)
            + ((colorByteArr[2] & 0xFF) << 8) + (colorByteArr[3] & 0xFF);
}

public static int[] rgb2lab(int R, int G, int B) {
    //http://www.brucelindbloom.com

    float r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr;
    float Ls, as, bs;
    float eps = 216.f / 24389.f;
    float k = 24389.f / 27.f;

    float Xr = 0.964221f;  // reference white D50
    float Yr = 1.0f;
    float Zr = 0.825211f;

    // RGB to XYZ
    r = R / 255.f; //R 0..1
    g = G / 255.f; //G 0..1
    b = B / 255.f; //B 0..1

    // assuming sRGB (D65)
    if (r <= 0.04045)
        r = r / 12;
    else
        r = (float) Math.pow((r + 0.055) / 1.055, 2.4);

    if (g <= 0.04045)
        g = g / 12;
    else
        g = (float) Math.pow((g + 0.055) / 1.055, 2.4);

    if (b <= 0.04045)
        b = b / 12;
    else
        b = (float) Math.pow((b + 0.055) / 1.055, 2.4);


    X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b;
    Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b;
    Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b;

    // XYZ to Lab
    xr = X / Xr;
    yr = Y / Yr;
    zr = Z / Zr;

    if (xr > eps)
        fx = (float) Math.pow(xr, 1 / 3.);
    else
        fx = (float) ((k * xr + 16.) / 116.);

    if (yr > eps)
        fy = (float) Math.pow(yr, 1 / 3.);
    else
        fy = (float) ((k * yr + 16.) / 116.);

    if (zr > eps)
        fz = (float) Math.pow(zr, 1 / 3.);
    else
        fz = (float) ((k * zr + 16.) / 116);

    Ls = (116 * fy) - 16;
    as = 500 * (fx - fy);
    bs = 200 * (fy - fz);

    int[] lab = new int[3];
    lab[0] = (int) (2.55 * Ls + .5);
    lab[1] = (int) (as + .5);
    lab[2] = (int) (bs + .5);
    return lab;
}

/**
 * Computes the difference between two RGB colors by converting them to the L*a*b scale and
 * comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76}
 */
public static double getColorDifference(int a, int b) {
    int r1, g1, b1, r2, g2, b2;
    r1 = Color.red(a);
    g1 = Color.green(a);
    b1 = Color.blue(a);
    r2 = Color.red(b);
    g2 = Color.green(b);
    b2 = Color.blue(b);
    int[] lab1 = rgb2lab(r1, g1, b1);
    int[] lab2 = rgb2lab(r2, g2, b2);
    return Math.sqrt(Math.pow(lab2[0] - lab1[0], 2) + Math.pow(lab2[1] - lab1[1], 2) + Math.pow(lab2[2] - lab1[2], 2));
}
}

powyższy kod zawiera błąd w rgb2lab: dzielenie przez 12 należy zastąpić dzieleniem przez 12,92 w konwersji r, gib. w przeciwnym razie funkcja nie jest ciągła przy r = 0,04045
John Smith

10

Jeszcze inna odpowiedź, choć podobna do tej Supr - po prostu inna przestrzeń kolorów.

Rzecz w tym, że ludzie postrzegają różnicę kolorów niejednolicie, a przestrzeń kolorów RGB to ignoruje. W rezultacie, jeśli użyjesz przestrzeni kolorów RGB i po prostu obliczysz odległość euklidesową między 2 kolorami, możesz otrzymać różnicę, która jest matematycznie absolutnie poprawna, ale nie pokrywa się z tym, co powiedzą ci ludzie.

To może nie być problemem - myślę, że różnica nie jest aż tak duża, ale jeśli chcesz rozwiązać ten „lepiej”, powinieneś przekonwertować kolory RGB na przestrzeń kolorów, która została specjalnie zaprojektowana, aby uniknąć powyższego problemu. Jest kilka z nich, ulepszeń z wcześniejszych modeli (ponieważ jest to oparte na ludzkiej percepcji, musimy zmierzyć „prawidłowe” wartości na podstawie danych eksperymentalnych). Jest przestrzeń kolorów Lab, która moim zdaniem byłaby najlepsza, chociaż trochę skomplikowana do konwersji. Prostszy byłby CIE XYZ .

Oto witryna zawierająca formuły do ​​konwersji między różnymi przestrzeniami kolorów , abyś mógł trochę poeksperymentować.


3

Wszystkie poniższe metody dają wynik w skali od 0-100.

internal static class ColorDifference
{
    internal enum Method
    {
        Binary, // true or false, 0 is false
        Square,
        Dimensional,
        CIE76
    }

    public static double Calculate(Method method, int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return Calculate(method, c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]);
    }

    public static double Calculate(Method method, int r1, int r2, int g1, int g2, int b1, int b2, int a1 = -1, int a2 = -1)
    {
        switch (method)
        {
            case Method.Binary:
                return (r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2) ? 0 : 100;
            case Method.CIE76:
                return CalculateCIE76(r1, r2, g1, g2, b1, b2);
            case Method.Dimensional:
                if (a1 == -1 || a2 == -1) return Calculate3D(r1, r2, g1, g2, b1, b2);
                else return Calculate4D(r1, r2, g1, g2, b1, b2, a1, a2);
            case Method.Square:
                return CalculateSquare(r1, r2, g1, g2, b1, b2, a1, a2);
            default:
                throw new InvalidOperationException();
        }
    }

    public static double Calculate(Method method, Color c1, Color c2, bool alpha)
    {
        switch (method)
        {
            case Method.Binary:
                return (c1.R == c2.R && c1.G == c2.G && c1.B == c2.B && (!alpha || c1.A == c2.A)) ? 0 : 100;
            case Method.CIE76:
                if (alpha) throw new InvalidOperationException();
                return CalculateCIE76(c1, c2);
            case Method.Dimensional:
                if (alpha) return Calculate4D(c1, c2);
                else return Calculate3D(c1, c2);
            case Method.Square:
                if (alpha) return CalculateSquareAlpha(c1, c2);
                else return CalculateSquare(c1, c2);
            default:
                throw new InvalidOperationException();
        }
    }

    // A simple idea, based on on a Square
    public static double CalculateSquare(int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return CalculateSquare(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3]);
    }

    public static double CalculateSquare(Color c1, Color c2)
    {
        return CalculateSquare(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B);
    }

    public static double CalculateSquareAlpha(int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return CalculateSquare(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]);
    }

    public static double CalculateSquareAlpha(Color c1, Color c2)
    {
        return CalculateSquare(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B, c1.A, c2.A);
    }

    public static double CalculateSquare(int r1, int r2, int g1, int g2, int b1, int b2, int a1 = -1, int a2 = -1)
    {
        if (a1 == -1 || a2 == -1) return (Math.Abs(r1 - r2) + Math.Abs(g1 - g2) + Math.Abs(b1 - b2)) / 7.65;
        else return (Math.Abs(r1 - r2) + Math.Abs(g1 - g2) + Math.Abs(b1 - b2) + Math.Abs(a1 - a2)) / 10.2;
    }

    // from:http://stackoverflow.com/questions/9018016/how-to-compare-two-colors
    public static double Calculate3D(int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return Calculate3D(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3]);
    }

    public static double Calculate3D(Color c1, Color c2)
    {
        return Calculate3D(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B);
    }

    public static double Calculate3D(int r1, int r2, int g1, int g2, int b1, int b2)
    {
        return Math.Sqrt(Math.Pow(Math.Abs(r1 - r2), 2) + Math.Pow(Math.Abs(g1 - g2), 2) + Math.Pow(Math.Abs(b1 - b2), 2)) / 4.41672955930063709849498817084;
    }

    // Same as above, but made 4D to include alpha channel
    public static double Calculate4D(int argb1, int argb2)
    {
        int[] c1 = ColorConversion.ArgbToArray(argb1);
        int[] c2 = ColorConversion.ArgbToArray(argb2);
        return Calculate4D(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]);
    }

    public static double Calculate4D(Color c1, Color c2)
    {
        return Calculate4D(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B, c1.A, c2.A);
    }

    public static double Calculate4D(int r1, int r2, int g1, int g2, int b1, int b2, int a1, int a2)
    {
        return Math.Sqrt(Math.Pow(Math.Abs(r1 - r2), 2) + Math.Pow(Math.Abs(g1 - g2), 2) + Math.Pow(Math.Abs(b1 - b2), 2) + Math.Pow(Math.Abs(a1 - a2), 2)) / 5.1;
    }

    /**
    * Computes the difference between two RGB colors by converting them to the L*a*b scale and
    * comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76}
    */
    public static double CalculateCIE76(int argb1, int argb2)
    {
        return CalculateCIE76(Color.FromArgb(argb1), Color.FromArgb(argb2));
    }

    public static double CalculateCIE76(Color c1, Color c2)
    {
        return CalculateCIE76(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B);
    }

    public static double CalculateCIE76(int r1, int r2, int g1, int g2, int b1, int b2)
    {
        int[] lab1 = ColorConversion.ColorToLab(r1, g1, b1);
        int[] lab2 = ColorConversion.ColorToLab(r2, g2, b2);
        return Math.Sqrt(Math.Pow(lab2[0] - lab1[0], 2) + Math.Pow(lab2[1] - lab1[1], 2) + Math.Pow(lab2[2] - lab1[2], 2)) / 2.55;
    }
}


internal static class ColorConversion
{

    public static int[] ArgbToArray(int argb)
    {
        return new int[] { (argb >> 24), (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF };
    }

    public static int[] ColorToLab(int R, int G, int B)
    {
        // http://www.brucelindbloom.com

        double r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr;
        double Ls, fas, fbs;
        double eps = 216.0f / 24389.0f;
        double k = 24389.0f / 27.0f;

        double Xr = 0.964221f;  // reference white D50
        double Yr = 1.0f;
        double Zr = 0.825211f;

        // RGB to XYZ
        r = R / 255.0f; //R 0..1
        g = G / 255.0f; //G 0..1
        b = B / 255.0f; //B 0..1

        // assuming sRGB (D65)
        if (r <= 0.04045) r = r / 12;
        else r = (float)Math.Pow((r + 0.055) / 1.055, 2.4);

        if (g <= 0.04045) g = g / 12;
        else g = (float)Math.Pow((g + 0.055) / 1.055, 2.4);

        if (b <= 0.04045) b = b / 12;
        else b = (float)Math.Pow((b + 0.055) / 1.055, 2.4);

        X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b;
        Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b;
        Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b;

        // XYZ to Lab
        xr = X / Xr;
        yr = Y / Yr;
        zr = Z / Zr;

        if (xr > eps) fx = (float)Math.Pow(xr, 1 / 3.0);
        else fx = (float)((k * xr + 16.0) / 116.0);

        if (yr > eps) fy = (float)Math.Pow(yr, 1 / 3.0);
        else fy = (float)((k * yr + 16.0) / 116.0);

        if (zr > eps) fz = (float)Math.Pow(zr, 1 / 3.0);
        else fz = (float)((k * zr + 16.0) / 116);

        Ls = (116 * fy) - 16;
        fas = 500 * (fx - fy);
        fbs = 200 * (fy - fz);

        int[] lab = new int[3];
        lab[0] = (int)(2.55 * Ls + 0.5);
        lab[1] = (int)(fas + 0.5);
        lab[2] = (int)(fbs + 0.5);
        return lab;
    }
}

2

Najlepszym sposobem jest deltaE. DeltaE to liczba pokazująca różnicę kolorów. Jeśli deltae <1, to różnicy nie można rozpoznać ludzkimi oczami. Napisałem kod w kanwie i js do konwersji rgb na lab, a następnie obliczenia delta e. W tym przykładzie kod rozpoznaje piksele, które mają inny kolor, z kolorem podstawowym, który zapisałem jako LAB1. a jeśli jest inny, sprawia, że ​​piksele są czerwone. Możesz zwiększyć lub zmniejszyć czułość różnicy kolorów, zwiększając lub zmniejszając dopuszczalny zakres delta e. W tym przykładzie przypisałem 10 dla deltaE w linii, którą napisałem (deltae <= 10):

<script>   
  var constants = {
    canvasWidth: 700, // In pixels.
    canvasHeight: 600, // In pixels.
    colorMap: new Array() 
          };



  // -----------------------------------------------------------------------------------------------------

  function fillcolormap(imageObj1) {


    function rgbtoxyz(red1,green1,blue1){ // a converter for converting rgb model to xyz model
 var red2 = red1/255;
 var green2 = green1/255;
 var blue2 = blue1/255;
 if(red2>0.04045){
      red2 = (red2+0.055)/1.055;
      red2 = Math.pow(red2,2.4);
 }
 else{
      red2 = red2/12.92;
 }
 if(green2>0.04045){
      green2 = (green2+0.055)/1.055;
      green2 = Math.pow(green2,2.4);    
 }
 else{
      green2 = green2/12.92;
 }
 if(blue2>0.04045){
      blue2 = (blue2+0.055)/1.055;
      blue2 = Math.pow(blue2,2.4);    
 }
 else{
      blue2 = blue2/12.92;
 }
 red2 = (red2*100);
 green2 = (green2*100);
 blue2 = (blue2*100);
 var x = (red2 * 0.4124) + (green2 * 0.3576) + (blue2 * 0.1805);
 var y = (red2 * 0.2126) + (green2 * 0.7152) + (blue2 * 0.0722);
 var z = (red2 * 0.0193) + (green2 * 0.1192) + (blue2 * 0.9505);
 var xyzresult = new Array();
 xyzresult[0] = x;
 xyzresult[1] = y;
 xyzresult[2] = z;
 return(xyzresult);
} //end of rgb_to_xyz function
function xyztolab(xyz){ //a convertor from xyz to lab model
 var x = xyz[0];
 var y = xyz[1];
 var z = xyz[2];
 var x2 = x/95.047;
 var y2 = y/100;
 var z2 = z/108.883;
 if(x2>0.008856){
      x2 = Math.pow(x2,1/3);
 }
 else{
      x2 = (7.787*x2) + (16/116);
 }
 if(y2>0.008856){
      y2 = Math.pow(y2,1/3);
 }
 else{
      y2 = (7.787*y2) + (16/116);
 }
 if(z2>0.008856){
      z2 = Math.pow(z2,1/3);
 }
 else{
      z2 = (7.787*z2) + (16/116);
 }
 var l= 116*y2 - 16;
 var a= 500*(x2-y2);
 var b= 200*(y2-z2);
 var labresult = new Array();
 labresult[0] = l;
 labresult[1] = a;
 labresult[2] = b;
 return(labresult);

}

    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');
    var imageX = 0;
    var imageY = 0;

    context.drawImage(imageObj1, imageX, imageY, 240, 140);
    var imageData = context.getImageData(0, 0, 240, 140);
    var data = imageData.data;
    var n = data.length;
   // iterate over all pixels

    var m = 0;
    for (var i = 0; i < n; i += 4) {
      var red = data[i];
      var green = data[i + 1];
      var blue = data[i + 2];
    var xyzcolor = new Array();
    xyzcolor = rgbtoxyz(red,green,blue);
    var lab = new Array();
    lab = xyztolab(xyzcolor);
    constants.colorMap.push(lab); //fill up the colormap array with lab colors.         
      } 

  }

// ------------------------------------------------ -------------------------------------------------- ---

    function colorize(pixqty) {

         function deltae94(lab1,lab2){    //calculating Delta E 1994

         var c1 = Math.sqrt((lab1[1]*lab1[1])+(lab1[2]*lab1[2]));
         var c2 =  Math.sqrt((lab2[1]*lab2[1])+(lab2[2]*lab2[2]));
         var dc = c1-c2;
         var dl = lab1[0]-lab2[0];
         var da = lab1[1]-lab2[1];
         var db = lab1[2]-lab2[2];
         var dh = Math.sqrt((da*da)+(db*db)-(dc*dc));
         var first = dl;
         var second = dc/(1+(0.045*c1));
         var third = dh/(1+(0.015*c1));
         var deresult = Math.sqrt((first*first)+(second*second)+(third*third));
         return(deresult);
          } // end of deltae94 function
    var lab11 =  new Array("80","-4","21");
    var lab12 = new Array();
    var k2=0;
    var canvas = document.getElementById('myCanvas');
                                        var context = canvas.getContext('2d');
                                        var imageData = context.getImageData(0, 0, 240, 140);
                                        var data = imageData.data;

    for (var i=0; i<pixqty; i++) {

    lab12 = constants.colorMap[i];

    var deltae = deltae94(lab11,lab12);     
                                        if (deltae <= 10) {

                                        data[i*4] = 255;
                                        data[(i*4)+1] = 0;
                                        data[(i*4)+2] = 0;  
                                        k2++;
                                        } // end of if 
                                } //end of for loop
    context.clearRect(0,0,240,140);
    alert(k2);
    context.putImageData(imageData,0,0);
} 
// -----------------------------------------------------------------------------------------------------

$(window).load(function () {    
  var imageObj = new Image();
  imageObj.onload = function() {
  fillcolormap(imageObj);    
  }
  imageObj.src = './mixcolor.png';
});

// ---------------------------------------------------------------------------------------------------
 var pixno2 = 240*140; 
 </script>

1
Trochę martwię się niektórymi z twoich podziałów całkowitych. 1/3i 16/116oba oceniają 0, co prawie na pewno nie jest tym, czego chcesz. Prawdopodobnie twój algorytm jest poprawny, ale twój kod z pewnością nie.
Dawood ibn Kareem

Opisujesz CIE-LAB dE94. Delta E oznacza zmianę w euklidesie. Oznacza to, że w standardowej przestrzeni kolorów Lab, odległość euklidesowa określona przez bardzo standardową formułę odległości euklidesowej. Podczas gdy modyfikacje Delta E, a mianowicie 76, 94, 2000 (jest też Delta E, CMC, który jest używany do tekstyliów i tym podobnych), to różne wzory odległości między pozycjami w przestrzeni kolorów Lab. Kod laboratorium jest taki sam w każdym, kod różnicy kolorów nie. . Krótko mówiąc, Delta E nie jest tym, co się nazywa.
Tatarize

2

Prostą metodą, która używa tylko RGB, jest

cR=R1-R2 
cG=G1-G2 
cB=B1-B2 
uR=R1+R2 
distance=cR*cR*(2+uR/256) + cG*cG*4 + cB*cB*(2+(255-uR)/256)

Używam tego od jakiegoś czasu i działa wystarczająco dobrze w większości zastosowań.


Korzystając z powyższego wzoru, jaki jest zakres wartości odległości
Aman Aggarwal,

jest to bardzo zbliżone do przybliżenia różnicy kolorów u Euklidesa. Domyślam się, że pomija składnik główny, aby przyspieszyć obliczenia, więc jest to zakres od 0 do 100 ^ 3. Jeśli chcesz znormalizować do 100, zrób dystans do potęgi1/3
Daniel

2

Użyłem tego w moim Androidzie i wydaje się satysfakcjonujące, chociaż przestrzeń RGB nie jest zalecana:

    public double colourDistance(int red1,int green1, int blue1, int red2, int green2, int blue2)
{
      double rmean = ( red1 + red2 )/2;
    int r = red1 - red2;
    int g = green1 - green2;
    int b = blue1 - blue2;
    double weightR = 2 + rmean/256;
    double weightG = 4.0;
    double weightB = 2 + (255-rmean)/256;
    return Math.sqrt(weightR*r*r + weightG*g*g + weightB*b*b);
}

Następnie użyłem poniższego, aby uzyskać procent podobieństwa:

double maxColDist = 764.8339663572415;
double d1 = colourDistance(red1,green1,blue1,red2,green2,blue2);
String s1 = (int) Math.round(((maxColDist-d1)/maxColDist)*100) + "% match";

Działa wystarczająco dobrze.


2

Wypróbowałem różne metody, takie jak przestrzeń kolorów LAB, porównania HSV i odkryłem, że jasność działa całkiem dobrze w tym celu.

Oto wersja Pythona

def lum(c):
    def factor(component):
        component = component / 255;
        if (component <= 0.03928):
            component = component / 12.92;
        else:
            component = math.pow(((component + 0.055) / 1.055), 2.4);

        return component
    components = [factor(ci) for ci in c]

    return (components[0] * 0.2126 + components[1] * 0.7152 + components[2] * 0.0722) + 0.05;

def color_distance(c1, c2):

    l1 = lum(c1)
    l2 = lum(c2)
    higher = max(l1, l2)
    lower = min(l1, l2)

    return (higher - lower) / higher


c1 = ImageColor.getrgb('white')
c2 = ImageColor.getrgb('yellow')
print(color_distance(c1, c2))

Da tobie

0.0687619047619048

Jakie jest pochodzenie ImageColor? edytuj Znalazłem, to jestfrom PIL import ImageColor
ademar111190

Czy jasność nie jest jasnością koloru? Więc w tym przypadku kolor zielony, niebieski i czerwony nie byłyby zgłaszane jako różne, o ile jasność jest taka sama?
Peter B.

1

Spodziewam się, że na końcu zechcesz przeanalizować cały obraz, prawda? Możesz więc sprawdzić najmniejszą / najwyższą różnicę w matrycy kolorów tożsamości.

Większość operacji matematycznych do przetwarzania grafiki wykorzystuje macierze, ponieważ możliwe algorytmy, które ich używają, są często szybsze niż klasyczne obliczenia punkt po punkcie, odległość i porównanie. (np. dla operacji wykorzystujących DirectX, OpenGL, ...)

Więc myślę, że powinieneś zacząć tutaj:

http://en.wikipedia.org/wiki/Identity_matrix

http://en.wikipedia.org/wiki/Matrix_difference_equation

... i jak skomentował już Beska powyżej:

To może nie dawać najlepszej "widocznej" różnicy ...

Co oznacza również, że twój algorytm zależy od twojej definicji "podobnego do", jeśli przetwarzasz obrazy.


1

Wersja Kotlin, z jakim procentem chcesz dopasować.

Wywołanie metody z opcjonalnym argumentem procentowym

isMatchingColor(intColor1, intColor2, 95) // should match color if 95% similar

Treść metody

private fun isMatchingColor(intColor1: Int, intColor2: Int, percent: Int = 90): Boolean {
    val threadSold = 255 - (255 / 100f * percent)

    val diffAlpha = abs(Color.alpha(intColor1) - Color.alpha(intColor2))
    val diffRed = abs(Color.red(intColor1) - Color.red(intColor2))
    val diffGreen = abs(Color.green(intColor1) - Color.green(intColor2))
    val diffBlue = abs(Color.blue(intColor1) - Color.blue(intColor2))

    if (diffAlpha > threadSold) {
        return false
    }

    if (diffRed > threadSold) {
        return false
    }

    if (diffGreen > threadSold) {
        return false
    }

    if (diffBlue > threadSold) {
        return false
    }

    return true
}

0

Będziesz musiał przekonwertować wszystkie kolory RGB na przestrzeń kolorów Lab, aby móc je porównać tak, jak widzą je ludzie. W przeciwnym razie kolory RGB będą się „pasować” w bardzo dziwny sposób.

Łącze do Wikipedii na temat Różnice kolorów zawiera wprowadzenie do różnych algorytmów różnic w przestrzeni kolorów w Laboratorium, które zostały zdefiniowane przez lata. Najprostszy, który sprawdza tylko odległość euklidesową dwóch kolorów laboratoryjnych, działa, ale ma kilka wad.

Dogodnie istnieje implementacja bardziej wyrafinowanego algorytmu CIEDE2000 w projekcie OpenIMAJ w języku Java . Podaj mu swoje dwa zestawy kolorów Lab, a otrzymasz pojedynczą wartość odległości.


0

Jedynym „właściwym” sposobem porównania kolorów jest zrobienie tego z deltaE w CIELab lub CIELuv.

Ale w przypadku wielu aplikacji myślę, że jest to wystarczająco dobre przybliżenie:

distance = 3 * |dR| + 4 * |dG| + 3 * |dB|

Myślę, że ważona odległość do Manhattanu ma dużo większy sens przy porównywaniu kolorów. Pamiętaj, że kolory podstawowe są tylko w naszej głowie. Nie mają żadnego fizycznego znaczenia. CIELab i CIELuv są modelowane statystycznie na podstawie naszego postrzegania koloru.


0

Na szybkie i brudne możesz to zrobić

import java.awt.Color;
private Color dropPrecision(Color c,int threshold){
    return new Color((c.getRed()/threshold),
                     (c.getGreen()/threshold),
                     (c.getBlue()/threshold));
}
public boolean inThreshold(Color _1,Color _2,int threshold){
    return dropPrecision(_1,threshold)==dropPrecision(_2,threshold);
}

wykorzystanie dzielenia liczb całkowitych do kwantyzacji kolorów.


0

Szybka odpowiedź 5

Znalazłem ten wątek, ponieważ potrzebowałem szybkiej wersji tego pytania. Ponieważ nikt nie odpowiedział na rozwiązanie, oto moje:

extension UIColor {

    var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        getRed(&red, green: &green, blue: &blue, alpha: &alpha)

        return (red, green, blue, alpha)
    }

    func isSimilar(to colorB: UIColor) -> Bool {
        let rgbA = self.rgba
        let rgbB = colorB.rgba

        let diffRed = abs(CGFloat(rgbA.red) - CGFloat(rgbB.red))
        let diffGreen = abs(rgbA.green - rgbB.green)
        let diffBlue = abs(rgbA.blue - rgbB.blue)

        let pctRed = diffRed
        let pctGreen = diffGreen
        let pctBlue = diffBlue

        let pct = (pctRed + pctGreen + pctBlue) / 3 * 100

        return pct < 10 ? true : false
    }
}

Stosowanie:

let black: UIColor = UIColor.black
let white: UIColor = UIColor.white

let similar: Bool = black.isSimilar(to: white)

Ustawiłem mniej niż 10% różnicy, aby zwrócić podobne kolory, ale możesz to dostosować samodzielnie.


0

Android dla ColorUtils API RGBToHSL: Miałem dwa kolory int argb ( color1, color2 ) i chciałem uzyskać odległość / różnicę między tymi dwoma kolorami. Oto, co zrobiłem;

private float getHue(int color) {
    int R = (color >> 16) & 0xff;
    int G = (color >>  8) & 0xff;
    int B = (color      ) & 0xff;
    float[] colorHue = new float[3];
    ColorUtils.RGBToHSL(R, G, B, colorHue);
    return colorHue[0];
}

Następnie użyłem poniższego kodu, aby znaleźć odległość między dwoma kolorami.

private float getDistance(getHue(color1), getHue(color2)) {
    float avgHue = (hue1 + hue2)/2;
    return Math.abs(hue1 - avgHue);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.