SYR Tool 1.0

Du filtrage des couleurs…

Yves -12 octobre 2005

 

"Dis papa c'est quoi cette bouteille de lait ?"

C'est une très bonne question. Et bien sachez que SYR s'intéresse au filtrage des couleurs parce que les seules informations dont il dispose actuellement sont les informations de ses caméras (Pascal aurait cependant branché dernièrement un micro sous le chassis).

De ce fait, les seules informations dont il dispose afin de se faire une idée de son environnement sont des images. Heureusement pour nous les objets et les personnes apparaissent généralement d'une couleur plus ou moins uniforme.

Ex : la peau est rose, l'herbe est verte, ….

Ainsi, en reconnaissant les couleurs d'une image, nous pouvons segmenter celle-ci en région de même couleur ce qui est un premier pas dans la reconnaissance de zones de l'espace distinctes comme des objets.

Pour le premier test d'évaluation de SYR, on se propose à titre d'exercice de reconnaître une cible rouge.

Mais je vous vois déjà me dire : "Dis papa c'est quoi cette bouteille de lait ?"

Et vous n'auriez pas tort.

En effet, les couleurs sont souvent codées grâce à une combinaison de 3 couleurs de base : Rouge, Vert et Bleu. Ainsi le violet est un mélange de Rouge et de Bleu, le jaune un mélange de Rouge et de Vert. Généralement, les quantités de rouge, de vert et de bleu d'une couleur donnée sont des valeurs entre 0 et 255.

Il semblerait alors judicieux pour reconnaître une cible rouge de vérifier que la quantité de rouge qui la compose est assez élevée de la façon suivante :

SI Rouge > 200 ALORS l'objet est rouge SINON il ne l'est pas

200 apparaissant comme une valeur suffisamment élevée.

Malheureusement il n'en est pas ainsi.

En effet, si je considère la couleur suivante :

Il s'agit d'un rouge ayant comme composantes :

Si je rajoute à ce rouge un peu de vert, j'obtiens la couleur suivante :

Il s'agit d'un orange :

Enfin si je rajoute à ce orange, un peu de bleu, j'obtiens la couleur suivante :

Il s'agit d'un rose ou d'un rouge clair :

On pourrait se dire que le rose n'est pas du rouge malheureusement dans la réalité, certains pixels d'un objet rouge peuvent paraître rose à cause du reflet de la lumière (par exemple). Il est donc important de considérer que le rose se rapproche plus du rouge clair que du bleu foncé.

A ce stade, on se rend compte que le fait qu'une couleur apparaissent rouge n'est pas dû uniquement à la valeur de sa composante rouge mais dépend de la proportion de bleu et de vert qui la compose. Bref…..c'est pas simple.

Heureusement SYR est cool : il n'a pas de problème, il n'a que des solutions.

Il décide de changer de modèle de représentation des couleurs : on ne va plus représenter les couleurs par les composantes Rouge, Vert et Bleu (RVB ou bien RGB en anglais) mais par les composantes Teinte, Luminance et Chrominance (HLS en anglais).

Et là, vous de me dire en cœur : "mais dis papa c'est quoi cette bouteille de lait ?"

Et moi de vous répondre :

HLS c'est comme une autre palette de peinture : en mélangeant du H, du L et du S, je suis capable d'obtenir les mêmes couleurs qu'en mélangeant du R, du V et du B.

Avec cette différence qui fait que HLS est beaucoup plus adapté pour reconnaître des couleurs : H représente directement la teinte !

Quant à L et S :

Ce que l'on comprend mieux sur le dessin suivant représentant dans le cercle la Teinte et dans le carré les différentes variation de Luminance et de Chrominance pour la teinte sélectionnée : ici, le violet.

