Comment comparer deux couleurs pour la similarité / la différence

Je veux concevoir un programme qui peut m’aider à évaluer entre 5 couleurs prédéfinies dont l’une est plus proche d’une couleur variable et avec quel pourcentage. La chose est que je ne sais pas comment faire cela manuellement, étape par étape. Il est donc encore plus difficile de penser à un programme.

Plus de détails: Les couleurs proviennent de photographies de tubes avec du gel de différentes couleurs. J’ai 5 tubes de couleurs différentes, chacun représentant 1 des 5 niveaux. Je veux prendre des photos d’autres échantillons et sur l’ordinateur évaluer à quel niveau cet échantillon appartient en comparant les couleurs, et je veux savoir cela avec un pourcentage d’approximation aussi. Je voudrais un programme qui fait quelque chose comme ça: http://www.colortools.net/color_matcher.html

Si vous pouvez me dire quelles mesures prendre, même si ce sont des choses à penser et à faire manuellement. Ce serait très utile.

Voir l’article de Wikipedia sur la différence de couleur pour les bonnes pistes. Fondamentalement, vous voulez calculer une mésortingque de distance dans un espace colorimésortingque multidimensionnel. Mais RVB n’est pas “perceptuellement uniforme”, de sorte que votre mesure de distance Euclidienne RVB proposée par Vadim ne correspondra pas à la distance entre les couleurs perçue par l’homme. Pour commencer, L a b * se veut un espace de couleurs perceptuellement uniforme, et la mésortingque deltaE est couramment utilisée. Mais il existe des espaces de couleurs plus raffinés et des formules deltaE plus raffinées qui se rapprochent de la perception humaine.

Vous devrez en apprendre davantage sur les espaces de couleurs et les illuminants pour effectuer les conversions. Mais pour une formule rapide et meilleure que la mésortingque Euclidienne RVB, faites ceci: supposez que vos valeurs RVB se trouvent dans l’espace colorimésortingque sRVB, trouvez les formules de conversion sRVB dans L a b *, convertissez vos couleurs sRVB en L a b *, et calculer deltaE entre vos deux valeurs L a b *. Ce n’est pas coûteux en calcul, il ne s’agit que de formules non linéaires, certaines se multiplient et s’ajoutent.

Juste une idée qui me vint en premier (désolé si stupide). Trois composantes de couleurs peuvent être supposées des coordonnées 3D de points et vous pouvez ensuite calculer la distance entre les points.

FE

 Point1 has R1 G1 B1 Point2 has R2 G2 B2 

La distance entre les couleurs est

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

Le pourcentage est

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

Une valeur de couleur a plus d’une dimension, il n’y a donc aucun moyen insortingnsèque de comparer deux couleurs. Vous devez déterminer pour vos cas d’utilisation la signification des couleurs et ainsi les comparer au mieux.

Vous souhaitez probablement comparer les propriétés de teinte, de saturation et / ou de luminosité des couleurs avec celles des composants rouge / vert / bleu. Si vous avez du mal à comprendre comment vous voulez les comparer, prenez des paires de couleurs d’échantillon et comparez-les mentalement, puis essayez de vous justifier / de vous expliquer pourquoi elles sont similaires / différentes.

Une fois que vous connaissez les propriétés / composants des couleurs que vous souhaitez comparer, vous devez déterminer comment extraire ces informations d’une couleur.

Vous devrez probablement simplement convertir la couleur de la représentation commune RedGreenBlue en HueSaturationLightness, puis calculer quelque chose comme:

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

Cet exemple vous donnerait une valeur scalaire simple indiquant la distance entre le dégradé / teinte des couleurs.

Voir HSL et HSV sur Wikipedia .

Si vous avez deux objects Color c1 et c2 , vous pouvez simplement comparer chaque valeur RVB de c1 avec celle 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()); 

Ces valeurs que vous pouvez simplement diviser par la quantité de saturations de différence (255), et vous obtiendrez la différence entre les deux.

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

Après quoi, vous pouvez simplement trouver la différence de couleur moyenne en pourcentage.

 (pctDiffRed + pctDiffGreen + pctDiffBlue) / 3 * 100 

Ce qui vous donnerait une différence en pourcentage entre c1 et c2 .

En fait, j’ai suivi le même chemin il y a deux mois. Il n’y a pas de réponse parfaite à la question (qui a été posée ici quelques fois) mais il y a une réponse plus sophistiquée que le sqrt (rr) etc. et plus facile à impliquer directement avec RGB sans passer à tous les espaces colorimésortingques. J’ai trouvé cette formule ici qui est une approximation peu coûteuse de la formule réelle assez compliquée (par le CIE qui est le W3C de couleur, puisque c’est une quête non terminée, vous pouvez y trouver des équations de différence de couleur plus anciennes). bonne chance

Edit: Pour la postérité, voici le code C pertinent:

 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 est l’une des meilleures méthodes pour comparer deux couleurs selon la perception humaine. La différence s’appelle Delta-E. Quand il est inférieur à 1, l’oeil humain ne peut pas reconnaître la différence.

