Finding Nearest Colour

  • Thread starter Thread starter pepito72
  • Start date Start date
P

pepito72

Hi

A problem with trying to find the nearest colour. I have an image
which I use GetPixel to return the colour of various points.
However .... not all colours returned will be a System/Known/Pre-
defined colour. So they wont have corresponding names - this is a
problem because my app needs to return the colour name of each pixel.
So I have looked at GetNearestColor but this just seems to return the
original color I passed it in the first place!?

Any pointers would be appreciated.

Thanks
Tad
 
A problem with trying to find the nearest colour.

Tad,

In order to find the nearest known color, you must get the difference
for each known color based on your sampled color. You could do this by
looping through each known color in the enumeration
Drawing.KnownColor. Next, you should compare the R, G, and B values
for the known to the sample - it is important to use Math.Abs() here
because you want the magnitude, negative values will not help.
Finally, you keep the value with the smallest distance and return it.

In my sample code below, I also created a structure for returning the
Name, Color and the Distance - this is not really necessary.

HTH,
Jeremy


Here is my code:

-----------------8<--------------------------------------------

Private R As New Random

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click

Dim randColor As Color = Color.FromArgb(R.Next(0, 255), _
R.Next(0, 255), _
R.Next(0, 255))

Me.RandomColorBox.BackColor = randColor
Me.RandomColorLabel.Text = randColor.ToString


'// now match to nearest known color
Dim nearest As ColorName = FindNearestKnown(randColor)
Me.KnownColorLabel.Text = nearest.Name
Me.NearestColorBox.BackColor = nearest.Color

End Sub

Structure ColorName
Public Color As Color
Public Name As String
Public Distance As Integer
End Structure

Public Function FindNearestKnown(ByVal c As Color) As ColorName
Dim best As ColorName

best.Name = Nothing

For Each colorName As String In _
[Enum].GetNames(GetType(KnownColor))
Dim known As Color = Color.FromName(colorName)
Dim dist As Integer

dist = Math.Abs(CInt(c.R) - known.R) _
+ Math.Abs(CInt(c.G) - known.G) _
+ Math.Abs(CInt(c.B) - known.B)

If best.Name Is Nothing OrElse dist < best.Distance Then
best.Color = known
best.Name = colorName
best.Distance = dist
End If
Next

Return best
End Function
 
Jeremy said:
Dim dist As Integer

dist = Math.Abs(CInt(c.R) - known.R) _
+ Math.Abs(CInt(c.G) - known.G) _
+ Math.Abs(CInt(c.B) - known.B)

Wouldn't that be better measured as the root mean square distance?

<snip>
Dim known As Color
Dim dist As Double
For Each colorName As String In _
[Enum].GetNames(GetType(KnownColor))
Color = Color.FromName(colorName)

dist=math.sqrt((CInt(c.R) - known.R)^2 + (CInt(c.G) - known.G)^2 +
(CInt(c.B) - known.B)^2))

If best.Name Is Nothing OrElse dist < best.Distance Then
best.Color = known
best.Name = colorName
best.Distance = dist
End If
Next
<snip>

(OK, so not any need to take the square root; just use the square of the
distance. The comparisons remain valid.)

Andrew
 
A problem with trying to find the nearest colour.

Tad,

In order to find the nearest known color, you must get the difference
for each known color based on your sampled color. You could do this by
looping through each known color in the enumeration
Drawing.KnownColor. Next, you should compare the R, G, and B values
for the known to the sample - it is important to use Math.Abs() here
because you want the magnitude, negative values will not help.
Finally, you keep the value with the smallest distance and return it.

In my sample code below, I also created a structure for returning the
Name, Color and the Distance - this is not really necessary.

HTH,
Jeremy

Here is my code:

-----------------8<--------------------------------------------

Private R As New Random

Private Sub Button1_Click(ByVal sender As System.Object, _
              ByVal e As System.EventArgs) Handles Button1.Click

    Dim randColor As Color = Color.FromArgb(R.Next(0, 255), _
                                     R.Next(0, 255), _
                                     R.Next(0, 255))

    Me.RandomColorBox.BackColor = randColor
    Me.RandomColorLabel.Text = randColor.ToString

    '// now match to nearest known color
    Dim nearest As ColorName = FindNearestKnown(randColor)
    Me.KnownColorLabel.Text = nearest.Name
    Me.NearestColorBox.BackColor = nearest.Color

End Sub

Structure ColorName
    Public Color As Color
    Public Name As String
    Public Distance As Integer
End Structure

Public Function FindNearestKnown(ByVal c As Color) As ColorName
    Dim best As ColorName

    best.Name = Nothing

    For Each colorName As String In _
                    [Enum].GetNames(GetType(KnownColor))
        Dim known As Color = Color.FromName(colorName)
        Dim dist As Integer

        dist = Math.Abs(CInt(c.R) - known.R) _
                + Math.Abs(CInt(c.G) - known.G) _
                + Math.Abs(CInt(c.B) - known.B)

        If best.Name Is Nothing OrElse dist < best.Distance Then
            best.Color = known
            best.Name = colorName
            best.Distance = dist
        End If
    Next

    Return best
End Function

Thanks Jeremy -that did it.
 
Wouldn't that be better measured as the root mean square distance?
Andrew

Yes, the RMS method is definitely a better match - good suggestion. I
ran several tests, and consistently about 160 out of 1000 samples
(~16%) are more accurate with RMS. So I guess it depends on weather
you want performance or accuracy, although, the performance gain is
probably going to be negligible.

Jeremy
 
Yes, the RMS method is definitely a better match - good suggestion. I
ran several tests, and consistently about 160 out of 1000 samples
(~16%) are more accurate with RMS. So I guess it depends on weather
you want performance or accuracy, although, the performance gain is
probably going to be negligible.

Jeremy

One thing, I think your RMS calculation is missing something:

dist=math.sqrt((CInt(c.R) - known.R)^2 + (CInt(c.G) - known.G)^2 +
(CInt(c.B) - known.B)^2))


Shouldn't it be:

dist=math.sqrt(
((CInt(c.R) - known.R)^2 +
(CInt(c.G) - known.G)^2 +
(CInt(c.B) - known.B)^2))
/ 3
)


?
 
Jeremy said:
One thing, I think your RMS calculation is missing something:
Shouldn't it be:

dist=math.sqrt(
((CInt(c.R) - known.R)^2 +
(CInt(c.G) - known.G)^2 +
(CInt(c.B) - known.B)^2))
/ 3
)

Hey, it was only wrong by a factor of 1.7-ish :-)

Dim known As Color
Dim dist As Double ' temporary distance measure
For Each colorName As String In _
[Enum].GetNames(GetType(KnownColor))
Color = Color.FromName(colorName)

dist=(CInt(c.R) - known.R)^2 + (CInt(c.G) - known.G)^2 + (CInt(c.B) -
known.B)^2)

If best.Name Is Nothing OrElse dist < best.Distance Then
best.Color = known
best.Name = colorName
best.Distance = Math.Sqrt(dist/3)
End If
Next

Also, the OP may want to consider the idea of a "nearest" colour in a
different colour-space, for example the hue may be more important than the
brightness.

Andrew
 
Also, the OP may want to consider the idea of a "nearest" colour in a
different colour-space, for example the hue may be more important than the
brightness.

Andrew

Actually, I was thinking the same thing. I created a test with various
combinations, Hue, RGB, Brightness, Saturation, and it didn't seem to
help much. I think it is because there is such large gaps between the
known colors that it can only get so accurate. Maybe if this were a
different application it would have a bigger impact (i.e. using more
colors than the "known" colors).

Jeremy
 
Back
Top