5.18. Histogrammes et statistiques

En plus des opérations qui modifient les pixels d’une image, la classe Image propose une famille de méthodes qui les mesurent – résumer la distribution des valeurs de pixels, renvoyer la luminosité moyenne et médiane, trouver le seuil optimal entre les pixels sombres et clairs, indiquer l’étalement des canaux de couleur. Ces mesures alimentent les applications de deux façons : comme entrées du code qui décide quel seuil utiliser, quel gain régler, à quoi ressemble le profil tonal de la scène ; et comme signaux de diagnostic – « la scène est-elle assez lumineuse ? » – sur lesquels une application peut agir sans prendre de décision concernant un pixel particulier.

Le point de départ de presque toutes les mesures est l”histogramme.

5.18.1. L’histogramme

Un histogramme d’une image est un décompte du nombre de pixels possédant chaque valeur de luminosité possible. Pour une image en niveaux de gris, il s’agit d’une liste de décomptes indexée par les valeurs 0 à 255. Pour une image en couleur, ce sont trois listes de ce type – une par canal.

get_histogram() en calcule un :

h = img.get_histogram()

L’objet renvoyé est un résultat histogram qui expose les listes de cases (bins) par canal ainsi que quelques requêtes de haut niveau sur celles-ci. Les décomptes de cases sont normalisés de sorte que leur somme vaut 1.0 – l’histogramme décrit le profil de la distribution plutôt que le décompte absolu de pixels, ce qui rend les mesures comparables entre des images de tailles différentes.

Pour les images en niveaux de gris, l’histogramme possède un seul canal de cases, disponible via h.bins() (ou de manière équivalente h[0]). Pour les images RGB565, l’histogramme est calculé dans l’espace colorimétrique LAB présenté sur la page du seuillage binaire, avec trois canaux de cases disponibles via h.l_bins(), h.a_bins(), h.b_bins() (ou h[0], h[1], h[2]). LAB est le même choix que celui des méthodes de seuil et de suivi ; les histogrammes s’accordent avec les seuils sur l’espace dans lequel la couleur est mesurée.

5.18.2. Les cases et leur nombre

L’histogramme par défaut possède une case par valeur de pixel possible – 256 cases pour un canal 8 bits. Parfois, c’est une résolution plus fine que nécessaire pour l’application. Un classifieur qui ne s’intéresse qu’au profil grossier de la distribution sera peut-être mieux servi par un nombre de cases plus réduit – 32 voire 8 cases – ce qui à la fois s’exécute plus vite et produit un résultat plus net face au bruit. Le mot-clé bins (et les l_bins, a_bins, b_bins par canal pour la couleur) en fixe le nombre :

h = img.get_histogram(bins=32)

La portée par ROI et par seuil fonctionne de la même façon que sur toutes les autres méthodes de mesure. Passez un roi pour confiner l’histogramme à un rectangle de pixels ; passez une liste thresholds pour n’inclure que les pixels qui correspondent à ces plages. La forme par seuil est ce qui fait de « calculer l’histogramme des seuls pixels correspondants » une opération en un seul appel – un schéma courant lorsqu’une application veut caractériser la texture d’une région déjà détectée sans devoir parcourir les pixels elle-même.

Un histogramme en niveaux de gris dessiné sous forme d'une rangée de barres sur la plage de luminosité 0 à 255. La distribution présente deux pics -- un petit pic sombre et un pic clair plus grand -- séparés par une vallée nette. Trois lignes verticales sont superposées : le seuil d'Otsu dans la vallée, la moyenne décalée vers le plus grand pic clair, et la médiane plus à droite où le décompte cumulé de pixels atteint la moitié.

Un histogramme en niveaux de gris avec trois mesures de synthèse superposées : le seuil d’Otsu (le seuil qui sépare le mieux les groupes sombres et clairs), la moyenne et la médiane. Chaque mesure dit quelque chose de différent sur la même distribution.

5.18.3. Statistiques

Un histogramme est une description de la fréquence de chaque valeur ; les statistiques sont les résumés numériques qui en sont dérivés. L’objet statistics renvoyé par get_statistics() porte l’ensemble standard :

  • mean – la moyenne arithmétique des valeurs de pixels.

  • median – la valeur en dessous de laquelle se trouve la moitié des pixels.

  • mode – la valeur unique la plus fréquente.

  • stdev – l’écart-type, une mesure de l’étalement autour de la moyenne.

  • min et max – les valeurs de pixels les plus claires et les plus sombres présentes.

  • lq et uq – les seuils du quartile inférieur et du quartile supérieur.