Note : sur le schéma on prend conscience que la teinte est cyclique, à savoir que si sa valeur se situe entre 0 et 360, et que je la fais croître à partir de 0 une fois que je dépasse 360 je reviens à 0. En fait, la teinte se mesure en degrés. La luminance et la chrominance ne sont pas cyclique, lorsque nous les calculerons elle pourront prendre des valeurs entre 0 et 600 (dans les algorithmes développés la teinte toujours cyclique prendra également des valeurs entre 0 et 600, question d'échelle…..)

Ce que je suis en train de dire c'est que nous allons transformer les composantes RVB en composantes HLS parce que HLS est plus adapté à la reconnaissance de couleurs. Il existe donc un programme capable de me calculer HLS lorsque je connais RVB. Je donne ce programme écrit en Visual Basic en annexe.

Ainsi, H, la teinte, répond en grande partie à la question : "est-ce que c'est rouge ?"

Mais on comprend également que L et S détiennent aussi une partie de la réponse à cette question. En effet, une couleur qui aura une grande luminance sera tellement claire qu'elle se rapprochera plus du blanc que du rouge. Selon le même principe, une couleur qui aura une faible chrominance se rapprochera plus du gris que du rouge.

Pour répondre à la question "est-ce que c'est rouge ?", il faudra donc demander à H, à L et à S puis il faudra combiner leurs réponses.

Et la bouteille de lait dans tout ca ?

Il est vrai qu'il existe des variations de rouge de telle sorte qu'une teinte donnée peut paraître plus ou moins rouge.

Par exemple, lorsque la teinte est égale à 0 la couleur associée est le rouge (le haut du cercle sur le dessin du dessus). Lorsque la teinte est à 10, la couleur associée est un peu moins rouge mais rouge quand même. A 20, elle tire vers le orange. En d'autres termes; lorsque la teinte est 0 c'est rouge et plus je m'éloigne de 0 moins c'est rouge

Il faut croire que si on me demande : "est-ce que c'est rouge ?", je peux répondre : " à moitié". La bonne question semble donc être plutôt la suivante : "combien c'est rouge ?".

Et la réponse doit donc être un nombre représentatif de combien c'est rouge. Disons pour faire simple que je décide que : 1 c'est rouge, 0.5 c'est à moitié rouge et 0 c'est pas rouge du tout. Tout autre nombre entre 0 et 1 représente autant de variante de la réponse à la question "combien c'est rouge ?" : "c'est presque rouge", "c'est carrément rouge", "c'est très peu rouge".

Ca y est le problème est posé : j'ai une teinte qui varie de 0 à 360 et je dois lui associer un nombre entre 0 et 1 indiquant dans quelle mesure la teinte est rouge.

C'est à dire lorsque la teinte est 0, ma réponse est 1 et plus je m'éloigne plus je répond une valeur proche de 0.

Voyons voir, cela doit pouvoir ressembler à quelque chose comme ca :

Vous avez raison : cette courbe est très jolie. De surcroît, intuitivement, on comprend que comme la forme de cette courbe est "fluide", elle permet de modéliser une réponse "fluide" à la question "combien c'est rouge".

Egalement on comprend la puissance d'une telle courbe. Par exemple, disons que je la déplace vers la droite (sur l'axe des teintes) pour que son centre se situe vers 120 qui correspond à la teinte bleue. Hé bien voilà que ma courbe répond à la question : "Combien c'est bleu" : un peu bleu, pas bleu du tout, complètement bleu.

Mais, ca s'arrête pas la ! En fonction que je la dessine large ou fine cette courbe elle permet de tenir compte de la subjectivité de la question "combien c'est rouge ?" Si je la dessine très large, à l'extrême même le orange et le violet sont considérés comme rouge. Au contraire, si je la dessine fine, seul le rouge qui est rouge-rouge (c'est à dire vraiment rouge) sera considéré comme rouge. Un rouge ne serait-ce que légèrement orangé ne serait alors pas considéré comme rouge.

