How to Compare Color Object and Get Closest Color in an Color[]

How to compare Color object and get closest Color in an Color[]?

Color distance is not a precisely defined thing. So here are three methods to measure it:

  • One method that checks only the hues of the colors, ignoring both saturation and brightness
  • One that only measures the direct distance in RGB space
  • And one that weighs hue, saturation and brightness in some way.

Obviously you may want to change the magic numbers in the 3rd measurement: hue is in 0-360, brightness and saturation are in 0-1, so with these numbers hue weighs about 3.6 times stronger than saturation and brightness..

Update: The original solution I posted contained several errors:

  • The Linq I used didn't find the closest but the closestFromBelow; this meant a 50% chance of being off by one.
  • In some places I used the color.GetBrightness() method. This is, to put it mildly, totally useless. To wit: Blue and Yellow have the same value of 0.5!
  • The values for hue go from 0-360, but of course they wrap around! I missed that completely..

I have replaced most of the original answer with corrected code:

These now are the new versions of the methods, each returning the index of the closest match found:

// closed match for hues only:
int closestColor1(List<Color> colors, Color target)
{
var hue1 = target.GetHue();
var diffs = colors.Select(n => getHueDistance(n.GetHue(), hue1));
var diffMin = diffs.Min(n => n);
return diffs.ToList().FindIndex(n => n == diffMin);
}

// closed match in RGB space
int closestColor2(List<Color> colors, Color target)
{
var colorDiffs = colors.Select(n => ColorDiff(n, target)).Min(n =>n);
return colors.FindIndex(n => ColorDiff(n, target) == colorDiffs);
}

// weighed distance using hue, saturation and brightness
int closestColor3(List<Color> colors, Color target)
{
float hue1 = target.GetHue();
var num1 = ColorNum(target);
var diffs = colors.Select(n => Math.Abs(ColorNum(n) - num1) +
getHueDistance(n.GetHue(), hue1) );
var diffMin = diffs.Min(x => x);
return diffs.ToList().FindIndex(n => n == diffMin);
}

A few helper functions:

 // color brightness as perceived:
float getBrightness(Color c)
{ return (c.R * 0.299f + c.G * 0.587f + c.B *0.114f) / 256f;}

// distance between two hues:
float getHueDistance(float hue1, float hue2)
{
float d = Math.Abs(hue1 - hue2); return d > 180 ? 360 - d : d; }

// weighed only by saturation and brightness (from my trackbars)
float ColorNum(Color c) { return c.GetSaturation() * factorSat +
getBrightness(c) * factorBri; }

// distance in RGB space
int ColorDiff(Color c1, Color c2)
{ return (int ) Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R)
+ (c1.G - c2.G) * (c1.G - c2.G)
+ (c1.B - c2.B) * (c1.B - c2.B)); }

Here is the handy little helper I used for the screenshot texts:

Brush tBrush(Color c) { 
return getBrightness(c) < 0.5 ? Brushes.White : Brushes.Black; }

I have updated the screenshot to display not only 13 colors but also a number of mostly reddish colors for testing; all colors are shown with their values for hue, saturation and brightness. The last three numbers are the results of the three methods.

As you can see, the simple distance method is quite misleading hue-wise for bright and non-saturated colors: The last color (Ivory) is in fact a bright and pale yellow!

The third method which gauges all color properties is best imo. You should play around with the gauging numbers, though!

In the end it really depends on what you want to achieve; if, as it seems, you only care about the hues of the colors, simply go for the first method! You can call it, using your array like this:

int indexInArray = closestColor1(clist.ToList(), someColor);

For more on color distances see Wikipedia!

color distances

// the colors I used:
// your array
Color[] clist = new Color[13];
clist[0] = Color.Blue;
clist[1] = Color.BlueViolet;
clist[2] = Color.Magenta;
clist[3] = Color.Purple;
clist[4] = Color.Red;
clist[5] = Color.Tomato;
clist[6] = Color.Orange;
clist[7] = Color.Yellow;
clist[8] = Color.YellowGreen;
clist[9] = Color.Green;
clist[10] = Color.SpringGreen;
clist[11] = Color.Cyan;
clist[12] = Color.Ivory;

// and a list of color to test:
List<Color> targets = new List<Color>();
targets.Add(Color.Pink);
targets.Add(Color.OrangeRed);
targets.Add(Color.LightPink);
targets.Add(Color.DarkSalmon);
targets.Add(Color.LightCoral);
targets.Add(Color.DarkRed);
targets.Add(Color.IndianRed);
targets.Add(Color.LavenderBlush);
targets.Add(Color.Lavender);

How to determine if a color is close to another color

How about something like this?