Pour une image RGB565, les formes par canal (l_mean, a_median, b_mode, et ainsi de suite) fournissent les mêmes mesures canal par canal.

La plupart de ces nombres apparaissent dans des contextes précis. mean et stdev donnent ensemble une estimation du bruit : une scène censée être uniforme a un faible stdev, tandis qu’un capteur bruité donne à la même scène un stdev plus élevé. min et max donnent le contraste de l’image : plus ils sont proches, plus la scène est plate ; plus ils sont éloignés, plus l’algorithme dispose de plage dynamique pour travailler. median est le centre robuste lorsque la distribution comporte des valeurs aberrantes (quelques pixels très clairs ne tirent pas la médiane comme ils tirent la moyenne). mode est la valeur unique la plus fréquente, utile pour trouver le niveau d’arrière-plan d’une image dont l’arrière-plan couvre la plupart des pixels.

get_statistics() exécute le passage d’histogramme en interne puis le résume ; passer les mêmes arguments thresholds et roi qu’un histogramme calculé précédemment produit les statistiques pour le même ensemble de pixels.

5.18.4. Centiles et recherches dans la CDF

L’objet histogram expose une méthode get_percentile() qui transforme une fraction en une valeur de pixel – la valeur en dessous de laquelle se trouve la fraction de pixels demandée. h.get_percentile(0.5) est la médiane ; h.get_percentile(0.05) et h.get_percentile(0.95) donnent ensemble un min/max robuste qui ignore les 5 % inférieurs et supérieurs en tant que valeurs aberrantes.

C’est la forme qu’une application utilise lorsqu’elle veut caractériser la plage des valeurs de pixels sans laisser une poignée de pixels clairs ou sombres isolés fausser la réponse. Le min/max robuste issu des 5e et 95e centiles est aussi l’entrée naturelle d’un passage d’étirement de contraste – le remappage par pixel que couvre la page sur les corrections tonales.

5.18.5. La méthode d’Otsu

Les histogrammes répondent à une autre question qui mérite d’être mentionnée à part : étant donné une image dont les pixels se répartissent en un groupe « sombre » et un groupe « clair », quel est le seuil entre les deux ? La page sur le seuillage a déjà nommé le mécanisme par son résultat – un unique seuil global que l’application peut transmettre à binary() – mais a différé le comment. Le comment, c’est la méthode d’Otsu, et elle réside dans l’histogramme.

L’intuition : une image avec un premier plan et un arrière-plan nets possède deux groupes dans son histogramme de luminosité, avec une vallée entre eux. Le bon endroit pour seuiller est le fond de la vallée – la valeur où les deux groupes sont le mieux séparés. La méthode d’Otsu parcourt tous les seuils possibles et choisit celui pour lequel les variances intra-groupes sont les plus petites (ce qui revient à dire que la variance inter-groupes est la plus grande), et le résultat est la séparation binaire optimale pour la distribution propre à cette image particulière.

L’objet histogram expose Otsu via get_threshold :

h = img.get_histogram()
t = h.get_threshold()

L’objet threshold renvoyé possède des attributs value (pour les niveaux de gris) ou l_value / a_value / b_value (pour la couleur) portant le seuil choisi. Réinjecter directement le résultat dans binary() donne un seuil global auto-ajustable dont le seuil est choisi par l’image elle-même :

img.binary([(t.value, 255)])

Ce schéma ne résout pas le problème de l’éclairage inégal que résout le seuil adaptatif basé sur des filtres ; ce qu’il résout, c’est la question « à quelle valeur dois-je seuiller ? » lorsque le seuillage global est déjà la bonne approche. Pour une scène dont la distinction premier plan / arrière-plan est bien définie, la valeur que choisit Otsu se situe généralement à quelques unités de celle qu’un humain choisirait à l’œil.

5.18.6. Calcul sur une image de différence

Un détail utile sur get_histogram() et get_statistics() : toutes deux acceptent un mot-clé difference qui prend une autre Image et calcule l’histogramme (ou les statistiques) de la différence par pixel entre la source et cette image, sans allouer d’image de différence séparée. C’est la façon économique de demander « de combien la scène a-t-elle changé depuis la trame de référence ? » sans payer un appel explicite à difference() pour produire une image dont le seul but est d’être mesurée. Pour un script de détection de mouvement tournant en continu, l’économie finit par compter.