如何比较两种颜色的相似/差异

我想devise一个程序,可以帮助我评估5个预定义的颜色,其中一个更像是一个可变的颜色,以及百分比。 事情是,我不知道如何手动一步一步做。 所以想一个程序就更难了。

更多细节:颜色来自不同颜色的胶pipe照片。 我有5个不同颜色的pipe子,每个都代表5个等级中的1个。 我想拍摄其他样品的照片,并在计算机上通过比较颜色来评估样品属于哪个水平,我也想知道这个比例也是近似值。 我想要一个像这样的程序: http : //www.colortools.net/color_matcher.html

如果你能告诉我要采取什么措施,哪怕是让我思考和手动做的事情。 这将是非常有益的。

请参阅维基百科关于色差的文章。 基本上,您想要在某些多维色彩空间中计算距离度量。 但是RGB不是“感知上一致的”,所以Vadim提出的欧几里德RGB距离度量将与人类感知的颜色之间的距离不匹配。 首先,L a b *意图是知觉上统一的色彩空间,并且deltaE度量是常用的。 但是更精致的色彩空间和更精致的deltaE公式更接近于匹配人类的感知。

您将不得不进一步了解颜色空间和光源以进行转换。 但是,对于比欧几里得RGB公制更好的快速公式,只需这样做:假设您的RGB值在sRGB色彩空间中,findsRGB到L a b *转换公式,将您的sRGB色彩转换为L a b *并计算两个L a b *值之间的deltaE。 这不是计算上的昂贵,它只是一些非线性公式和一些乘法和增加。

只是一个想法,首先来到我的脑海(对不起,如果愚蠢)。 颜色的三个组成部分可以被假定为三维点的坐标,然后你可以计算点之间的距离。

FE

 Point1 has R1 G1 B1 Point2 has R2 G2 B2 

颜色之间的距离是

 d=sqrt((r2-r1)^2+(g2-g1)^2+(b2-b1)^2) 

百分比是

 p=d/sqrt((255)^2+(255)^2+(255)^2) 

一个颜色值有一个以上的维度,所以没有内在的方法来比较两种颜色。 你必须为你的使用情况确定颜色的含义,从而如何更好地比较它们。

最有可能的是,你想比较颜色的色调,饱和度和/或亮度属性,如红/绿/蓝分量。 如果你很难弄清楚你想如何比较它们,那么拿一些样本颜色对比,然后试着自我辩解/解释为什么他们是相似/不同的。

一旦知道了要比较的颜色属性/分量,就需要弄清楚如何从颜色中提取信息。

最有可能你只需要将常见的RedGreenBlue表示的颜色转换成HueSaturationLightness,然后计算类似

 avghue = (color1.hue + color2.hue)/2 distance = abs(color1.hue-avghue) 

这个例子会给你一个简单的标量值,指示颜色的渐变/色调距离彼此有多远。

请参阅维基百科的HSL和HSV 。

如果您有两个Color对象c1c2 ,则可以将c1每个RGB值与c2每个RGB值进行比较。

 int diffRed = Math.abs(c1.getRed() - c2.getRed()); int diffGreen = Math.abs(c1.getGreen() - c2.getGreen()); int diffBlue = Math.abs(c1.getBlue() - c2.getBlue()); 

这些值你可以用不同的饱和度(255)来区分,你会得到两者之间的差异。

 float pctDiffRed = (float)diffRed / 255; float pctDiffGreen = (float)diffGreen / 255; float pctDiffBlue = (float)diffBlue / 255; 

之后你可以find百分比的平均色差。

 (pctDiffRed + pctDiffGreen + pctDiffBlue) / 3 * 100 

这会给你在c1c2之间的百分比的差异。

实际上我几个月前走了同一条路。 这个问题没有完美的答案(在这里问了几次),但有一个更复杂的sqrt(rr)等答案,更容易直接与RGB实现,而不移动到所有types的替代色彩空间。 我在这里find了这个公式,这是一个相当复杂的实际公式的低成本近似(由CIE这是W3C的颜色,因为这是一个没有完成的任务,你可以find更老和更简单的色差方程)。 祝你好运