bool ColorsAreClose(Color a, Color z, int threshold = 50)
{
int r = (int)a.R - z.R,
g = (int)a.G - z.G,
b = (int)a.B - z.B;
return (r*r + g*g + b*b) <= threshold*threshold;
}

(I just guessed at the default threshold, but you should set it to what you want.)

Basically this just computes, on average, whether the three color channels are close enough between the two colors.

What's the best way to round a Color object to the nearest Color Constant?

The basic approach is to find the closest standard color to your sample by simply comparing the sample to each of them. The problem, of course, is in defining "closest." The most obvious would be use the Euclidean distance in RGB space. The problem is that this distance does not correspond very well with our perceptual sense of "closest color". A discussion of this problem, along with a nice (easily computed) metric (including pseudocode!) can be found in this paper.

EDIT: Just in case the link to that paper goes dead (or if you're lazy and are willing to use code without understanding what it does), here's my Java version of the "color distance function" the paper suggests as a "low-cost approximation" to their recommended distance function (a weighted Euclidean distance in RGB space):

double colorDistance(Color c1, Color c2)
{
int red1 = c1.getRed();
int red2 = c2.getRed();
int rmean = (red1 + red2) >> 1;
int r = red1 - red2;
int g = c1.getGreen() - c2.getGreen();
int b = c1.getBlue() - c2.getBlue();
return Math.sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8));
}

Note that if you're just going to rank color distances, you can dispense with the call to Math.sqrt(), saving some computation costs.

Processing how to get the nearest color from a Collection of colors

The RGB color channels of a color() are encoded in an int. You can extract the red, green and blue component of the color, by red(), green() and blue().

Treat the color channels as a 3 dimensional vector (PVector) and compute the Euclidean distance of 2 color vectors, by dist(). The color with the shortest "distance" is the "nearest" color:

In the following function the arguments c1 and c2 are colors of type int:

float ColorDistance(int c1, int c2) {
return PVector.dist(
new PVector(red(c1), green(c1), blue(c1)),
new PVector(red(c2), green(c2), blue(c2)));
}

Find the "nearest" color, in a collection of colors, by finding the minimum floating point "distance" (ColorDistance(i, color)).

Python - Find the closest color to a color, from giving list of colors

You want to find the sum of the absolute difference between the red, green and blue numbers and choose the smallest one.

from math import sqrt

COLORS = (
(181, 230, 99),
(23, 186, 241),
(99, 23, 153),
(231, 99, 29),
)

def closest_color(rgb):
r, g, b = rgb
color_diffs = []
for color in COLORS:
cr, cg, cb = color
color_diff = sqrt((r - cr)**2 + (g - cg)**2 + (b - cb)**2)
color_diffs.append((color_diff, color))
return min(color_diffs)[1]

closest_color((12, 34, 156))
# => (99, 23, 153)

closest_color((23, 145, 234))
# => (23, 186, 241)

EDIT: Improved code and used Euclidian distance calculation Sven mentioned above instead of basic diff sum.

Finding nearest match RGB color from array of colors

There are many different ways to determine color "distance."

There's absolute distance, i.e. the sum of the differences between each channel value:

/**
* Find the "naive" difference between two colors.
* @param int[] $color_a Three-element array with R,G,B color values 0-255.
* @param int[] $color_b Three-element array with R,G,B color values 0-255.
* @return int
*/
function absoluteColorDistance(array $color_a, array $color_b): int {
return
abs($color_a[0] - $color_b[0]) +
abs($color_a[1] - $color_b[1]) +
abs($color_a[2] - $color_b[2]);
}

There's also difference in luminosity, which will give more of a color-independent comparison:

/**
* Find the difference between two colors' luminance values.
* @param int[] $color_a Three-element array with R,G,B color values 0-255.
* @param int[] $color_b Three-element array with R,G,B color values 0-255.
* @return int
*/
function luminanceDistance(int $color_a, int $color_b): int {
$luminance_f = function ($red, $green, $blue): int {
// Source: https://en.wikipedia.org/wiki/Relative_luminance
$luminance = (int) (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue);
return $luminance;
};

return abs(
$luminance_f($color_a[0], $color_a[1], $color_a[2]) -
$luminance_f($color_b[0], $color_b[1], $color_b[2])
);
}

Once you figure out how to compare colors, the next problem you need to solve is finding the color with the least distance from your target color:

$nearest_distance = null;
$nearest_color = null;
foreach ($palate as $test_color) {
$test_distance = luminanceDistance($test_color, $rgbcolor);
if (isset($nearest_distance)) {
if ($nearest_distance > $test_distance) {
// found a closer color
$nearest_distance = $test_distance;
$nearest_color = $test_color;
}
} else {
$nearest_color = $test_color;
$nearest_distance = $test_distance;
}
}

