2

When it comes to comparing colors during image analysis, you'll soon discover that you can just use grayscale image. Why? Because usually you do this:

double average = (color.r+color.g+color.b)/3;

Based on grascale average colors, I made an algorithm that is actually quite satisfying when it comes to find an object on screen (I used whole desktop, but well, this is enough):

image description

Search by average color took 67ms while searching by exact pixel match (blue frame) took 1.255 seconds! (and the former terminated right after finding first match, while the average color algorithm loops whole image).

But I wanted to improve precision on GUI's. In GUI red button looks just like blue button and may be matched wrongly. This is why I implemented color-sensitive integral image. Now I discovered that I don't know how to properly compare the color sums to get some real color difference.

So imagine you have 2 arrays of 3 elements.

//Summed colors on the image you're looking for
double sumOnSearchedImage[3];
//Summed colors on currently checked rectangle (in some loop we'll not bother with here)
double sumOnBigImage[3];

Every number in the arrays represents red, blue and green sum (not average) respectively. How do you compare these so that difference between rgb(0, 255, 255) and rgb(255,255,255) is larger than difference between rgb(170,170,170) and rgb(255,255,255)?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • Given that colours exist in a 3 dimensional space with r, g and b forming the three axes, have you considered calculating a vector from one colour to the next and then comparing the length/direction of the vectors rather than the sum of the differences on each axis? – Jason Feb 02 '15 at 03:00
  • I'm thinking maybe using a transform like wavelet may cause a big improvement in speed and accuracy, why are you using simple rgb format at all?! – Lrrr Feb 02 '15 at 05:57
  • @AliAmiri I'm a simple learner. You must start with *something*. – Tomáš Zato Feb 02 '15 at 12:22

2 Answers2

3

use dot product

dc=cos(ang)=dot(col1,col2);
dc=r1*r2+g1*g2+b1*b2

for normalized RGB colors (unit vectors) this gives you coefficient in range dc=<0,1> where 0 means 90 deg angle between colors (max possible difference) and 1 means the same color (not intensity)

performance

use 8bit per channel ... so range is <0,255> to avoid FPU usage. You can avoid sqrt use for unnormalized colors simply by:

dc=(r1*r2+g1*g2+b1*b2)^2/(|col1|^2*|col2|^2)
|col|^2=r*r+g*g+b*b

[edit1] additional info

normalized colors are unit 3D vectors

if you convert this to 8bit range like 255*(r,g,b) then you get the 8bit per channel range so you can handle each color channel as integer or as fixed point decimal. For the fixed point you just need to change multiplication and division all the rest of operations are the same:

add=a+b
sub=a-b
mul=(a*b)>>8
div=((a<<8)/b)>>8

when you use normalized colors then |col|=1 so you do not need sqrt nor division. For fixed point just shift right by 8 bits instead... For integer <0,255> the |col|=255 which is also done by ~shift right by 8 bits. For unnormalized colors you need to divide by |col| which need sqrt and division but the dc coefficient is in range <0,1> so if you use dc^2 you just change the linearity of coefficient which is not important for you and for |col|^2 the sqrt usage is obsolete because |col|^2=sqrt(r*r+g*g+b*b)^2=(r*r+g*g+b*b).

For better speed up you should convert entire image to normalized colors prior to your task. If coded right it should be around 10+ ms for common desktop resolutions

[Notes]

there are other color spaces more suited for your purpose like HSV

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
2

In the metric space the distance between rgb(0, 255, 255) and rgb(255,255,255) is already far larger than distance between rgb(170,170,170) and rgb(255,255,255).

Only use not distances themselves, but their squares, for speed.

|(0, 255, 255), (255,255,255)|^2 = 255^2 = 9*85^2
|(170,170,170), (255,255,255)|^2 = 3*85^2

BTW, don't be astonished to find that grayscale seeing is often enough. The good design makes designers to make things a) well visible and b) at least somehow visible for about 18% of people, for just so many have problems with colors seeing. http://www.colour-blindness.com/general/prevalence/

Gangnus
  • 24,044
  • 16
  • 90
  • 149
  • So basically all I do is that I square my results (which were originally `abs(color1-color2)`)? – Tomáš Zato Feb 02 '15 at 12:31
  • Yes. Simply multiply it by themselves. Counting distances is very expensive. But COMPARING them is very cheap, for you needn't use sqrt(). – Gangnus Feb 02 '15 at 12:36
  • Ok. The funny thing about it is, that I was thinking about the very same approach - but never realised that it's comparing distances. You know, when I need non-linear analysis of results, I use either `sqrt` (smaller results are more significant) or `^2` (larger results are more significant). But in this case I was afraid to just throw some random function in the calculation. Thanks for explaining this. – Tomáš Zato Feb 02 '15 at 12:48