5.5. Régions et masques

Par défaut, chaque opération du module image touche chaque pixel de son image source. C’est le comportement le plus simple à décrire, et le bon lorsque le rôle de l’algorithme couvre véritablement toute la trame – une correction de couleur uniforme, un histogramme global, une passe d’encodage pour la transmission. Mais en pratique, la plupart des algorithmes veulent en regarder moins. Un suiveur de blobs surveillant un marqueur coloré s’intéresse à la partie de la scène où le marqueur peut apparaître, pas au mur derrière lui. Une passe de nettoyage morphologique n’est sûre que sur les pixels qu’une étape antérieure a marqués comme candidats. Un détecteur de visage peut ne s’exécuter qu’à l’intérieur de la boîte englobante qu’un détecteur plus grossier a déjà restreinte. Le module image prend en charge ce travail au moyen de deux mécanismes qui limitent la portée d’une opération à un sous-ensemble de pixels : les régions d’intérêt rectangulaires et les masques binaires. Ils se combinent librement, et presque toute méthode qui touche aux pixels accepte l’un ou l’autre – ou les deux – comme argument nommé.

5.5.1. Régions d’intérêt

Une région d’intérêt est un rectangle de pixels désigné par le quadruplet (x, y, w, h) présenté sur la page des coordonnées. Environ trente méthodes de la surface acceptent un argument nommé roi ; lorsqu’il est présent, l’opération ne s’exécute que sur les pixels à l’intérieur de ce rectangle et laisse le reste de l’image intact. Lorsque roi vaut None ou est omis, l’opération s’exécute sur l’image entière – comme si roi=(0, 0, width, height) avait été passé.

Dans le code, le mot-clé prend place aux côtés des autres arguments que l’opération accepte :

# Compute a histogram over a centred crop of the image.
h = img.get_histogram(roi=(64, 64, 128, 128))

Le premier avantage qu’apportent les ROI est le contrôle des faux positifs. Un suiveur de couleur qui ne regarde que la table ne se déclenchera jamais sur la chemise qui passe devant ; un détecteur de contours (arête) qui ne s’exécute qu’à l’intérieur de la zone de travail définie ne signalera jamais les contours du support de caméra lui-même. Réduire la zone de recherche à la partie de la scène qui intéresse réellement l’algorithme est l’amélioration la moins coûteuse qu’un pipeline puisse apporter à sa propre fiabilité.

Le second avantage qu’ils apportent est le pipeline du grossier au fin. Les objets résultats de détection – un blob, un rect, un apriltag, et ainsi de suite – exposent leur boîte englobante sous la forme du même quadruplet (x, y, w, h) que roi accepte. Ainsi, une première étape grossière peut renvoyer une boîte englobante, la boîte se glisse directement dans le roi de l’étape suivante, et la seconde étape s’exécute sur la zone plus restreinte. Chaque rétrécissement progressif accélère à la fois l’étape suivante et rend ses résultats plus fiables, car l’espace de recherche a déjà été filtré.

5.5.2. Masques binaires

Un rectangle est la forme appropriée lorsque la zone d’intérêt est alignée sur les axes. Lorsqu’elle ne l’est pas – une région courbe, une région non convexe, les pixels qu’une étape antérieure a classés comme « correspondances » – il faut indiquer à l’opération de limiter sa portée à un motif arbitraire de pixels. Le mécanisme pour cela est un masque binaire : une Image distincte, de mêmes dimensions que la source, utilisée comme interrupteur marche / arrêt par pixel. Un pixel non nul dans le masque signifie « inclure le pixel source correspondant » ; un pixel nul signifie « laisser le pixel source tranquille ».

Un masque est habituellement une image BINARY – le format à un bit par pixel qui existe exactement à cette fin – mais toute image à canal unique fonctionnera, car le consommateur traite toute valeur non nulle comme étant à l’état marche.

