Tomáš Zato
Tomáš Zato

Reputation: 53129

Compare RGB colors so that color difference is more significant then intensity sum

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)?

Upvotes: 2

Views: 2840

Answers (2)

Spektre
Spektre

Reputation: 51845

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

Upvotes: 3

Gangnus
Gangnus

Reputation: 24464

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/

Upvotes: 2

Related Questions