7

Imagine we how some basic colors:

RED = Color ((196, 2, 51), "RED")
ORANGE = Color ((255, 165, 0), "ORANGE")
YELLOW = Color ((255, 205, 0), "YELLOW")
GREEN = Color ((0, 128, 0), "GREEN")
BLUE = Color ((0, 0, 255), "BLUE")
VIOLET = Color ((127, 0, 255), "VIOLET")
BLACK = Color ((0, 0, 0), "BLACK")
WHITE = Color ((255, 255, 255), "WHITE")

I want to have a function, which gets a 3-tuple as a parameter (like (206, 17, 38)), and it should return the color which it is. For instance, (206, 17, 38) is red, and (2, 2, 0) is black, and (0, 255, 0) is green. Which is most accurate way to choose one of 8 colors?

Graf
  • 1,437
  • 3
  • 17
  • 27

6 Answers6

13

Short answer: use the Euclidean distance in a device independent color space (source: Color difference article in Wikipedia). Since RGB is device-dependent, you should first map your colors to one of the device-independent color spaces.

I suggest to convert RGB to Lab*. To quote Wikipedia again:

Unlike the RGB and CMYK color models, Lab color is designed to approximate human vision.

Here's a recipe to do the conversion. Once you have the L, a, b values, calculate the Euclidean distance between your color and all the reference colors and choose the closest one.


Actually, the python-colormath Python module on Google Code (under GPL v3) is capable of converting between many different color spaces and calculates color differences as well.

Bolo
  • 11,542
  • 7
  • 41
  • 60
  • 1
    Excellent! python-colormath is what I needed! EXACTLY! – Graf Aug 25 '10 at 13:33
  • The python-colormath references http://www.brucelindbloom.com/ – an excellent source if you want to understand the math behind the conversions. – Bolo Aug 25 '10 at 13:36
5

I'm by no means a color expert, but I've been desperately looking for a RGB/HEX/HSV to simple color names converter in python. After doing some research I believe I made a formidable solution. According to IfLoop in this post:

If you end up using Cartesian distance to compare colors, you should normally translate the inputs into a linear, perceptual color space, such as Lab or Yuv. Neither RGB nor HSV are linear, and so cartesian distance doesn't much relate to similar two colors really are. – IfLoop Jul 27 '11 at 21:15

Therefore, Jochen Ritzel's code won't always return the proper color, as Graf pointed out. This is because both RGB and HSV are linear color spaces. We need to use a linear perceptual color space like YUV.

So what I did was I took Jochen Ritzel's code and replaced the rgb to hsv code with the rgb to yuv code based on this post.

colors = dict((
((196, 2, 51), "RED"),
((255, 165, 0), "ORANGE"),
((255, 205, 0), "YELLOW"),
((0, 128, 0), "GREEN"),
((0, 0, 255), "BLUE"),
((127, 0, 255), "VIOLET"),
((0, 0, 0), "BLACK"),
((255, 255, 255), "WHITE"),))

def rgb_to_ycc(r, g, b): #http://bit.ly/1blFUsF
    y = .299*r + .587*g + .114*b
    cb = 128 -.168736*r -.331364*g + .5*b
    cr = 128 +.5*r - .418688*g - .081312*b
    return y, cb, cr

def to_ycc( color ): 
    """ converts color tuples to floats and then to yuv """
    return rgb_to_ycc(*[x/255.0 for x in color])

def color_dist( c1, c2):
    """ returns the squared euklidian distance between two color vectors in yuv space """
    return sum( (a-b)**2 for a,b in zip(to_ycc(c1),to_ycc(c2)) )

def min_color_diff( color_to_match, colors):
    """ returns the `(distance, color_name)` with the minimal distance to `colors`"""
    return min( # overal best is the best match to any color:
        (color_dist(color_to_match, test), colors[test]) # (distance to `test` color, color name)
        for test in colors)

if __name__ == "__main__":
    r = input('r: ')
    g = input('g: ')
    b = input('b: ')
    color_to_match = (r, g, b)
    print min_color_diff( color_to_match, colors)
    input('Press enter to exit.')

Now we seem to end up with the right colors almost every time:

>>> color_to_match = (2, 2, 0) #Graf's test
>>> print min_color_diff( color_to_match, colors)
>>> 
(6.408043991348166e-05, 'BLACK')

More examples:

>>> color_to_match = (131, 26, 26)
>>> print min_color_diff( color_to_match, colors)
>>> 
(0.027661314571288835, 'RED')
>>> color_to_match = (69, 203, 136)
>>> print min_color_diff( color_to_match, colors)
>>> 
(0.11505647737959283, 'GREEN')

So far it appears that my version seems to be working almost perfectly, but please note: It is likely that if an rgb color is too bright or too dark, you will probably get returned 'WHITE' or 'BLACK.' To resolve this you will need to add lighter and darker colors to your colors dictionary. Also adding more colors like 'BROWN' and 'GRAY' (and so on) to the colors dictionary will also return better results.

Community
  • 1
  • 1