Bref, cette courbe elle est vachement intelligente. Et comme vous l'êtes aussi vous allez me demander : "d'accord mais je vais être obligé de prendre un crayon pour dessiner la courbe que je veux où je veux ?"

On va faire plus simple : disons qu'avec 2 paramètres vous allez définir la forme et la position exacte de votre courbe.

Regardez le dessin suivant :

Il s'agit d'une courbe de Gauss, c'est à dire que c'est une fonction mathématique dont je vous donne la formule à titre informatif seulement :

Gauss(Teinte) = Combien c'est rouge (Teinte) = Exp(-0.5 * ((Teinte - m ) /s ) ^ 2)

Où Exp est la fonction exponentielle

Cette formule peut paraître rébarbative et compliquée.

Ce qui est important, c'est qu'on y voit 2 paramètres :

Voilà, en modifiant seulement les valeurs de ces 2 paramètres et quelque soit mon niveau en mathématique, je dessine les courbes de Gauss que je veux. Décidémment : je suis très fort !

Comme vous savez maintenant comment dire si la teinte H est rouge ou pas et surtout de combien, vous allez me regarder en demandant votre reste :

"d'accord ! mais ….et la bouteille de lait ?!"

Hé oui, comme je vous l'ai dit L la luminance (quantité de lumière) et S la chrominance (quantité de couleur) ont leur mot à dire.

Afin de prendre en compte leur avis de manière simple et intelligente, je vous propose de faire pour la luminance L et la chrominance S la même chose que nous avons fait pour la teinte H.

Nous allons ainsi définir 2 autres courbes de Gauss qui vont répondre aux questions :

"est-ce que cette chrominance correspond à du rouge et surtout de combien ?"

"est-ce que cette luminance correspond à du rouge et surtout de combien ?"

Mais oui ! rappelez vous, si la chrominance est trop faible, y'a pas assez de couleur, c'est pas rouge….c'est gris ! Il faut pas que je dise que c'est rouge ! C'est pareil si la luminance est trop importante, c'est que y'a trop de lumière, c'est pas rouge, c'est blanc !

La encore chacune de ces 2 courbes va être définie par 2 paramètres : la moyenne et l'écart-type.

Cependant pour déterminer si la chrominance S correspond à du rouge, on va s'intéresser à de fortes valeurs : plus y'a de couleur, plus c'est rouge. Donc nous allons centrer cette courbe sur la valeur maximale de chrominance (600) afin que quand la chrominance est maximale la courbe de Gauss définie pour la chrominance S me réponde : "cette chrominance peut être du rouge avec une valeur très possible de 1". De plus, en choisissant un écart-type faible pour la chrominance on s'intéressera aux couleurs vives.

Pour déterminer si la luminance L correspond à du rouge, on va s'intéresser à des valeurs moyennes : ni trop claires ni trop foncées. Donc, nous allons centrer cette courbe sur la valeur moyenne de luminance (300) afin d'éliminer les couleurs qui soient trop claires ou trop foncées pour décider si elles sont rouges ou pas.

Voilà on a donc 3 courbes de Gauss chacune "filtrant" la réponse à la question "combien c'est rouge" pour la teinte H, la luminance L et la chrominance S ce qui est résumé dans le dessin suivant :

C'est le moment de vous montrer une copie d'écran de l'application SYR Tool qui va vous permettre de visualiser tout ca.

Pour comprendre cet écran il faut procéder dans l'ordre.

Cette boite de dialogue permet de nous aider à sélectionner des paramètres pour nos 3 courbes de Gauss afin de filtrer la couleur que l'on désire. Il y a 6 paramètres, 2 par courbes de Gauss : il s'agit des valeurs "Moy." (moyenne) et "Sig." (écart-type) dans les cadres "H Filter", "L Filter", "S filter".

Le cadre RGB va constituer un échantillon de couleurs tests qui vont être filtrées afin d'évaluer la pertinence des paramètres que nous avons choisis pour les courbes de Gauss H, L et S.

