Cómo comparar dos colores para similitud / diferencia

Quiero diseñar un progtwig que pueda ayudarme a evaluar entre 5 colores predefinidos, que uno es más similar a un color variable, y con qué porcentaje. La cuestión es que no sé cómo hacerlo manualmente paso a paso. Entonces, es aún más difícil pensar en un progtwig.

Más detalles: Los colores son de fotografías de tubos con gel que en diferentes colores. Tengo 5 tubos con diferentes colores cada uno es representativo de 1 de 5 niveles. Quiero tomar fotografías de otras muestras y en la computadora evaluar a qué nivel pertenece la muestra al comparar colores, y también quiero saberlo con un porcentaje de aproximación. Me gustaría un progtwig que haga algo como esto: http://www.colortools.net/color_matcher.html

Si puede decirme qué pasos debo seguir, incluso si son cosas que debo pensar y hacer manualmente. Sería muy útil.

Vea el artículo de Wikipedia sobre Diferencia de color para las pistas correctas. Básicamente, desea calcular una métrica de distancia en un espacio de color multidimensional. Pero RGB no es “perceptualmente uniforme”, por lo que su métrica de distancia Euclidean RGB sugerida por Vadim no coincidirá con la distancia percibida por los humanos entre los colores. Para empezar, L a b * está destinado a ser un espacio de color perceptualmente uniforme, y la métrica deltaE se usa comúnmente. Pero hay espacios de colores más refinados y fórmulas deltaE más refinadas que se acercan más a la percepción humana.

Tendrá que aprender más sobre espacios de colores e iluminantes para hacer las conversiones. Pero para una fórmula rápida que es mejor que la métrica Euclidean RGB, solo haga esto: suponga que sus valores RGB están en el espacio de color sRGB, encuentre las fórmulas de conversión sRGB a L a b *, convierta sus colores sRGB a L a b *, y computar deltaE entre sus dos valores de L a b *. No es computacionalmente costoso, solo son algunas fórmulas no lineales y algunas se multiplican y agregan.

Solo una idea que primero se me ocurrió (lo siento si es estúpido). Se pueden suponer tres componentes de colores en coordenadas 3D de puntos y luego se puede calcular la distancia entre los puntos.

FE

 Point1 has R1 G1 B1 Point2 has R2 G2 B2 

La distancia entre los colores es

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

Porcentaje es

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

Un valor de color tiene más de una dimensión, por lo que no hay una forma intrínseca de comparar dos colores. Debe determinar para su caso de uso el significado de los colores y, por lo tanto, la mejor forma de compararlos.

Lo más probable es que desee comparar las propiedades de tono, saturación y / o luminosidad de los colores opuestos a los componentes rojo / verde / azul. Si tiene problemas para descubrir cómo quiere compararlos, tome algunos pares de colores de muestra y compárelos mentalmente, luego intente justificarse / explicarse a sí mismo por qué son similares / diferentes.

Una vez que sepa qué propiedades / componentes de los colores desea comparar, entonces necesita descubrir cómo extraer esa información de un color.

Lo más probable es que solo necesite convertir el color de la representación común de RedGreenBlue en HueSaturationLightness, y luego calcule algo así como

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

Este ejemplo le daría un valor escalar simple que indica la distancia entre el gradiente / tono de los colores.

Ver HSL y HSV en Wikipedia .

Si tiene dos objetos Color c1 y c2 , puede comparar cada valor RGB de c1 con el de c2 .

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

Esos valores se pueden dividir por la cantidad de diferencia de saturaciones (255), y obtendrá la diferencia entre los dos.

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

Después de lo cual puede encontrar la diferencia de color promedio en porcentaje.

 (pctDiffRed + pctDiffGreen + pctDiffBlue) / 3 * 100 

Lo que le daría una diferencia en porcentaje entre c1 y c2 .

De hecho, caminé por el mismo camino hace un par de meses. no hay una respuesta perfecta a la pregunta (que se solicitó aquí un par de veces) pero hay una respuesta más sofisticada que el sqrt (rr) etc. y más fácil de implementar directamente con RGB sin moverse a todo tipo de espacios de color alternativos. Encontré esta fórmula aquí que es una aproximación de bajo costo de la fórmula real bastante complicada (por el CIE que es el W3C de color, ya que esta es una búsqueda no terminada, puedes encontrar ecuaciones de diferencia de color más antiguas y simples allí). buena suerte

Editar: para la posteridad, aquí está el código C relevante:

 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)); } 

Uno de los mejores métodos para comparar dos colores según la percepción humana es CIE76. La diferencia se llama Delta-E. Cuando es menor que 1, el ojo humano no puede reconocer la diferencia.

