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