Ainsi, dans ce cadre est affiché la palette des variantes de couleurs possible pour une valeur donnée de rouge (puisqu'ici R est sélectionné). La valeur ici est 128 (sur 255). L'axe horizontal définit de gauche à droite des valeurs croissantes de quantité de vert. L'axe vertical définit de haut en bas des valeurs croissantes de bleu. Par exemple, dans ce cadre, le pixel en haut à gauche est de la couleur {rouge = 128, vert = 0, bleu = 0} et le pixel en bas à droite est de la couleur {rouge = 128, vert = 255, bleu = 255}.

Chacun des pixels colorés de ce cadre va être filtré au travers de chacun des 3 filtres H, L et S dans le cadre correspondant et à la même position (ie : le pixel au milieu du cadre RGB correspond au pixel au milieu du cadre H Filter etc.)

Dans les cadres H Filter, L Filter, S Filter, on observe donc la réponse à la question posé au filtre : "de combien le pixel dans le cadre RGB est-il de la couleur désirée ?". La réponse est entre 0 et 1. Ainsi plus le pixel est blanc plus le filtre répond 1, plus le pixel est noir plus le filtre répond 0.

On observe donc des dégradés de noir et de blanc : pour chaque filtre, en reportant les zones les plus claires dans le cadre RGB, on visualise les couleurs considérés comme rouge par ce filtre ; en reportant les zones les plus foncés dans le cadre RGB on visualise les couleurs considérées comme non rouge par ce filtre.

Ca y est, décidémment on y échappe pas, il est à nouveau temps de se poser cette question fondamentale : "Mais ?! et la bouteille de lait ?!"

Car oui, il s'agit de combiner les filtres H, L et S en un filtre unique qui synthétise la réponse de mes 3 filtres en une seule et même réponse aux questions : "de combien un pixel est t'il d'une couleur donnée ?", "Est-ce que le pixel est un peu vert, complètement rouge ou presque violet ?"

Pour combiner ces filtres, on les multiplie. La réponse globale est 1 si chacun a répondu 1 puisque 1 x 1 x 1 = 1. Si l'un d'eux répond 0.1 alors que les autres répondent 1, la réponse globale sera définitivement 0.1 puisque 1 x 1 x 0.1 = 0.1

Il suffit donc qu'un filtre dise que ce n'est pas rouge pour que cela ne le soit pas ce qui est logique puisque chaque filtre traite une composante indépendante.

Et voilà ! on a réussi à répondre à la question "combien c'est rouge ?" et cette réponse se trouve représentée dans le cadre HLS.Filter.

Cependant, il serait temps de savoir si "oui ou non c'est rouge".parce que l'indécision dans le processus de décision, ca peut finir par devenir gênant.

En toute logique on peut se dire : si la réponse est inférieure à 0.5, y'a plus de chance que ce ne soit pas vrai donc je répond "c'est pas rouge". A l'inverse si c'est supérieur à 0.5 on maximiserait nos chances en répondant "c'est rouge". Ainsi on effectuerait un seuillage précis à la valeur 0.5.

On serait alors en droit de se demander s'il est normal que 0.49 corresponde à "pas rouge" et 0.51 à "rouge" alors que ces valeurs sont très proches. Pour prendre en compte cette remarque, on incorpore un 4ème filtre qui va effectuer un seuillage flou.

L'idée est donc de rendre la réponse du filtre moins indécise (c'est à dire de choisir entre "Rouge" et "Pas rouge") et en même temps sans que la limite de cette décision soit trop brutale.

Le schéma suivant montre ce que l'on veut. On veut transformer la valeur du filtre HLS (axe horizontal) en une valeur finale (axe vertical) qui indique toujours si c'est rouge ou non mais avec plus de détermination. Dans ce schéma on associe donc une valeur de l'axe horizontal vers une valeur de l'axe vertical.

On y voit 3 fonctions :

