32

I'm trying to find a way to compare two colors to find out how much they are alike. I can't seem to find any resources about the subject so I'm hoping to get some pointers here.

Idealy, I would like to get a score that tells how much they are alike. For example, 0 to 100, where 100 would be equal and 0 would be totally different.

Thanks!

Edit:

Getting to know a bit more about colors from the answers I understand my question was a bit vague. I will try to explain what I needed this for.

I have pixeldata (location and color) of an application window at 800x600 size so I can find out if a certain window is open or not by checking every x-interval.

However, this method fails as soon as the application is resized (the contents are scaled, not moved). I can calculate where the pixels move, but because of rounding and antialising the color can be slightly different.

Pieter's solution was good enough for me in this case, although all other responses were extremely helpfull as well, so I just upvoted everyone. I do think that ColorEye's answer is the most accurate when looking at this from a professional way, so I marked it as the answer.

SaphuA
  • 3,092
  • 3
  • 39
  • 58

8 Answers8

26

What you are looking for is called Delta-E.

http://www.colorwiki.com/wiki/Delta_E:_The_Color_Difference

It is the distance between two colors in LAB color space. It is said that the human eye cannot distinguish colors below 1 DeltaE (I find that my eyes can find differences in colors below 1 DeltaE, each person is different.)

There are 4 formulas for 'color difference'.

  • Delta E (CIE 1976)
  • Delta E (CIE 1994)
  • Delta E (CIE 2000)
  • Delta E (CMC)

Check the math link on this site:

So the proper answer is to convert your RGB to LAB using the formula given, then use DeltaE 1976 to determine the 'difference' in your colors. A result of 0 would indicate identical colors. Any value higher than 0 could be judged by the rule 'A delta e of 1 or less is indistinguishable by most people'.

JYelton
  • 35,664
  • 27
  • 132
  • 191
