Como comparar duas colors para similaridade / diferença

Eu quero projetar um programa que possa me ajudar a avaliar entre 5 colors pré-definidas, que é mais semelhante a uma cor variável, e com qual porcentagem. O problema é que não sei como fazer isso manualmente, passo a passo. Por isso, é ainda mais difícil pensar em um programa.

Mais detalhes: As colors são de fotografias de tubos com gel que têm colors diferentes. Eu tenho 5 tubos com colors diferentes, cada um é representativo de 1 de 5 níveis. Quero tirar fotografias de outras amostras e, no computador, avaliar em que nível essa amostra pertence, comparando as colors, e quero saber isso com uma porcentagem de aproximação também. Eu gostaria de um programa que faça algo assim: http://www.colortools.net/color_matcher.html

Se você puder me dizer quais os passos a dar, mesmo que sejam coisas para eu pensar e fazer manualmente. Seria muito útil.

Veja o artigo da Wikipedia sobre diferença de colors para os leads certos. Basicamente, você deseja calcular uma métrica de distância em algum espaço de colors multidimensional. Mas o RGB não é “perceptualmente uniforme”, então sua métrica de distância RGB euclidiana sugerida por Vadim não corresponderá à distância percebida pelo ser humano entre as colors. Para começar, L a b * deve ser um espaço de colors perceptualmente uniforme, e a métrica deltaE é comumente usada. Mas há espaços de colors mais refinados e fórmulas deltaE mais refinadas que se aproximam da percepção humana.

Você terá que aprender mais sobre espaços de colors e iluminantes para fazer as conversões. Mas para uma fórmula rápida que seja melhor que a métrica RGB euclidiana, faça o seguinte: presuma que seus valores RGB estejam no espaço de colors sRGB, encontre as fórmulas de conversão sRGB para L a b *, converta suas colors sRGB em L a b *, e compute deltaE entre seus dois valores L a b *. Não é computacionalmente caro, são apenas algumas fórmulas não-lineares e algumas multiplicações e acréscimos.

Apenas uma ideia que primeiro veio à minha mente (desculpe se estúpido). Três componentes de colors podem ser assumidas coordenadas 3D de pontos e, em seguida, você pode calcular a distância entre os pontos.

FE

 Point1 has R1 G1 B1 Point2 has R2 G2 B2 

Distância entre colors é

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

A porcentagem é

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

Um valor de cor tem mais de uma dimensão, portanto, não há maneira intrínseca de comparar duas colors. Você deve determinar, para seu caso de uso, o significado das colors e, portanto, como melhor compará-las.

O mais provável é que você queira comparar as propriedades de matiz, saturação e / ou luminosidade das colors em oposição aos componentes vermelho / verde / azul. Se você está tendo problemas para descobrir como você deseja compará-los, pegue alguns pares de amostras de colors e compare-as mentalmente, depois tente justificar / explicar para si mesmo porque elas são semelhantes / diferentes.

Depois que você souber quais propriedades / componentes das colors deseja comparar, será necessário descobrir como extrair essas informações de uma cor.

Muito provavelmente você só precisará converter a cor da representação comum de RedGreenBlue para HueSaturationLightness, e então calcular algo como

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

Este exemplo forneceria um valor escalar simples, indicando até que ponto o gradiente / matiz das colors é um do outro.

Veja HSL e HSV na Wikipedia .

Se você tiver dois objects Color c1 e c2 , basta comparar cada valor RGB de c1 com o 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()); 

Esses valores você pode simplesmente dividir pela quantidade de saturação de diferenças (255), e você terá a diferença entre os dois.

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

Depois disso, você pode encontrar a diferença média de colors em porcentagem.

 (pctDiffRed + pctDiffGreen + pctDiffBlue) / 3 * 100 

O que lhe daria uma diferença na porcentagem entre c1 e c2 .

na verdade eu andei no mesmo caminho há alguns meses atrás. não há uma resposta perfeita para a pergunta (que foi perguntada aqui um par de vezes) mas há uma mais sofisticada do que a resposta sqrt (rr) etc e mais fácil de implantar diretamente com RGB sem mover para todos os tipos de espaços alternativos de colors. Eu encontrei esta fórmula aqui que é uma aproximação de baixo custo da fórmula real bastante complicada (pelo CIE que é o W3C de cor, já que esta é uma quest não terminada, você pode encontrar equações de diferenças de cor mais antigas e mais simples lá). boa sorte

Edit: para a posteridade, aqui está o 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)); } 