La troisième courbe est de type sigmoïde. Afin d'implémenter la solution décrite dans le précédent schéma nous allons avoir recours à la formule mathématique d'une sigmoïde que je donne ici à des fins informatives seulement :

Sigmoïde (x) = Seuillage flou (filtre HLS) = 1/(1+Exp(s *(filtre HLS - m ))

Note : les experts remarqueront que cette fonction n'est pas nulle en 0 et n'est pas égale à 1 en 1. Il s'agit ici d'une déformation intentionnelle de la réalité afin de tenir compte des contraintes pédagoqiques.

L'essentiel à retenir est que l'on retrouve 2 paramètres qui permettent de définir précisément la forme de la courbe :

Ainsi grâce à cette fonction de seuillage flou nous pouvons accentuer la prise de décision autour d'un seuil (ie : ce qui n'était pas sur devient un peu plus certain et inversement) et la façon dont nous le faisons n'est pas brutale mais douce et continue. La copie d'écran suivante montre un même filtre HLS seuillé de trois façons différentes.

Et voilà, il nous aura fallut 4 filtres pour arriver de manière précise et floue à la fois (précisément floue) à répondre à la question "est-ce que c'est rouge ?"

C'est une question primordiale avouons le, surtout lorsqu'on cherche une cible rouge lors d'un test d'évaluation. Grâce à SYR Tool et son algorithme de filtrage de couleur, nous pouvons ainsi repérer cette cible rouge.

La copie d'écran suivante montre (hormi le fait que je suis en train de changer la tapisserie de mon salon) :

Mais comme on peut également répondre aux questions : "combien c'est rouge ?" ou "combien c'est bleu", on peut répondre aux questions "est-ce que le raisin est assez mur ?" ou encore "est-ce que le ciel est très bleu ?" et par extrapolation "quel temps fait t'il ?"

Mieux encore SYR peut vous complimenter sur la couleur de votre pull ou disserter sur un tableau de Gauguin ou de Kandinsky : "ce jaune-moutarde est très joli, ce rouge vermeille est magnifique, ce vert émeraude me fait rêver"

Mais non des moindres, grâce à cet outil de filtrage de couleur SYR dispose d'une bonne base pour reconnaître des couleurs et répondre à la question : "mais bon sang, où est-ce que je suis donc ?"

Peut-être pourra t'il enfin reconnaître son ennemi juré le renard ?

Peut-être pourra t'il rouler des mécaniques devant ses copines les poules ?

Et même, on peut même se prendre à rêver qu'un jour il puisse répondre à cette question énigmatique : "Dis papa, c'est quoi cette bouteille de lait ?"

 

 

 

~o~

 

Annexe

Voici les algorithmes de conversion entre les modèles de couleur RGB et HSV.

IMPORTANT : LES VALEURS H, L ET S PRENNENT TOUTES 3 DANS CES ALGORITHMES DES VALEURS ENTRE 0 ET 600

Public Sub RGBtoHLS(ByVal R As Integer, ByVal G As Integer, ByVal B As Integer, rH As Integer, rL As Integer, rS As Integer)

Dim nMax As Integer

Dim nMin As Integer

Dim nRdelta As Integer

Dim nGdelta As Integer

Dim nBdelta As Integer

Dim nRGBs(0 To 2) As Integer

Dim nHLSMax6 As Integer

Dim nMaxMinDelta As Integer

Dim nMaxMinDelta2 As Integer

If HLSmax = 0 Then

HLSmax = kHLSmax

End If

nRGBs(0) = R

nRGBs(1) = G

nRGBs(2) = B

nMax = zMax(nRGBs)

nMin = zMin(nRGBs)

rL = (((nMax + nMin) * HLSmax) + kRGBmax) / (2 * kRGBmax)

If nMax = nMin Then

rH = -1

Else

nMaxMinDelta = nMax - nMin

