Take the 2-minute tour ×

I've made a function to find a color within a image, and return x, y. Now I need to add a new function, where I can find a color with a given tolerence. Should be easy?

Code to find color in image, and return x, y:

def FindColorIn(r,g,b, xmin, xmax, ymin, ymax):
    image = ImageGrab.grab()
    for x in range(xmin, xmax):
        for y in range(ymin,ymax):
            px = image.getpixel((x, y))
            if px[0] == r and px[1] == g and px[2] == b:
                return x, y

def FindColor(r,g,b):
    image = ImageGrab.grab()
    size = image.size
    pos = FindColorIn(r,g,b, 1, size[0], 1, size[1])
    return pos


Taken from the answers the normal methods of comparing two colors are in Euclidean distance, or Chebyshev distance.

I decided to mostly use (squared) euclidean distance, and multiple different color-spaces. LAB, deltaE (LCH), XYZ, HSL, and RGB. In my code, most color-spaces use squared euclidean distance to compute the difference.

For example with LAB, RGB and XYZ a simple squared euc. distance does the trick:

if ((X-X1)^2 + (Y-Y1)^2 + (Z-Z1)^2) <= (Tol^2) then

LCH, and HSL is a little more complicated as both have a cylindrical hue, but some piece of math solves that, then it's on to using squared eucl. here as well.

In most these cases I've added "separate parameters" for tolerance for each channel (using 1 global tolerance, and alternative "modifiers" HueTol := Tolerance * hueMod or LightTol := Tolerance * LightMod).

It seems like colorspaces built on top of XYZ (LAB, LCH) does perform best in many of my scenarios. Tho HSL yields very good results in some cases, and it's much cheaper to convert to from RGB, RGB is also great tho, and fills most of my needs.

share|improve this question
What have you tried? What didn't work? –  John Zwinck Jan 14 '12 at 17:15
You should return something if you don't find the color in the image. ie, an error code. –  jb. Jan 14 '12 at 17:17
How are you defining the tolerance? Separate ranges for r, g and b? –  Ricardo Cárdenes Jan 14 '12 at 17:17
I'm with John: what have you already tried? You might look at cosine similarity and search for Python implementations. –  Kurt McKee Jan 14 '12 at 17:20
@jb.: returning None is Pythonic, and that's what his code already does. –  John Zwinck Jan 14 '12 at 17:26

5 Answers 5

up vote 12 down vote accepted

Computing distances between RGB colours, in a way that's meaningful to the eye, isn't as easy a just taking the Euclidian distance between the two RGB vectors.

There is an interesting article about this here: http://www.compuphase.com/cmetric.htm

The example implementation in C is this:

typedef struct {
   unsigned char r, g, b;
} RGB;

double ColourDistance(RGB e1, RGB 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 sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8));

It shouldn't be too difficult to port to Python.


Alternatively, as suggested in this answer, you could use HLS and HSV. The colorsys module seems to have functions to make the conversion from RGB. Its documentation also links to these pages, which are worth reading to understand why RGB Euclidian distance doesn't really work:


According to this answer, this library should be useful: http://code.google.com/p/python-colormath/

share|improve this answer
See my answer for an optimized Python version. –  Developer Dec 31 '12 at 5:04

Assuming that rtol, gtol, and btol are the tolerances for r,g, and b respectively, why not do:

if abs(px[0]- r) <= rtol and \
   abs(px[1]- g) <= gtol and \
   abs(px[2]- b) <= btol:
    return x, y
share|improve this answer

Instead of this:


Try this:

if max(map(lambda a,b: abs(a-b), px, (r,g,b))) < tolerance:

Where tolerance is the maximum difference you're willing to accept in any of the color channels.

What it does is to subtract each channel from your target values, take the absolute values, then the max of those.

share|improve this answer
import operator. –  John Zwinck Jan 14 '12 at 17:43
@SLACKY, you'd need to import operator first. (This being said, this is still a formula for Euclidian distance: this will not give you the results you expect visually.) –  Bruno Jan 14 '12 at 17:43
@Bruno: my metric is even worse than the Euclidean distance! I didn't put any emphasis on that part (but upvoted your answer for doing so). If the tolerance is small it may not matter, but if the tolerance is large it probably will matter. –  John Zwinck Jan 14 '12 at 17:45
@JohnZwinch: ah yes, sorry, I misread the formula (no squares...). It looks like a Chebyshev distance. –  Bruno Jan 14 '12 at 17:51
Oops--Python abs only does one item at a time. I've updated my answer to use a lambda to do the subtraction and abs on each element. –  John Zwinck Jan 14 '12 at 18:08


def eq_with_tolerance(a, b, t):
    return a-t <= b <= a+t

def FindColorIn(r,g,b, xmin, xmax, ymin, ymax, tolerance=0):
    image = ImageGrab.grab()
    for x in range(xmin, xmax):
        for y in range(ymin,ymax):
            px = image.getpixel((x, y))
            if eq_with_tolerance(r, px[0], tolerance) and eq_with_tolerance(g, px[1], tolerance) and eq_with_tolerance(b, px[2], tolerance):
                return x, y
share|improve this answer

Here is an optimized Python version adapted from Bruno's asnwer:

def ColorDistance(rgb1,rgb2):
    '''d = {} distance between two colors(3)'''
    rm = 0.5*(rgb1[0]+rgb2[0])
    d = sum((2+rm,4,3-rm)*(rgb1-rgb2)**2)**0.5
    return d


>>> import numpy
>>> rgb1 = numpy.array([1,1,0])
>>> rgb2 = numpy.array([0,0,0])
>>> ColorDistance(rgb1,rgb2)
share|improve this answer
From what I've seen, x**0.5 is much slower than from math import sqrt, then use sqrt(x). But if you import math and use math.sqrt(x) you will see little to no difference. –  JHolta Aug 8 '13 at 6:29

Your Answer