Himel Das
  • 1,148
  • 10
  • 13
  • 1
    This is mostly what the OP asked for if he/she wanted an actual source code. Most people forget the differences between the color spaces. RGB is for devices, and HSV is non-linear. Comparisons with euclidean should be made in YUV (Lab also works, but it's mostly made to compare with human vision). – dev_nut Jul 30 '15 at 20:22
  • Thanks for providing an answer. When I tried, (45, 106, 168), it printed GREEN. But in fact, that color is close to BLUE. – Indrajeet Jan 18 '16 at 14:05
3

Treat colors as vectors and count distance between the given and each of them and choose the one, which is the least. The simplest distance can be: |a1 - a2| + |b1 - b2| + |c1 - c2|.

Read this too: http://answers.yahoo.com/question/index?qid=20071202234050AAaDGLf, there is a better distance function described.

gruszczy
  • 40,948
  • 31
  • 128
  • 181
  • 2
    RGB is device-dependent and therefore it's not a good color space to measure color difference (see here: http://en.wikipedia.org/wiki/Color_difference) – Bolo Aug 25 '10 at 11:08
3

Use rgb_to_hsv to convert. Then match the color with the closet hue

For your example it would be RED because the hue matches exactly

>>> from colorsys import rgb_to_hsv
>>> rgb_to_hsv(192,2,51)
(0.83333333333333337, 0, 192)
>>> rgb_to_hsv(206, 17, 38)
(0.83333333333333337, 0, 206)
>>> 

Here's an example of how to find the closest match

>>> from colorsys import rgb_to_hsv
>>> 
>>> colors = dict((
...     ((196, 2, 51), "RED"),
...     ((255, 165, 0), "ORANGE"),
...     ((255, 205, 0), "YELLOW"),
...     ((0, 128, 0), "GREEN"),
...     ((0, 0, 255), "BLUE"),
...     ((127, 0, 255), "VIOLET"),
...     ((0, 0, 0), "BLACK"),
...     ((255, 255, 255), "WHITE"),))
>>> 
>>> color_to_match = (206,17,38)
>>> 
>>> print min((abs(rgb_to_hsv(*k)[0]-rgb_to_hsv(*color_to_match)[0]),v) for k,v in colors.items())
(0.0, 'RED')
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • That doesn't work to me, try it yourself with color (2,2,0), which is apparently black, your code says it orange. – Graf Aug 25 '10 at 12:51
1

I hope this is the way it's supposed to work: It converts the colors to hsv, then takes the (squared) euclidean distance to all available colors and returns the closest match.

Mostly a fixed version of gnibblers code.

from colorsys import rgb_to_hsv

colors = dict((
((196, 2, 51), "RED"),
((255, 165, 0), "ORANGE"),
((255, 205, 0), "YELLOW"),
((0, 128, 0), "GREEN"),
((0, 0, 255), "BLUE"),
((127, 0, 255), "VIOLET"),
((0, 0, 0), "BLACK"),
((255, 255, 255), "WHITE"),))

def to_hsv( color ): 
    """ converts color tuples to floats and then to hsv """
    return rgb_to_hsv(*[x/255.0 for x in color]) #rgb_to_hsv wants floats!

def color_dist( c1, c2):
    """ returns the squared euklidian distance between two color vectors in hsv space """
    return sum( (a-b)**2 for a,b in zip(to_hsv(c1),to_hsv(c2)) )

def min_color_diff( color_to_match, colors):
    """ returns the `(distance, color_name)` with the minimal distance to `colors`"""
    return min( # overal best is the best match to any color:
        (color_dist(color_to_match, test), colors[test]) # (distance to `test` color, color name)
        for test in colors)

color_to_match = (127, 255, 255)
print min_color_diff( color_to_match, colors)

All the funky list comprehension would look much better with a simple Color class that supports sorting and distance (but you can do that for practice ;-).

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • Maybe that works, thanks for your comment, but I a already found a better solution. – Graf Aug 25 '10 at 13:32
  • No that doesn't work as well, I tried color (2,2,0) and it said it is green. I think, that converting to hsv is not the best idea. It is generally suggested that you should only use euclidean distance on two Lab colors , not RGB colors, or hsv colors. – Graf Aug 25 '10 at 13:40
  • @Graf: thanks for the info. I guess we should leave stuff like that to people who actually know what they're doing and use the python-colormath module ;-) – Jochen Ritzel Aug 25 '10 at 14:36
  • Joseph and Graf, please check out my post below :) – Himel Das Feb 19 '14 at 16:21
0

The colors module of my Goulib library does this fairly well, and much more. It defines a Color class that can be inited from several color spaces, and grouped in a Palette dictionary. Several palettes are predefined, notably one that is indexed by the html/matplotlib names. Each Color automagically recieves a name from the index of the nearest color in this palette, measured in Lab space (deltaE)

see demo here http://nbviewer.jupyter.org/github/Goulu/Goulib/blob/master/notebooks/colors.ipynb

Dr. Goulu
  • 580
  • 7
  • 21