编辑:为后代,这是相关的C代码:

 typedef struct { unsigned char r, g, b; } RGB; double ColourDistance(RGB e1, RGB e2) { long rmean = ( (long)e1.r + (long)e2.r ) / 2; long r = (long)e1.r - (long)e2.r; long g = (long)e1.g - (long)e2.g; long b = (long)e1.b - (long)e2.b; return sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8)); } 

通过人类感知来比较两种颜色的最好方法之一是CIE76。 差异称为Delta-E。 当它小于1时,人眼无法识别差异。

有很棒的颜色工具类ColorUtils(下面的代码),其中包括CIE76比较方法。 它由苏黎世大学的Daniel Strebel编写。

从ColorUtils.class我使用的方法:

 static double colorDifference(int r1, int g1, int b1, int r2, int g2, int b2) 

r1,g1,b1 – 第一个颜色的RGB值

r2,g2,b2 – 您想要比较的第二种颜色的RGB值

如果你使用Android,你可以得到这样的值:

r1 = Color.red(pixel);

g1 = Color.green(pixel);

b1 = Color.blue(pixel);


苏黎世大学Daniel Strebel的ColorUtils.class:

 import android.graphics.Color; public class ColorUtil { public static int argb(int R, int G, int B) { return argb(Byte.MAX_VALUE, R, G, B); } public static int argb(int A, int R, int G, int B) { byte[] colorByteArr = {(byte) A, (byte) R, (byte) G, (byte) B}; return byteArrToInt(colorByteArr); } public static int[] rgb(int argb) { return new int[]{(argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF}; } public static int byteArrToInt(byte[] colorByteArr) { return (colorByteArr[0] << 24) + ((colorByteArr[1] & 0xFF) << 16) + ((colorByteArr[2] & 0xFF) << 8) + (colorByteArr[3] & 0xFF); } public static int[] rgb2lab(int R, int G, int B) { //http://www.brucelindbloom.com float r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr; float Ls, as, bs; float eps = 216.f / 24389.f; float k = 24389.f / 27.f; float Xr = 0.964221f; // reference white D50 float Yr = 1.0f; float Zr = 0.825211f; // RGB to XYZ r = R / 255.f; //R 0..1 g = G / 255.f; //G 0..1 b = B / 255.f; //B 0..1 // assuming sRGB (D65) if (r <= 0.04045) r = r / 12; else r = (float) Math.pow((r + 0.055) / 1.055, 2.4); if (g <= 0.04045) g = g / 12; else g = (float) Math.pow((g + 0.055) / 1.055, 2.4); if (b <= 0.04045) b = b / 12; else b = (float) Math.pow((b + 0.055) / 1.055, 2.4); X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b; Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b; Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b; // XYZ to Lab xr = X / Xr; yr = Y / Yr; zr = Z / Zr; if (xr > eps) fx = (float) Math.pow(xr, 1 / 3.); else fx = (float) ((k * xr + 16.) / 116.); if (yr > eps) fy = (float) Math.pow(yr, 1 / 3.); else fy = (float) ((k * yr + 16.) / 116.); if (zr > eps) fz = (float) Math.pow(zr, 1 / 3.); else fz = (float) ((k * zr + 16.) / 116); Ls = (116 * fy) - 16; as = 500 * (fx - fy); bs = 200 * (fy - fz); int[] lab = new int[3]; lab[0] = (int) (2.55 * Ls + .5); lab[1] = (int) (as + .5); lab[2] = (int) (bs + .5); return lab; } /** * Computes the difference between two RGB colors by converting them to the L*a*b scale and * comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76} */ public static double getColorDifference(int a, int b) { int r1, g1, b1, r2, g2, b2; r1 = Color.red(a); g1 = Color.green(a); b1 = Color.blue(a); r2 = Color.red(b); g2 = Color.green(b); b2 = Color.blue(b); int[] lab1 = rgb2lab(r1, g1, b1); int[] lab2 = rgb2lab(r2, g2, b2); return Math.sqrt(Math.pow(lab2[0] - lab1[0], 2) + Math.pow(lab2[1] - lab1[1], 2) + Math.pow(lab2[2] - lab1[2], 2)); } } 

只是另一个答案,虽然它类似于Supr的 – 只是一个不同的色彩空间。

事情是:人类感觉到色彩的不一致,RGB色彩空间忽略了这一点。 因此,如果你使用RGB色彩空间,只是计算两种颜色之间的欧几里得距离,你可能会得到一个在math上绝对正确的区别,但不会与人类会告诉你的一致。

这可能不是问题 – 差别不是我认为的那么大,但是如果你想解决这个“更好”的问题,你应该把你的RGB颜色转换成专门devise的颜色空间,以避免上述问题。 有几个,从早期的模型的改进(因为这是基于人类的认知,我们需要测量的“正确的”价值为基础的实验数据)。 有Lab的色彩空间 ,我认为它是最好的,虽然有点复杂的转换。 更简单的是CIE XYZ 。

这是一个网站,列出公式的不同颜色空间之间进行转换,所以你可以尝试一下。

最好的方法是deltaE。 DeltaE是一个显示颜色差异的数字。 如果deltae <1,则差别不能被人眼识别。 我在canvas和js中编写了一个代码,用于将rgb转换为lab,然后计算delta e。 在这个例子中,代码识别具有与LAB1保存的基本颜色不同的颜色的像素。 如果不同则使这些像素变红。 您可以增加或减less颜色差异的敏感度,也可以降低Δe的可接受范围。 在这个例子中,我在我写的行(deltae <= 10)中为deltaE指定了10:

 <script> var constants = { canvasWidth: 700, // In pixels. canvasHeight: 600, // In pixels. colorMap: new Array() }; // ----------------------------------------------------------------------------------------------------- function fillcolormap(imageObj1) { function rgbtoxyz(red1,green1,blue1){ // a converter for converting rgb model to xyz model var red2 = red1/255; var green2 = green1/255; var blue2 = blue1/255; if(red2>0.04045){ red2 = (red2+0.055)/1.055; red2 = Math.pow(red2,2.4); } else{ red2 = red2/12.92; } if(green2>0.04045){ green2 = (green2+0.055)/1.055; green2 = Math.pow(green2,2.4); } else{ green2 = green2/12.92; } if(blue2>0.04045){ blue2 = (blue2+0.055)/1.055; blue2 = Math.pow(blue2,2.4); } else{ blue2 = blue2/12.92; } red2 = (red2*100); green2 = (green2*100); blue2 = (blue2*100); var x = (red2 * 0.4124) + (green2 * 0.3576) + (blue2 * 0.1805); var y = (red2 * 0.2126) + (green2 * 0.7152) + (blue2 * 0.0722); var z = (red2 * 0.0193) + (green2 * 0.1192) + (blue2 * 0.9505); var xyzresult = new Array(); xyzresult[0] = x; xyzresult[1] = y; xyzresult[2] = z; return(xyzresult); } //end of rgb_to_xyz function function xyztolab(xyz){ //a convertor from xyz to lab model var x = xyz[0]; var y = xyz[1]; var z = xyz[2]; var x2 = x/95.047; var y2 = y/100; var z2 = z/108.883; if(x2>0.008856){ x2 = Math.pow(x2,1/3); } else{ x2 = (7.787*x2) + (16/116); } if(y2>0.008856){ y2 = Math.pow(y2,1/3); } else{ y2 = (7.787*y2) + (16/116); } if(z2>0.008856){ z2 = Math.pow(z2,1/3); } else{ z2 = (7.787*z2) + (16/116); } var l= 116*y2 - 16; var a= 500*(x2-y2); var b= 200*(y2-z2); var labresult = new Array(); labresult[0] = l; labresult[1] = a; labresult[2] = b; return(labresult); 

}

  var canvas = document.getElementById('myCanvas'); var context = canvas.getContext('2d'); var imageX = 0; var imageY = 0; context.drawImage(imageObj1, imageX, imageY, 240, 140); var imageData = context.getImageData(0, 0, 240, 140); var data = imageData.data; var n = data.length; // iterate over all pixels var m = 0; for (var i = 0; i < n; i += 4) { var red = data[i]; var green = data[i + 1]; var blue = data[i + 2]; var xyzcolor = new Array(); xyzcolor = rgbtoxyz(red,green,blue); var lab = new Array(); lab = xyztolab(xyzcolor); constants.colorMap.push(lab); //fill up the colormap array with lab colors. } } 

// ———————————————— ————————————————– —

  function colorize(pixqty) { function deltae94(lab1,lab2){ //calculating Delta E 1994 var c1 = Math.sqrt((lab1[1]*lab1[1])+(lab1[2]*lab1[2])); var c2 = Math.sqrt((lab2[1]*lab2[1])+(lab2[2]*lab2[2])); var dc = c1-c2; var dl = lab1[0]-lab2[0]; var da = lab1[1]-lab2[1]; var db = lab1[2]-lab2[2]; var dh = Math.sqrt((da*da)+(db*db)-(dc*dc)); var first = dl; var second = dc/(1+(0.045*c1)); var third = dh/(1+(0.015*c1)); var deresult = Math.sqrt((first*first)+(second*second)+(third*third)); return(deresult); } // end of deltae94 function var lab11 = new Array("80","-4","21"); var lab12 = new Array(); var k2=0; var canvas = document.getElementById('myCanvas'); var context = canvas.getContext('2d'); var imageData = context.getImageData(0, 0, 240, 140); var data = imageData.data; for (var i=0; i<pixqty; i++) { lab12 = constants.colorMap[i]; var deltae = deltae94(lab11,lab12); if (deltae <= 10) { data[i*4] = 255; data[(i*4)+1] = 0; data[(i*4)+2] = 0; k2++; } // end of if } //end of for loop context.clearRect(0,0,240,140); alert(k2); context.putImageData(imageData,0,0); } // ----------------------------------------------------------------------------------------------------- $(window).load(function () { var imageObj = new Image(); imageObj.onload = function() { fillcolormap(imageObj); } imageObj.src = './mixcolor.png'; }); // --------------------------------------------------------------------------------------------------- var pixno2 = 240*140; </script> 

下面的所有方法都会导致从0到100的范围。

 internal static class ColorDifference { internal enum Method { Binary, // true or false, 0 is false Square, Dimensional, CIE76 } public static double Calculate(Method method, int argb1, int argb2) { int[] c1 = ColorConversion.ArgbToArray(argb1); int[] c2 = ColorConversion.ArgbToArray(argb2); return Calculate(method, c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]); } public static double Calculate(Method method, int r1, int r2, int g1, int g2, int b1, int b2, int a1 = -1, int a2 = -1) { switch (method) { case Method.Binary: return (r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2) ? 0 : 100; case Method.CIE76: return CalculateCIE76(r1, r2, g1, g2, b1, b2); case Method.Dimensional: if (a1 == -1 || a2 == -1) return Calculate3D(r1, r2, g1, g2, b1, b2); else return Calculate4D(r1, r2, g1, g2, b1, b2, a1, a2); case Method.Square: return CalculateSquare(r1, r2, g1, g2, b1, b2, a1, a2); default: throw new InvalidOperationException(); } } public static double Calculate(Method method, Color c1, Color c2, bool alpha) { switch (method) { case Method.Binary: return (c1.R == c2.R && c1.G == c2.G && c1.B == c2.B && (!alpha || c1.A == c2.A)) ? 0 : 100; case Method.CIE76: if (alpha) throw new InvalidOperationException(); return CalculateCIE76(c1, c2); case Method.Dimensional: if (alpha) return Calculate4D(c1, c2); else return Calculate3D(c1, c2); case Method.Square: if (alpha) return CalculateSquareAlpha(c1, c2); else return CalculateSquare(c1, c2); default: throw new InvalidOperationException(); } } // A simple idea, based on on a Square public static double CalculateSquare(int argb1, int argb2) { int[] c1 = ColorConversion.ArgbToArray(argb1); int[] c2 = ColorConversion.ArgbToArray(argb2); return CalculateSquare(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3]); } public static double CalculateSquare(Color c1, Color c2) { return CalculateSquare(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B); } public static double CalculateSquareAlpha(int argb1, int argb2) { int[] c1 = ColorConversion.ArgbToArray(argb1); int[] c2 = ColorConversion.ArgbToArray(argb2); return CalculateSquare(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]); } public static double CalculateSquareAlpha(Color c1, Color c2) { return CalculateSquare(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B, c1.A, c2.A); } public static double CalculateSquare(int r1, int r2, int g1, int g2, int b1, int b2, int a1 = -1, int a2 = -1) { if (a1 == -1 || a2 == -1) return (Math.Abs(r1 - r2) + Math.Abs(g1 - g2) + Math.Abs(b1 - b2)) / 7.65; else return (Math.Abs(r1 - r2) + Math.Abs(g1 - g2) + Math.Abs(b1 - b2) + Math.Abs(a1 - a2)) / 10.2; } // from:http://stackoverflow.com/questions/9018016/how-to-compare-two-colors public static double Calculate3D(int argb1, int argb2) { int[] c1 = ColorConversion.ArgbToArray(argb1); int[] c2 = ColorConversion.ArgbToArray(argb2); return Calculate3D(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3]); } public static double Calculate3D(Color c1, Color c2) { return Calculate3D(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B); } public static double Calculate3D(int r1, int r2, int g1, int g2, int b1, int b2) { return Math.Sqrt(Math.Pow(Math.Abs(r1 - r2), 2) + Math.Pow(Math.Abs(g1 - g2), 2) + Math.Pow(Math.Abs(b1 - b2), 2)) / 4.41672955930063709849498817084; } // Same as above, but made 4D to include alpha channel public static double Calculate4D(int argb1, int argb2) { int[] c1 = ColorConversion.ArgbToArray(argb1); int[] c2 = ColorConversion.ArgbToArray(argb2); return Calculate4D(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]); } public static double Calculate4D(Color c1, Color c2) { return Calculate4D(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B, c1.A, c2.A); } public static double Calculate4D(int r1, int r2, int g1, int g2, int b1, int b2, int a1, int a2) { return Math.Sqrt(Math.Pow(Math.Abs(r1 - r2), 2) + Math.Pow(Math.Abs(g1 - g2), 2) + Math.Pow(Math.Abs(b1 - b2), 2) + Math.Pow(Math.Abs(a1 - a2), 2)) / 5.1; } /** * Computes the difference between two RGB colors by converting them to the L*a*b scale and * comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76} */ public static double CalculateCIE76(int argb1, int argb2) { return CalculateCIE76(Color.FromArgb(argb1), Color.FromArgb(argb2)); } public static double CalculateCIE76(Color c1, Color c2) { return CalculateCIE76(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B); } public static double CalculateCIE76(int r1, int r2, int g1, int g2, int b1, int b2) { int[] lab1 = ColorConversion.ColorToLab(r1, g1, b1); int[] lab2 = ColorConversion.ColorToLab(r2, g2, b2); return Math.Sqrt(Math.Pow(lab2[0] - lab1[0], 2) + Math.Pow(lab2[1] - lab1[1], 2) + Math.Pow(lab2[2] - lab1[2], 2)) / 2.55; } } internal static class ColorConversion { public static int[] ArgbToArray(int argb) { return new int[] { (argb >> 24), (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF }; } public static int[] ColorToLab(int R, int G, int B) { // http://www.brucelindbloom.com double r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr; double Ls, fas, fbs; double eps = 216.0f / 24389.0f; double k = 24389.0f / 27.0f; double Xr = 0.964221f; // reference white D50 double Yr = 1.0f; double Zr = 0.825211f; // RGB to XYZ r = R / 255.0f; //R 0..1 g = G / 255.0f; //G 0..1 b = B / 255.0f; //B 0..1 // assuming sRGB (D65) if (r <= 0.04045) r = r / 12; else r = (float)Math.Pow((r + 0.055) / 1.055, 2.4); if (g <= 0.04045) g = g / 12; else g = (float)Math.Pow((g + 0.055) / 1.055, 2.4); if (b <= 0.04045) b = b / 12; else b = (float)Math.Pow((b + 0.055) / 1.055, 2.4); X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b; Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b; Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b; // XYZ to Lab xr = X / Xr; yr = Y / Yr; zr = Z / Zr; if (xr > eps) fx = (float)Math.Pow(xr, 1 / 3.0); else fx = (float)((k * xr + 16.0) / 116.0); if (yr > eps) fy = (float)Math.Pow(yr, 1 / 3.0); else fy = (float)((k * yr + 16.0) / 116.0); if (zr > eps) fz = (float)Math.Pow(zr, 1 / 3.0); else fz = (float)((k * zr + 16.0) / 116); Ls = (116 * fy) - 16; fas = 500 * (fx - fy); fbs = 200 * (fy - fz); int[] lab = new int[3]; lab[0] = (int)(2.55 * Ls + 0.5); lab[1] = (int)(fas + 0.5); lab[2] = (int)(fbs + 0.5); return lab; } } 

我希望你想分析一个完整的图像,不是吗? 所以你可以检查身份颜色matrix的最小/最大差异。

大多数用于处理graphics的math运算使用matrix,因为使用它们的可能algorithm通常比经典的逐点距离和比较计算更快。 (例如,使用DirectX,OpenGL …的操作)

所以我认为你应该从这里开始:

http://en.wikipedia.org/wiki/Identity_matrix

http://en.wikipedia.org/wiki/Matrix_difference_equation

…和贝斯卡已经评论上面:

这可能不会给出最好的“可见”区别…

这也意味着如果你正在处理图像,你的algorithm依赖于你定义的“相似”。

一个简单的方法,只使用RGB是

 cR=R1-R2 cG=G1-G2 cB=B1-B2 uR=R1+R2 distance=cR*cR*(2+uR/256) + cG*cG*4 + cB*cB*(2+(255-uR)/256) 

我已经使用了一段时间了,对大多数用途来说,它已经足够好了。

您需要将任何RGB颜色转换为Lab颜色空间,以便能够以人类看到它们的方式进行比较。 否则,你会得到一些奇怪的方式“匹配”的RGB颜色。

Color Differences上的维基百科链接为您介绍了多年来定义的各种Lab色彩空间差异algorithm。 最简单的就是检查两个实验室颜色的欧氏距离,但是有一些缺陷。

在OpenIMAJ项目中, 可以方便地使用更复杂的CIEDE2000algorithm的Java实现。 提供你的两套实验室颜色,它会给你回单距离值。

比较颜色的唯一“正确”方法是使用CIELab或CIELuv中的deltaE。

但对于很多应用程序,我认为这是一个很好的近似值:

distance = 3 * |dR| + 4 * |dG| + 3 * |dB|

在比较颜色时,我认为加权的曼哈顿距离会更有意义。 请记住,颜色初选只在我们的头上。 他们没有任何物理意义。 CIELab和CIELuv是从我们对颜色的看法统计build模的。

快速和肮脏,你可以做

 import java.awt.Color; private Color dropPrecision(Color c,int threshold){ return new Color((c.getRed()/threshold), (c.getGreen()/threshold), (c.getBlue()/threshold)); } public boolean inThreshold(Color _1,Color _2,int threshold){ return dropPrecision(_1,threshold)==dropPrecision(_2,threshold); } 

利用整数除法来量化颜色。