If rL <= HLSmax / 2 Then

rS = ((nMaxMinDelta * HLSmax) + ((nMax + nMin) / 2)) / (nMax + nMin)

Else

rS = ((nMaxMinDelta * HLSmax) + ((2 * kRGBmax - nMax - nMin) / 2)) / (2 * kRGBmax - nMax - nMin)

End If

nHLSMax6 = HLSmax / 6

nMaxMinDelta2 = nMaxMinDelta / 2

nRdelta = (((nMax - R) * nHLSMax6) + nMaxMinDelta2) / nMaxMinDelta

nGdelta = (((nMax - G) * nHLSMax6) + nMaxMinDelta2) / nMaxMinDelta

nBdelta = (((nMax - B) * nHLSMax6) + nMaxMinDelta2) / nMaxMinDelta

Select Case nMax

Case R

rH = nBdelta - nGdelta

Case G

rH = (HLSmax / 3) + nRdelta - nBdelta

Case Else

rH = ((2 * HLSmax) / 3) + nGdelta - nRdelta

End Select

If rH < 0 Then

rH = rH + HLSmax

End If

If rH > HLSmax Then

rH = rH - HLSmax

End If

End If

End Sub

Public Sub HLStoRGB(ByVal H As Integer, ByVal L As Integer, ByVal S As Integer, rR As Integer, RG As Integer, rB As Integer)

Dim nH As Long

Dim nL As Long

Dim nS As Long

Dim nR As Long

Dim nG As Long

Dim nB As Long

Dim n1 As Long

Dim n2 As Long

Dim nHLSMax2 As Long

Dim nHLSMax3 As Long

If HLSmax = 0 Then

HLSmax = kHLSmax

End If

nH = H

nL = L

nS = S

If nS = 0 Then

nR = (nL * kRGBmax) / HLSmax

rR = nR

RG = nR

rB = nR

Else

nHLSMax2 = HLSmax / 2

nHLSMax3 = HLSmax / 3

If nL <= nHLSMax2 Then

n2 = (nL * (HLSmax + nS) + nHLSMax2) / HLSmax

Else

n2 = nL + nS - ((nL * nS) + nHLSMax2) / HLSmax

n1 = 2 * nL - n2

End If

rR = (zHueToRGB(n1, n2, nH + nHLSMax3) * kRGBmax + nHLSMax2) / HLSmax

RG = (zHueToRGB(n1, n2, nH) * kRGBmax + nHLSMax2) / HLSmax

rB = (zHueToRGB(n1, n2, nH - nHLSMax3) * kRGBmax + nHLSMax2) / HLSmax

End If

End Sub

Private Function zHueToRGB(ByVal n1 As Long, ByVal n2 As Long, Hue As Long) As Long

If Hue < 0 Then

Hue = Hue + HLSmax

End If

If Hue > HLSmax Then

Hue = Hue - HLSmax

End If

Select Case Hue

Case Is < HLSmax / 6

zHueToRGB = n1 + (((n2 - n1) * Hue + (HLSmax / 12)) / (HLSmax / 6))

Case Is < HLSmax / 2

zHueToRGB = n2

Case Hue < (HLSmax * 2) / 3

zHueToRGB = n1 + (((n2 - n1) * (((HLSmax * 2) / 3) - Hue) + (HLSmax / 12)) / (HLSmax / 6))

Case Else

zHueToRGB = n1

End Select

End Function

Private Function zMax(Values() As Integer) As Integer

Dim nMax As Integer

Dim i As Integer

For i = 0 To 2

If Values(i) > nMax Then

nMax = Values(i)

End If

Next 'i

zMax = nMax

End Function

Private Function zMin(Values() As Integer) As Integer

Dim nMin As Integer

Dim i As Integer

nMin = Values(0)

For i = 1 To 2

If Values(i) < nMin Then

nMin = Values(i)

End If

Next 'i

zMin = nMin

End Function