Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which is most accurate way to distinguish one of 8 colors?

Tags:

python

rgb

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?

like image 908
Graf Avatar asked Aug 25 '10 10:08

Graf


2 Answers

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.

like image 115
Bolo Avatar answered Oct 26 '22 14:10

Bolo


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.

like image 21
Himel Das Avatar answered Oct 26 '22 13:10

Himel Das