tags:

views:

116

answers:

4

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?

+4  A: 

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
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
+3  A: 

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')
gnibbler
That doesn't work to me, try it yourself with color (2,2,0), which is apparently black, your code says it orange.
Graf
+5  A: 

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 L*a*b*. 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
Excellent! python-colormath is what I needed! EXACTLY!
Graf
The python-colormath references http://www.brucelindbloom.com/ – an excellent source if you want to understand the math behind the conversions.
Bolo
A: 

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

THC4k
Maybe that works, thanks for your comment, but I a already found a better solution.
Graf
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
@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 ;-)
THC4k