Writing an Android application for color matching, I found that euclidean distance in the RGB color space is not the best function, as incorrect matches are provided by this type of distance. For example if we consider the list of HTML colors and look the color with minimum distance from (5,68,174) we get the MediumBlue color (0, 0, 205) that does not contain any red and green component. Which alternative functions can provide better result?
Here is the euclidean distance in the RGB space in Java:
public double euclideanColorDistance(Color c1, Color c2) {
int redDiff = c1.red - c2.red;
int greenDiff = c1.green - c2.green;
int blueDiff = c1.blue - c2.blue;
return Math.sqrt(redDiff * redDiff + greenDiff * greenDiff + blueDiff * blueDiff);
}
Writing an Android application for color matching, I found that euclidean distance in the RGB color space is not the best function, as incorrect matches are provided by this type of distance. For example if we consider the list of HTML colors and look the color with minimum distance from (5,68,174) we get the MediumBlue color (0, 0, 205) that does not contain any red and green component. Which alternative functions can provide better result?
Here is the euclidean distance in the RGB space in Java:
public double euclideanColorDistance(Color c1, Color c2) {
int redDiff = c1.red - c2.red;
int greenDiff = c1.green - c2.green;
int blueDiff = c1.blue - c2.blue;
return Math.sqrt(redDiff * redDiff + greenDiff * greenDiff + blueDiff * blueDiff);
}
Share
edited Mar 8 at 6:24
Stephen C
721k95 gold badges846 silver badges1.3k bronze badges
asked Mar 7 at 13:09
LivioLivio
7779 silver badges24 bronze badges
2
- Although I upvoted I still don't know what a "colour distance" is... Now if the ruler in this case had a length of example 360 then now one could tell you the distance from red to green. PS: If you're measuring difference for the sake of matching colours, then the Euclidean logic could work (without the square-rooting part, since it just messes up the numbers)... – VC.One Commented Mar 7 at 18:55
- Euclidean distance in the RGB space does not model properly the way human brain perceives colors, so color matching/similarity may get wrong results. I agree that square rooting is not relevant for matching. – Livio Commented Mar 7 at 20:39
2 Answers
Reset to default 1The metric defined by the CIE for perceptual difference in colour is delta E (ΔE).
Early definitions used simple equations based on LAB colour space, but as they were refined over the years they grew more complicated, taking into account chroma and hue from LCH as well. Fortunately the conversion to LAB and formulas for delta E are very well defined.
Color distance based on euclidean distance in the RGB space is not the best choice, in fact it is possible to use the distance defined in other color spaces that are more appropriate to model human vision, e.g CIE, HSL HSB or YCbCr.
Looking for the same color (5,68,174) of the question against the same list of HTML colors but using a distance function based on YCbCr color space, we get the RoyalBlue color (65, 105, 225) that looks better near the original color.
The function based on YCbCr color space may be coded in Java as:
public double yCbCrColorDistance(Color c1, Color c2) {
double[] result1 = chrominance_luminance(c1);
double[] result2 = chrominance_luminance(c2);
double delta_luminance = result1[0] - result2[0];
double delta_Cb = result1[1] - result2[1];
double delta_Cr = result1[2] - result2[2];
return Math.sqrt(delta_luminance * delta_luminance + delta_Cb * delta_Cb + delta_Cr * delta_Cr);
}
private double[] chrominance_luminance(Color color) {
double luminance = 0.299 * color.red + 0.587 * color.green + 0.114 * color.blue;
double Cb = 0.564 * (color.blue - luminance);
double Cr = 0.713 * (color.red - luminance);
double[] result = {luminance, Cb, Cr};
return result;
}
A better color distance is provided by delta E, as suggested by Wacton, here is an implementation in Java based on RGBToLab:
public static double deltaE(Color c1, Color c2) {
double [] outLab1 = new double[3];
RGBToLAB(c1, outLab1);
double [] outLab2 = new double[3];
RGBToLAB(c2, outLab2);
double diff_l = outLab1[0] - outLab2[0];
double diff_a = outLab1[1] - outLab2[1];
double diff_b = outLab1[2] - outLab2[2];
return Math.sqrt(diff_l * diff_l + diff_a * diff_a + diff_b * diff_b);
}
public static void RGBToLAB(Color color, double [] outLab) {
RGBToXYZ(color.red, color.green, color.blue, outLab);
XYZToLAB(outLab[0], outLab[1], outLab[2], outLab);
}
private static final double XYZ_WHITE_REFERENCE_X = 95.047;
private static final double XYZ_WHITE_REFERENCE_Y = 100;
private static final double XYZ_WHITE_REFERENCE_Z = 108.883;
private static final double XYZ_EPSILON = 0.008856;
private static final double XYZ_KAPPA = 903.3;
public static void RGBToXYZ(int r, int g, int b, double [] outXyz) {
double sr = r / 255.0;
sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4);
double sg = g / 255.0;
sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4);
double sb = b / 255.0;
sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4);
outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
}
public static void XYZToLAB(double x, double y, double z, double [] outLab) {
x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X);
y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y);
z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z);
outLab[0] = Math.max(0, 116 * y - 16);
outLab[1] = 500 * (x - y);
outLab[2] = 200 * (y - z);
}
private static double pivotXyzComponent(double component) {
return component > XYZ_EPSILON ? Math.pow(component, 1 / 3.0): (XYZ_KAPPA * component + 16) / 116;
}
The CIE based distance is more accurate, but uses more CPU clock cycles as it uses Math.pow()
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744927624a4601535.html
评论列表(0条)