La classe ColorUtils (code ci-dessous), qui comprend des méthodes de comparaison CIE76, contient de magnifiques utilitaires de couleur. Il est écrit par Daniel Strebel, Université de Zurich.

De ColorUtils.class j’utilise la méthode:

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

r1, g1, b1 – valeurs RVB de la première couleur

r2, g2, b2 – Valeurs RVB de la deuxième couleur que vous souhaitez comparer

Si vous travaillez avec Android, vous pouvez obtenir ces valeurs comme ceci:

r1 = Color.red(pixel);

g1 = Color.green(pixel);

b1 = Color.blue(pixel);


ColorUtils.class de Daniel Strebel, Université 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)); } } 

Juste une autre réponse, même si elle est similaire à celle de Supr – juste un espace de couleur différent.

La chose est la suivante: les humains perçoivent la différence de couleur pas uniformément et l’espace de couleur RVB ignore cela. Par conséquent, si vous utilisez l’espace colorimésortingque RVB et que vous calculez simplement la distance euclidienne entre deux couleurs, vous pouvez obtenir une différence mathématiquement correcte, mais qui ne coïnciderait pas avec ce que les humains vous diraient.

Ce n’est peut-être pas un problème – la différence n’est pas si grande que je pense, mais si vous voulez résoudre ce problème “mieux”, vous devez convertir vos couleurs RVB dans un espace couleur spécialement conçu pour éviter le problème ci-dessus. Il y en a plusieurs, des améliorations par rapport aux modèles précédents (puisque cela est basé sur la perception humaine, nous devons mesurer les valeurs “correctes” basées sur des données expérimentales). Il y a l’ espace de couleurs Lab qui, à mon avis, serait le meilleur, bien qu’un peu compliqué à convertir. Plus simple serait le CIE XYZ .

Voici un site qui répertorie les formules à convertir entre les différents espaces de couleur afin que vous puissiez expérimenter un peu.

Le meilleur moyen est deltaE. DeltaE est un nombre qui montre la différence des couleurs. Si deltae <1 alors la différence ne peut pas être reconnue par les yeux humains. J'ai écrit un code dans canvas et js pour convertir rgb en lab puis calculer delta e. Sur cet exemple, le code reconnaît les pixels de couleur différente avec une couleur de base que j'ai enregistrée en tant que LAB1. et puis si c'est différent rend ces pixels rouges. Vous pouvez augmenter ou réduire la sensibilité de la différence de couleur en augmentant ou en diminuant la plage acceptable de delta e. Dans cet exemple, j'ai attribué 10 pour deltaE dans la ligne que j'ai écrite (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 

Toutes les méthodes ci-dessous donnent une échelle 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; } } 

Je pense que vous voulez parsingr une image entière à la fin, n’est-ce pas? Ainsi, vous pouvez vérifier la plus petite / la plus haute différence dans la masortingce de couleurs de l’identité.

La plupart des opérations mathématiques de traitement des graphiques utilisent des masortingces, car les algorithmes possibles les utilisant sont souvent plus rapides que les calculs classiques de distance point par point et de comparaison. (par exemple pour les opérations utilisant DirectX, OpenGL, …)

Donc, je pense que vous devriez commencer ici:

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

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

… et comme l’a déjà commenté Beska ci-dessus:

Cela peut ne pas donner la meilleure différence “visible” …

Ce qui signifie également que votre algorithme dépend de votre définition de “similaire à” si vous traitez des images.

Une méthode simple qui utilise uniquement RVB est

 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) 

Je l’ai utilisé pendant un certain temps maintenant, et cela fonctionne assez bien dans la plupart des cas.

Vous devrez convertir toutes les couleurs RVB dans l’espace colorimésortingque Lab pour pouvoir les comparer à la manière dont les humains les voient. Sinon, vous obtiendrez des couleurs RVB qui correspondent à des manières très étranges.

Le lien wikipedia sur les différences de couleur vous donne un aperçu des différents algorithmes de différence d’espace colorimésortingque de laboratoire qui ont été définis au fil des ans. Le plus simple qui vérifie simplement la distance euclidienne de deux couleurs de laboratoire, fonctionne mais présente quelques défauts.

Il y a commodément une implémentation Java de l’algorithme CIEDE2000 plus sophistiqué dans le projet OpenIMAJ . Fournissez-lui vos deux jeux de couleurs Lab et cela vous redonnera une valeur de distance unique.

La seule façon “correcte” de comparer les couleurs est de le faire avec deltaE dans CIELab ou CIELuv.

Mais pour beaucoup d’applications, je pense que c’est une approximation suffisante:

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

Je pense qu’une distance pondérée de manhattan est beaucoup plus sensée lorsque l’on compare les couleurs. Rappelez-vous que les couleurs primaires ne sont que dans notre tête. Ils n’ont aucune signification physique. CIELab et CIELuv sont modélisés statistiquement à partir de notre perception de la couleur.

Pour rapide et sale, vous pouvez faire

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

en utilisant la division entière pour quantifier les couleurs.