Les méthodes de filtrage, de seuillage et d’arithmétique acceptent un argument nommé mask. La forme est la même pour chacune : une image binaire allouée séparément, de mêmes dimensions que la source, passée en paramètre.

Les ROI et les masques se combinent. Passez les deux, et l’opération ne s’exécute que sur les pixels qui sont à l’intérieur de la ROI et à l’état marche dans le masque. Les deux mécanismes offrent au code applicatif des leviers indépendants – l’un pour la zone d’intérêt rectangulaire, l’autre pour le motif arbitraire à l’intérieur de celle-ci – sans qu’aucune des deux formes hérite des contraintes de l’autre.

Une petite grille représentant une image. Un rectangle en pointillés tracé sur la partie supérieure médiane de la grille étiquette la ROI : seuls les pixels à l'intérieur de ce rectangle sont pris en compte. À l'intérieur de la ROI, un ensemble de cellules remplies à peu près circulaire étiquette le masque : seules ces cellules remplies sont réellement modifiées. Les cellules restantes sont légèrement ombrées pour indiquer qu'elles restent intactes.

Une ROI confine une opération à un rectangle aligné sur les axes. Un masque la restreint davantage à un motif arbitraire de pixels. Les deux se combinent : seuls les pixels à l’intérieur de la ROI et à l’état marche dans le masque sont modifiés.

5.5.3. Construire des masques

Trois méthodes de Image construisent en place des géométries de masque courantes en mettant à zéro les pixels à l’extérieur de la région choisie :

Chacune prend (x, y, w, h) (pour le rectangle et l’ellipse) ou (x, y, radius) (pour le cercle). Appeler l’une d’elles sans arguments centre la géométrie et la dimensionne pour remplir l’image, ce qui est la forme à laquelle une application a recours lorsque le but est un simple ovale ou cercle pleine image qui ne masque que les coins.

mask = image.Image(img.width(), img.height(), image.BINARY)
mask.clear()              # start from all zeros
mask.mask_ellipse()       # centred, full-size oval

Les masques intéressants proviennent rarement des seules méthodes mask_*. Ils proviennent des étapes antérieures du pipeline : une passe de seuillage produit une image binaire dont les pixels non nuls marquent les correspondances, exactement la bonne forme à fournir à l’argument mask= de l’étape suivante. Une passe de nettoyage morphologique affine ce masque sans en changer la forme. Tout ce qui finit par être une image à canal unique est lui-même un masque valide.

5.5.4. Comment les opérations modifient l’image

Un motif visible dans chaque extrait de code des dernières pages – l’opération renvoyant la même img pour le chaînage – mérite d’être mis en évidence explicitement afin de ne pas avoir à être réénoncé chaque fois qu’une nouvelle méthode est présentée. Trois familles de méthodes apparaissent sur la surface Image, chacune traitant l’image source différemment :

  • Les méthodes opérantes modifient les pixels de la source en place et renvoient la même image pour le chaînage. Les familles du dessin, de l’arithmétique, du seuillage et du filtrage se comportent toutes ainsi. img.gaussian(1) floute img et renvoie la même img ; réaffecter – img = img.gaussian(1) – est inoffensif mais inutile.

  • Les méthodes de conversion opèrent en place par défaut de la même manière que les méthodes opérantes, mais elles acceptent copy=True et copy_to_fb=True pour allouer une image résultat distincte lorsque la source doit être préservée. Les conversions de format et les copies géométriques sont les principaux membres de cette famille.

  • Les méthodes d’inspection lisent les pixels et renvoient un objet résultat – une liste de caractéristiques détectées, un histogramme, un ensemble de statistiques – sans modifier du tout l’image source.

Cette trichotomie est cohérente sur toute la surface. Savoir à quelle famille appartient une méthode indique à l’application ce qu’elle peut attendre d’un appel : si les pixels de la source resteront intacts, si une image résultat distincte sera allouée, et si la valeur de retour est la source elle-même ou autre chose.