Um dos melhores methods para comparar duas colors pela percepção humana é o CIE76. A diferença é chamada Delta-E. Quando é menor que 1, o olho humano não consegue reconhecer a diferença.

Existe uma maravilhosa class de utilitários de cor, ColorUtils (código abaixo), que inclui os methods de comparação CIE76. É escrito por Daniel Strebel, da Universidade de Zurique.

De ColorUtils.class eu uso o método:

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

r1, g1, b1 – valores RGB da primeira cor

r2, g2, b2 – valores RGB da segunda cor que você gostaria de comparar

Se você trabalha com Android, você pode obter esses valores assim:

r1 = Color.red(pixel);

g1 = Color.green(pixel);

b1 = Color.blue(pixel);


ColorUtils.class por Daniel Strebel, Universidade de Zurique:

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

Apenas outra resposta, embora seja semelhante à de Supr – apenas um espaço de cor diferente.

A coisa é: os seres humanos percebem a diferença de cor não uniformemente e o espaço de colors RGB está ignorando isso. Como resultado, se você usar o espaço de colors RGB e apenas calcular a distância euclideana entre 2 colors, poderá obter uma diferença que é matematicamente correta, mas que não coincidiria com o que os humanos diriam a você.

Isso pode não ser um problema – a diferença não é tão grande, eu acho, mas se você quiser resolver isso “melhor” você deve converter suas colors RGB em um espaço de colors que foi especificamente projetado para evitar o problema acima. Existem vários, melhorias de modelos anteriores (uma vez que isso é baseado na percepção humana, precisamos medir os valores “corretos” com base em dados experimentais). Há o espaço de colors do Lab, que acho que seria o melhor, embora um pouco complicado para convertê-lo. Mais simples seria o CIE XYZ .

Aqui está um site que lista as fórmulas para converter entre diferentes espaços de colors para que você possa experimentar um pouco.

A melhor maneira é deltaE. DeltaE é um número que mostra a diferença das colors. Se deltae <1 então a diferença não pode reconhecer pelos olhos humanos. Eu escrevi um código em canvas e js para converter rgb em lab e depois calcular delta e. Neste exemplo, o código está reconhecendo pixels que têm cores diferentes com uma cor de base que salvei como LAB1. e, se for diferente, faz com que esses pixels fiquem vermelhos. Você pode aumentar ou reduzir a sensibilidade da diferença de cor ao aumentar ou diminuir o intervalo aceitável de delta e. Neste exemplo eu atribuí 10 para deltaE na linha que escrevi (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 os methods abaixo resultam em uma escala de 0 a 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 você queira analisar uma imagem inteira no final, não é? Assim, você pode verificar a menor / maior diferença para a matriz de colors de identidade.

A maioria das operações matemáticas para processamento de charts usa matrizes, porque os algoritmos possíveis que os utilizam são freqüentemente mais rápidos do que os cálculos de distância e comparação de ponto a ponto. (por exemplo, para operações usando DirectX, OpenGL, …)

Então eu acho que você deveria começar aqui:

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

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

… e como Beska já comentou acima:

Isso pode não dar a melhor diferença “visível” …

O que significa também que seu algoritmo depende da sua definição de “semelhante a” se você estiver processando imagens.

Um método simples que usa apenas 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) 

Eu usei este por um tempo agora, e funciona bem o suficiente para a maioria dos propósitos.

Você precisará converter todas as colors RGB no espaço de colors Lab para poder compará-las da maneira que os humanos as veem. Caso contrário, você estará obtendo colors RGB que ‘combinam’ de maneiras muito estranhas.

O link da Wikipédia sobre diferenças de colors fornece uma introdução aos vários algoritmos de diferença de espaço de colors do Lab que foram definidos ao longo dos anos. O mais simples que apenas verifica a distância euclidiana de duas colors de laboratório, funciona, mas tem algumas falhas.

Convenientemente, há uma implementação Java do algoritmo CIEDE2000 mais sofisticado no projeto OpenIMAJ . Forneça seus dois conjuntos de colors de laboratório e ele retornará o valor da distância única.

A única maneira “certa” de comparar colors é fazer isso com o deltaE no CIELab ou no CIELuv.

Mas para muitas aplicações eu acho que isso é uma boa aproximação:

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

Eu acho que uma distância ponderada de Manhattan faz muito mais sentido quando comparamos colors. Lembre-se que as colors primárias estão apenas na nossa cabeça. Eles não têm nenhum significado físico. O CIELab e o CIELuv são modelados estatisticamente a partir da nossa percepção de cor.

Para rápido e sujo, você pode fazer

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

fazendo uso de divisão inteira para quantizar as colors.