Find closest match to an RGB color value in a color swatch

Don't reinvent the wheel, try with Extended WPF Toolkit™ Community Edition, you have two controls ColorPicker and ColorCanvas.

Replace colors in image by closest color in palette using numpy

1. Option: Single image evaluation (slow)

Pros

- any palette any time (flexible)

Cons

- slow
- memory for large number of colors in palette
- not good for batch processing

2. Option: Batch processing (super fast)

Pros
- super fast (50ms per image), independent of palette size
- low memory, independent of image size or pallete size
- ideal for batch processing if palette doesnt change
- simple code
Cons
- requires creation of color cube (once, up to 3 minutes)
- color cube can contain only one palette

Requirements
- color cube requires 1.5mb of space on disk in form of compressed np matrix

Option 1:

take image, create pallete object with same size as image, calculate distances, retrieve new image with np.argmin indices

import numpy as np
from PIL import Image
import requests

# get some image
im = Image.open(requests.get("https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Big_Nature_%28155420955%29.jpeg/800px-Big_Nature_%28155420955%29.jpeg", stream=True).raw)
newsize = (1000, 1000)
im = im.resize(newsize)
# im.show()
im = np.asarray(im)
new_shape = (im.shape[0],im.shape[1],1,3)

# Ignore above
# Now we have image of shape (1000,1000,1,3). 1 is there so its easy to subtract from color container
image = im.reshape(im.shape[0],im.shape[1],1,3)

# test colors
colors = [[0,0,0],[255,255,255],[0,0,255]]

# Create color container
## It has same dimensions as image (1000,1000,number of colors,3)
colors_container = np.ones(shape=[image.shape[0],image.shape[1],len(colors),3])
for i,color in enumerate(colors):
colors_container[:,:,i,:] = color

def closest(image,color_container):
shape = image.shape[:2]
total_shape = shape[0]*shape[1]

# calculate distances
### shape = (x,y,number of colors)
distances = np.sqrt(np.sum((color_container-image)**2,axis=3))

# get position of the smalles distance
## this means we look for color_container position ????-> (x,y,????,3)
### before min_index has shape (x,y), now shape = (x*y)
#### reshaped_container shape = (x*y,number of colors,3)
min_index = np.argmin(distances,axis=2).reshape(-1)
# Natural index. Bind pixel position with color_position
natural_index = np.arange(total_shape)

# This is due to easy index access
## shape is (1000*1000,number of colors, 3)
reshaped_container = colors_container.reshape(-1,len(colors),3)

# Pass pixel position with corresponding position of smallest color
color_view = reshaped_container[natural_index,min_index].reshape(shape[0],shape[1],3)
return color_view

# NOTE: Dont pass uint8 due to overflow during subtract
result_image = closest(image,colors_container)

Image.fromarray(result_image.astype(np.uint8)).show()

Option 2:

build 256x256x256x3 size color cube based on your palette. In other words, for every existing color assign corresponding palette color that is closest. Save color cube (once/first time). Load color cube. Take image and use every color in image as index in color cube.

import numpy as np
from PIL import Image
import requests
import time
# get some image
im = Image.open(requests.get("https://helpx.adobe.com/content/dam/help/en/photoshop/using/convert-color-image-black-white/jcr_content/main-pars/before_and_after/image-before/Landscape-Color.jpg", stream=True).raw)
newsize = (1000, 1000)
im = im.resize(newsize)
im = np.asarray(im)

### Initialization: Do just once
# Step 1: Define palette
palette = np.array([[255,255,255],[125,0,0],[0,0,125],[0,0,0]])

# Step 2: Create/Load precalculated color cube
try:
# for all colors (256*256*256) assign color from palette
precalculated = np.load('view.npz')['color_cube']
except:
precalculated = np.zeros(shape=[256,256,256,3])
for i in range(256):
print('processing',100*i/256)
for j in range(256):
for k in range(256):
index = np.argmin(np.sqrt(np.sum(((palette)-np.array([i,j,k]))**2,axis=1)))
precalculated[i,j,k] = palette[index]
np.savez_compressed('view', color_cube = precalculated)


# Processing part
#### Step 1: Take precalculated color cube for defined palette and

def get_view(color_cube,image):
shape = image.shape[0:2]
indices = image.reshape(-1,3)
# pass image colors and retrieve corresponding palette color
new_image = color_cube[indices[:,0],indices[:,1],indices[:,2]]

return new_image.reshape(shape[0],shape[1],3).astype(np.uint8)

start = time.time()
result = get_view(precalculated,im)
print('Image processing: ',time.time()-start)
Image.fromarray(result).show()


Related Topics



Leave a reply



Submit