Hay maravillosas utilidades de color de la clase ColorUtils (código a continuación), que incluye métodos de comparación CIE76. Está escrito por Daniel Strebel, Universidad de Zurich.

De ColorUtils.class uso el método:

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

r1, g1, b1 – valores RGB del primer color

r2, g2, b2 – valores RGB del segundo color que le gustaría comparar

Si trabajas con Android, puedes obtener estos valores de esta manera:

r1 = Color.red(pixel);

g1 = Color.green(pixel);

b1 = Color.blue(pixel);


ColorUtils.class por Daniel Strebel, Universidad de Zurich:

 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)); } } 

Solo otra respuesta, aunque es similar a la de Supr: solo un espacio de color diferente.

El hecho es que los humanos perciben la diferencia de color de manera no uniforme y el espacio de color RGB está ignorando esto. Como resultado, si usa el espacio de color RGB y solo calcula la distancia euclidiana entre 2 colores, puede obtener una diferencia que es matemáticamente absolutamente correcta, pero que no coincidiría con lo que los humanos le dirían.

Esto puede no ser un problema, la diferencia no es tan grande, creo, pero si quieres resolver este “mejor” deberías convertir tus colores RGB en un espacio de color específicamente diseñado para evitar el problema anterior. Existen varias mejoras a partir de modelos anteriores (dado que esto se basa en la percepción humana, necesitamos medir los valores “correctos” basados ​​en datos experimentales). Está el espacio de color Lab, que creo que sería el mejor, aunque un poco complicado para convertirlo. Más simple sería el CIE XYZ .

Aquí hay un sitio que enumera la fórmula para convertir entre diferentes espacios de color para que pueda experimentar un poco.

La mejor manera es deltaE. DeltaE es un número que muestra la diferencia de los colores. Si deltae <1 entonces la diferencia no puede reconocerse por el ojo humano. Escribí un código en canvas y js para convertir rgb en lab y luego calcular delta e. En este ejemplo, el código reconoce píxeles que tienen un color diferente con un color base que guardé como LAB1. y luego, si es diferente, hace que esos píxeles se vuelvan rojos. Puede aumentar o reducir la sensibilidad de la diferencia de color al aumentar o disminuir el rango aceptable de delta e. En este ejemplo, asigné 10 para deltaE en la línea que escribí (deltae < = 10):

  

}

  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 

Todos los métodos a continuación dan como resultado una escala de 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; } } 

Espero que quieras analizar una imagen completa al final, ¿verdad? De modo que podría verificar la diferencia más pequeña / más alta a la matriz de color de identidad.

La mayoría de las operaciones matemáticas para el procesamiento de gráficos utilizan matrices, porque los posibles algoritmos que las utilizan son a menudo más rápidos que la distancia punto por punto clásica y los cálculos de comparación. (por ejemplo, para operaciones que usan DirectX, OpenGL, …)

Entonces creo que deberías comenzar aquí:

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

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

… y como Beska ya comenté anteriormente:

Esto puede no dar la mejor diferencia “visible” …

Lo que significa también que su algoritmo depende de su definición de “similar a” si está procesando imágenes.

Un método simple que solo usa RGB es

 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) 

He usado este por un tiempo, y funciona lo suficientemente bien para la mayoría de los propósitos.

Tendrá que convertir cualquier color RGB en el espacio de color Lab para poder compararlos de la forma en que los ven los humanos. De lo contrario, obtendrás colores RGB que “combinan” de formas muy extrañas.

El enlace de wikipedia sobre Diferencias de color le ofrece una introducción a los diversos algoritmos de diferencia de espacio de color de laboratorio que se han definido a lo largo de los años. El más simple que solo verifica la distancia euclidiana de dos colores de laboratorio, funciona pero tiene algunos defectos.

Convenientemente hay una implementación Java del algoritmo CIEDE2000 más sofisticado en el proyecto OpenIMAJ . Proporcione sus dos juegos de colores Lab y le devolverá el valor de una sola distancia.

La única forma “correcta” de comparar colores es hacerlo con deltaE en CIELab o CIELuv.

Pero para muchas aplicaciones, creo que esta es una aproximación lo suficientemente buena:

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

Creo que una distancia ponderada de Manhattan tiene mucho más sentido cuando se comparan los colores. Recuerde que las primarias de color están solo en nuestra cabeza. Ellos no tienen ningún significado físico. CIELab y CIELuv se modelan estadísticamente a partir de nuestra percepción del color.

Para rápido y sucio, puedes hacer

 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); } 

haciendo uso de la división de enteros para cuantificar los colores.