ColorEyes
  • 276
  • 2
  • 3
  • 2
    Thanks for your reply, I was afraid it would be something as complicated as this. I will mark your answer since it is the most accurate, although will probably go for a solution much like Pieter's. – SaphuA Oct 19 '10 at 15:23
  • 1
    This site has some useful conversion algorithms (see http://www.easyrgb.com/index.php?X=MATH and http://www.easyrgb.com/index.php?X=DELT). – rsbarro Aug 27 '12 at 21:00
  • 1
    Note that in Lab color space CIE 1976 is just euclidean distance between points, so `DE = sqrt((L2-L1)^2 + (a2-a1)^2 + (b2-b1)^2)`. – hruske Feb 27 '16 at 14:35
19

There's an open-source .net library that lets you do this easily: https://github.com/hvalidi/ColorMine

The most common method for comparing colors is CIE76:

var a = new Rgb { R = 149, G = 13, B = 12 }
var b = new Rgb { R = 255, G = 13, B = 12 }

var deltaE = a.Compare(b,new Cie1976Comparison());
Whiletrue
  • 551
  • 1
  • 7
  • 18
Joe Zack
  • 3,268
  • 2
  • 31
  • 37
14

Colors have different weights affecting human eye. So convert the colors to grayscale using their calculated weights:

Gray Color = .11 * B + .59 * G + .30 * R

And your difference will be

difference = (GrayColor1 - GrayColor2) * 100.0 / 255.0

with difference ranging from 0-100.

This is actually commonly used and very simple approach thats used calculating image differences in image procesing.

-edit this is the very simple and still usable formula - even in commercial applications. If you want to go deep you should check out the color difference methods called: CIE1976, CIE1994, CIE2000 and CMC Here you can find some more detailed info: http://en.wikipedia.org/wiki/Color_difference

Rev
  • 5,827
  • 4
  • 27
  • 51
honibis
  • 811
  • 1
  • 5
  • 13
  • 2
    Yep thats right. But its basic and it works in the real world. Edited the answer with some more info. – honibis Oct 19 '10 at 14:04
  • To have a result ranging from 0-100, the divider needs to be 255 not 256. I will fix the answer. – Rev May 07 '20 at 12:21
  • 1
    The range can not be 0-100 because if you have RGB = (0,0,0) and compare it against another color, the result will always be negative. So the actual range wold be -100 - 100, unless some info in the text is missing. – Devolus Nov 14 '20 at 14:58
11

Something like this:

    public static int CompareColors(Color a, Color b)
    {
        return 100 * (int)(
            1.0 - ((double)(
                Math.Abs(a.R - b.R) +
                Math.Abs(a.G - b.G) +
                Math.Abs(a.B - b.B)
            ) / (256.0 * 3))
        );
    }
Pieter van Ginkel
  • 29,160
  • 8
  • 71
  • 111
  • Thanks, I think I can work from this for now, although I will need to see how accurate this is. There are a few mistakes in your code though (256 should be 255 and casting the result to an int isn't very smart either :D) – SaphuA Oct 19 '10 at 12:33
  • 2
    While mathematically sound this is not a good idea since it does not take into account how colors are perceived. You can easily find pairs of colors that are similar but will yield a low score, and colors that are dissimilar but will yield a high score. – Hannes Ovrén Oct 19 '10 at 13:25
  • Yes you're right. You can go all out with this, but this is a quick and dirty way to get a proximate difference. I think the "correct" comparison methods would take a few pages :). – Pieter van Ginkel Oct 19 '10 at 13:35
  • I don't agree with whoever downvoted this answer. While not as accurate as ColorEyes's answer this was still usefull to me. – SaphuA Oct 19 '10 at 15:24
  • 3
    @SaphuA: It might be "correct" in some cases, but it is still very wrong. For a quick example, the colors 0x7f0000 and 0x007f00 will yield (approximately) the same results as comparing 0xb2b2b2 and 0xffffff. In other words: A bright red and bright green are considered to be just as close as white and a quite light grey. That is not likely a result you want. – Hannes Ovrén Aug 10 '11 at 14:47
11

Converting the RGB color to the HSL color space often produces good results. Check wikipedia for the conversion formula. It is up to you to assign weights to the differences in H, the color, S, how 'deep' the color is and L, how bright it is.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 2
    Weight L heavier than the others for sure. Our eyes are far more sensitive to changes in brightness than changes in color. – Brad Oct 19 '10 at 15:43
9

I found a interesting approach called Colour metric and adapted it to C#

public static double ColourDistance(Color e1, Color 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 Math.Sqrt((((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8));
}
fubo
  • 44,811
  • 17
  • 103
  • 137
7

Colour perception depends on many factors and similarity can be measured in many ways. Just comparing how similar the R, G and B components are generally gives results humans won't agree with.

There's some general material on colour comparisons in wikipedia, and on working with natural colour spaces in C# in this question.

Community
  • 1
  • 1
Pontus Gagge
  • 17,166
  • 1
  • 38
  • 51
0

I've translated the code for DeltaE2000 on Bruce Lindbloom's page into C.

Here:

     //
     //  deltae2000.c
     //
     //  Translated by Dr Cube on 10/1/16.
     //  Translated to C from this javascript code written by Bruce LindBloom:
     //    http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
     //    http://www.brucelindbloom.com/javascript/ColorDiff.js

     #include <stdio.h>
     #include <math.h>

     #define Lab2k struct Lab2kStruct
     Lab2k
     {
        float L;
        float a;
        float b;
     };

     // function expects Lab where: 0 >= L <=100.0 , -100 >=a <= 100.0  and  -100 >= b <= 100.0

     float
     DeltaE2000(Lab2k Lab1,Lab2k Lab2)
     {
        float kL = 1.0;
        float kC = 1.0;
        float kH = 1.0;
        float lBarPrime = 0.5 * (Lab1.L + Lab2.L);
        float c1 = sqrtf(Lab1.a * Lab1.a + Lab1.b * Lab1.b);
        float c2 = sqrtf(Lab2.a * Lab2.a + Lab2.b * Lab2.b);
        float cBar = 0.5 * (c1 + c2);
        float cBar7 = cBar * cBar * cBar * cBar * cBar * cBar * cBar;
        float g = 0.5 * (1.0 - sqrtf(cBar7 / (cBar7 + 6103515625.0)));  /* 6103515625 = 25^7 */
        float a1Prime = Lab1.a * (1.0 + g);
        float a2Prime = Lab2.a * (1.0 + g);
        float c1Prime = sqrtf(a1Prime * a1Prime + Lab1.b * Lab1.b);
        float c2Prime = sqrtf(a2Prime * a2Prime + Lab2.b * Lab2.b);
        float cBarPrime = 0.5 * (c1Prime + c2Prime);
        float h1Prime = (atan2f(Lab1.b, a1Prime) * 180.0) / M_PI;
        float dhPrime; // not initialized on purpose

        if (h1Prime < 0.0)
           h1Prime += 360.0;
        float h2Prime = (atan2f(Lab2.b, a2Prime) * 180.0) / M_PI;
        if (h2Prime < 0.0)
           h2Prime += 360.0;
        float hBarPrime = (fabsf(h1Prime - h2Prime) > 180.0) ? (0.5 * (h1Prime + h2Prime + 360.0)) : (0.5 * (h1Prime + h2Prime));
        float t = 1.0 -
        0.17 * cosf(M_PI * (      hBarPrime - 30.0) / 180.0) +
        0.24 * cosf(M_PI * (2.0 * hBarPrime       ) / 180.0) +
        0.32 * cosf(M_PI * (3.0 * hBarPrime +  6.0) / 180.0) -
        0.20 * cosf(M_PI * (4.0 * hBarPrime - 63.0) / 180.0);
        if (fabsf(h2Prime - h1Prime) <= 180.0)
           dhPrime = h2Prime - h1Prime;
        else
           dhPrime = (h2Prime <= h1Prime) ? (h2Prime - h1Prime + 360.0) : (h2Prime - h1Prime - 360.0);
        float dLPrime = Lab2.L - Lab1.L;
        float dCPrime = c2Prime - c1Prime;
        float dHPrime = 2.0 * sqrtf(c1Prime * c2Prime) * sinf(M_PI * (0.5 * dhPrime) / 180.0);
        float sL = 1.0 + ((0.015 * (lBarPrime - 50.0) * (lBarPrime - 50.0)) / sqrtf(20.0 + (lBarPrime - 50.0) * (lBarPrime - 50.0)));
        float sC = 1.0 + 0.045 * cBarPrime;
        float sH = 1.0 + 0.015 * cBarPrime * t;
        float dTheta = 30.0 * expf(-((hBarPrime - 275.0) / 25.0) * ((hBarPrime - 275.0) / 25.0));
        float cBarPrime7 = cBarPrime * cBarPrime * cBarPrime * cBarPrime * cBarPrime * cBarPrime * cBarPrime;
        float rC = sqrtf(cBarPrime7 / (cBarPrime7 + 6103515625.0));
        float rT = -2.0 * rC * sinf(M_PI * (2.0 * dTheta) / 180.0);
        return(sqrtf(
                           (dLPrime / (kL * sL)) * (dLPrime / (kL * sL)) +
                           (dCPrime / (kC * sC)) * (dCPrime / (kC * sC)) +
                           (dHPrime / (kH * sH)) * (dHPrime / (kH * sH)) +
                           (dCPrime / (kC * sC)) * (dHPrime / (kH * sH)) * rT
                      )
         );
     }