anon
anon

Reputation: 697

finding similar colors programmatically

I have a buffered image in java and I want to record how similar each pixel is to another based on the color value. so the pixels with 'similar' colors will have a higher similarity value. for example red and pink will have a similarity value 1000 but red and blue will have something like 300 or less.

how can I do this. when I get the RGB from a buffered Image pixel it returns a negative integer I am not sure how to implement this with that.

Upvotes: 17

Views: 19462

Answers (12)

Breton
Breton

Reputation: 15592

HSL is a bad move. L*a*b is a color space designed to represent how color is actually percieved, and is based on data from hundreds of experiments involving people with real eyes looking at different colors and saying "I can tell the difference between those two. But not those two".

Distance in L*a*b space represents actual percieved distance according to the predictions derived from those experiments.

Once you convert into L*a*b you just need to measure linear distance in a 3D space.

Upvotes: 11

Tim Gee
Tim Gee

Reputation: 1062

There's an interesting paper on exactly this problem:

A New Perceptually Uniform Color Space with Associated Color Similarity Measure for Content-Based Image and Video Retrieval by M. Sarifuddin and Rokia Missaoui

You can find this easily using Google or in particular [Google Scholar.][1]

To summarise, some color spaces (e.g. RGB, HSV, Lab) and distance measures (such as Geometric mean and Euclidean distance) are better representations of human perception of color similarity than others. The paper talks about a new color space, which is better than the rest, but it also provides a good comparison of the common existing color spaces and distance measures. Qualitatively*, it seems the best measure for perceptual distance using commonly available color spaces is : the HSV color space and a cylindrical distance measure.

*At least, according to Figure 15 in the referenced paper.

The cylindrical distance measure is (in Latex notation):

D_{cyl} = \sqrt{\Delta V^{2}+S_1^{2}+S_2^{2}-2S_1S_2cos(\Delta H)}

Upvotes: 1

lavinio
lavinio

Reputation: 24329

First, how are you getting the integer value?

Once you get the RGB values, you could try

((r2 - r1)2 + (g2 - g1)2 + (b2 - b1)2)1/2

This would give you the distance in 3D space from the two points, each designated by (r1,g1,b1) and (r2,g2,b2).

Or there are more sophisticated ways using the HSV value of the color.

Upvotes: 24

Tatarize
Tatarize

Reputation: 10816

If you are going to use HSV you need to realize that HSV are not points in a three dimensional space but rather the angle, magnitude, and distance-from-top of a cone. To calculate the distance of an HSV value you either need to determine your points in 3d space by transforming.

X = Cos(H)*S*V

Y = Sin(H)*S*V

Z = V

For both points and then taking the Euclidian distance between them:

Sqrt((X0 - X1)*(X0 - X1) + (Y0 - Y1)*(Y0 - Y1) + (Z0 - Z1)*(Z0 - Z1))

At a cost of 2 Cos, 2 Sin, and a square root.

Alternatively you can actually calculate distance a bit more easily if you're so inclined by realizing that when flattened to 2D space you simply have two vectors from the origin, and applying the law of cosign to find the distance in XY space:

C² = A² + B² + 2*A*B*Cos(Theta)

Where A = S*V of the first value, and B = S*V of the second and cosign is the difference theta or H0-H1

Then you factor in Z, to expand the 2D space into 3D space.

A = S0*V0
B = S1*V1
dTheta = H1-H0
dZ = V0-V1
distance = sqrt(dZ*dZ + A*A + B*B + 2*A*B*Cos(dTheta);

Note that because the law of cosigns gives us C² we just plug it right in there with the change in Z. Which costs 1 Cos and 1 Sqrt. HSV is plenty useful, you just need to know what type of color space it's describing. You can't just slap them into a euclidian function and get something coherent out of it.

Upvotes: 7

jennico
jennico

Reputation: 1

i tried it out. the HSL/HSV value is definitely not useful. for instance:

  • all colors with L=0 are 'black' (RGB 000000), though their HSL difference may implicate a high color distance.

  • all colors with S=0 are a shade of 'gray', though their HSL difference may implicate a high color distance.

  • the H (hue) range begins and ends with a shade of 'red', so H=0 and H=[max] (360° or 100% or 240, depending on the application) are both red and relatively similar to each other, but the Euclidean HSL distance is close to maximum.

so my recommendation is to use the Euclidean RGB distance (r2-r1)² + (g2-g1)² + (b2-b1)² without root. the (subjective) threshold of 1000 then works fine for similar colors. colors with differences > 1000 are well distinguishable by the human eye. additionally it can be helful to weight the components differently (see prev. post).

Upvotes: -2

David R Tribble
David R Tribble

Reputation: 12214

This is a similar question to #1634206.

If you're looking for the distance in RGB space, the Euclidean distance will work, assuming you treat red, green, and blue values all equally.

If you want to weight them differently, as is commonly done when converting color/RGB to grayscale, you need to weight each component by a different amount. For example, using the popular conversion from RGB to grayscale of 30% red + 59% green + 11% blue:

d2 = (30*(r1-r2))**2 + (59*(g1-g2))**2 + (11*(b1-b2))**2;

The smaller the value of d2, the closer the colors (r1,g1,b1)and(r2,g2,b2) are to each other.

But there are other color spaces to choose from than just RGB, which may be better suited to your problem.

Upvotes: 0

camickr
camickr

Reputation: 324207

I find HSL values easier to understand. HSL Color explains how they work and provides the conversion routines. Like the other answer you would need to determine what similiar means to you.

Upvotes: 1

jitter
jitter

Reputation: 54615

I suggest you start reading here

Color difference formulas if you want to do this right. It explains the ΔE*ab, ΔE*94, ΔE*00 and ΔE*CMC formulas for calculating color difference.

Upvotes: 9

BalusC
BalusC

Reputation: 1109715

You could get the separate bytes as follows:

int rgb = bufferedImage.getRGB(x, y); // Returns by default ARGB.
int alpha = (rgb >>> 24) & 0xFF;
int red = (rgb >>> 16) & 0xFF;
int green = (rgb >>> 8) & 0xFF;
int blue = (rgb >>> 0) & 0xFF;

Upvotes: 2

plinth
plinth

Reputation: 49209

You're probably calling getRGB() on each pixel which is returning the color as 4 8 bits bytes, the high byte alpha, the next byte red, the next byte green, the next byte blue. You need to separate out the channels. Even then, color similarity in RGB space is not so great - you might get much better results using HSL or HSV space. See here for conversion code.

In other words:

int a = (argb >> 24) & 0xff;
int r = (argb >> 16) & 0xff;
int g = (argb >> 8) & 0xff;
int b = argb & 0xff;

I don't know the specific byte ordering in java buffered images, but I think that's right.

Upvotes: 2

EmFi
EmFi

Reputation: 23450

The easiest is to convert both colours to HSV value and find the difference in H values. Minimal changes means the colours are similar. It's up to you to define a threshold though.

Upvotes: 2

Related Questions