5.25. Recherche de blobs

Le seuillage a transformé la trame capturée en un masque binaire : chaque pixel réussit le test de seuil ou non. Cela répond à la question de savoir quelles couleurs auxquelles l’application s’intéresse apparaissent dans la scène, mais pas – le masque n’est qu’une mer de 1 et de 0. L’étape suivante est la détection de blobs : parcourir le masque, trouver les régions contiguës de pixels réussis et renvoyer chacune d’elles sous forme d’objet doté d’une position, d’une taille, d’une orientation et des autres propriétés sur lesquelles une application peut agir.

find_blobs() est la méthode de prédilection pour cette étape, et c’est le point d’entrée le plus courant dans l’univers des objets de résultat du module image. Suivre une balle colorée, suivre une ligne peinte au sol, compter le nombre de points lumineux que voit un capteur thermique, déterminer si une LED bleue est allumée ou éteinte – le même appel couvre tous ces cas. Les entrées changent (les seuils, la région explorée, les filtres appliqués au résultat), mais le schéma d’appel reste le même.

5.25.1. L’appel de base

find_blobs prend une liste de seuils et renvoie une liste d’objets de résultat blob :

thresholds = [(30, 100, 15, 127, 15, 127)]  # LAB threshold for red
blobs = img.find_blobs(thresholds)

for b in blobs:
    img.draw_rectangle(b.rect, color=(255, 0, 0))
    img.draw_cross(b.cx, b.cy, color=(255, 0, 0))

Chaque tuple de seuil a la même forme que les seuils passés à binary() – six entrées (l_lo, l_hi, a_lo, a_hi, b_lo, b_hi) pour une image RGB565 (les bornes sont en LAB), deux entrées (lo, hi) pour une image en niveaux de gris. Jusqu’à 32 seuils peuvent être fournis en un seul appel, ce qui rend find_blobs() si flexible : des balises rouges, vertes et bleues peuvent être suivies simultanément, chacune contribuant ses propres blobs à la liste renvoyée, et la propriété code de chaque blob identifie le seuil auquel il correspond.

Les appels draw_rectangle() et draw_cross() ci-dessus annotent la trame capturée pour l’aperçu de l’IDE. Le résultat blob comporte déjà b.rect (la boîte englobante sous forme de quadruplet) ainsi que b.cx / b.cy (le centroïde entier), de sorte que redessiner la détection dans la trame ne demande que deux appels de méthode.

5.25.2. Ce que contient le résultat

Chaque Blob est un tuple d’attributs qui regroupe tout ce que le détecteur a mesuré sur la région. Les propriétés se répartissent en quatre groupes.

Le groupe boîte englobante et centroïdex, y, w, h, rect, cx, cy, cxf, cyf – décrit la position du blob. rect est le quadruplet (x, y, w, h) attendu par les méthodes de dessin ; cx et cy sont le centroïde en coordonnées de pixels entières ; cxf et cyf sont le centroïde en coordonnées flottantes sous-pixel, utiles lorsqu’un étalonnage en amont s’intéresse aux positions fractionnaires.

Les descripteurs de formepixels, area, density, perimeter, roundness, elongation, compactness, rotation – décrivent l’aspect du blob. pixels est le nombre de pixels réussis ; area est l’aire de la boîte englobante alignée sur les axes (w * h) ; density est le rapport des deux, qui approche 1.0 pour un rectangle plein et chute vers 0.0 pour un fin trait diagonal. roundness et compactness évaluent toutes deux à quel point le blob est rond, selon des points de vue géométriques différents (roundness à partir des moments du second ordre, compactness à partir du rapport périmètre/aire) ; elongation vaut 1.0 - roundness par commodité. rotation est l’orientation du grand axe en radians, qui est la plus précise sur les blobs allongés et devient bruitée sur ceux qui sont presque ronds (un axe ambigu n’a pas de direction bien définie).

Les métadonnées de seuil et de fusioncode, count – identifient quel seuil a correspondu et combien de blobs sources ont été fusionnés dans celui qui est renvoyé. code est un masque binaire de 32 bits avec un bit activé par seuil correspondant (un seuil unique donne code == 1 ; un blob multicolore fusionné peut avoir plusieurs bits activés) ; count vaut 1 sauf si merge=True a combiné plusieurs détections en une seule.

Le groupe coinscorners, min_corners – fournit la géométrie pivotée du blob. corners est le quadruplet des extrêmes (x, y) extraits du contour du blob, triés dans le sens horaire à partir du coin supérieur gauche ; min_corners est le quadruplet des coins du rectangle pivoté d’aire minimale qui englobe le blob. Le rectangle d’aire minimale est l’ajustement serré ; le rect aligné sur les axes est l’ajustement lâche aligné sur la grille de pixels. Les deux sont utiles selon qu’une étape en aval a besoin d’une boîte orientée ou d’une boîte simple.

Une détection de blob illustrée sur un masque de seuil binaire. Le panneau de gauche montre un masque ovale incliné de pixels réussis. Le panneau de droite montre le même masque annoté avec la boîte englobante alignée sur les axes dessinée autour de lui, le centroïde marqué d'une croix au centre, un rectangle pivoté d'aire minimale en pointillés épousant l'ovale selon son angle réel, et la ligne du grand axe passant par le centroïde et pointant dans la direction de la longueur de l'ovale.

Un blob porte la boîte englobante alignée sur les axes (rect, x, y, w, h), le centroïde (cx, cy ou en sous-pixel cxf, cyf), le rectangle pivoté d’aire minimale (min_corners plus rotation), et les lignes optionnelles du grand / petit axe calculées par les fonctions utilitaires de niveau module ci-dessous.

5.25.4. Fusionner les blobs qui se chevauchent

merge=True post-traite la liste de résultats pour combiner les blobs dont les rectangles englobants se chevauchent. L’usage naturel est la détection d’une cible dont la caméra perçoit la couleur comme plusieurs régions seuillées à cause de reflets spéculaires, de lignes d’ombre ou d’un éclairage inégal sur l’objet : une seule balle rouge peut revenir sous forme de trois ou quatre petits blobs rouges qui, pris ensemble, tracent la balle. Avec merge=True, les trois blobs deviennent un seul grand blob, le rect couvre l’union, le code est le OU binaire des codes des blobs fusionnés (de sorte qu’une fusion multicolore identifie quelles couleurs ont contribué), et count indique combien de blobs sources ont été combinés.

margin agrandit ou réduit les rectangles englobants avant le test de chevauchement. Avec margin=2, les blobs dont les rectangles englobants sont à moins de 2 pixels l’un de l’autre fusionnent quand même ; avec margin=-2, seuls les blobs dont les rectangles englobants se chevauchent d’au moins 2 pixels fusionnent. Le réglage naturel : une marge positive pour traiter les blobs que le seuil a fragmentés en morceaux adjacents ; une marge négative pour garder séparés des objets distincts mais étroitement groupés.

merge_cb s’exécute sur chaque paire candidate avant que la fusion ne se produise. La fonction de rappel reçoit les deux blobs et renvoie True pour autoriser la fusion ou False pour l’empêcher. C’est l’outil idéal pour effectuer des vérifications croisées sur les fusions que la règle géométrique manque – par exemple, refuser de fusionner deux blobs dont les angles de rotation diffèrent de plus d’un seuil, ou refuser de fusionner un petit blob dans un blob bien plus grand si le petit n’est que du moucheté.

5.25.5. Histogrammes de projection

x_hist_bins_max et y_hist_bins_max attachent des histogrammes de projection optionnels à chaque blob. Un histogramme de projection est le nombre de pixels réussis le long d’un axe : l’histogramme de l’axe X totalise les pixels réussis par colonne à l’intérieur de la boîte englobante du blob, et l’histogramme de l’axe Y totalise par ligne. Les deux valent zéro par défaut – les histogrammes ne sont pas calculés tant qu’une valeur max non nulle n’est pas fournie, car ils ajouteraient sinon du travail à chaque détection.

Lorsqu’ils sont calculés, les histogrammes fournissent un signal 1-D peu coûteux sur lequel une application peut effectuer une analyse plus poussée : détecter la position d’une bande verticale à l’intérieur du blob, trouver le point de rupture d’une cible bicolore, compter le nombre de trous le long du grand axe. Ils sont renseignés sous forme de propriétés x_hist_bins et y_hist_bins sur chaque Blob.

5.25.6. Aides géométriques supplémentaires

Quelques mesures géométriques supplémentaires existent sous forme de fonctions de niveau module qui prennent un blob et renvoient la mesure demandée :

  • image.get_solidity() renvoie la solidité du blob – le nombre de pixels divisé par l’aire de l’enveloppe convexe. Une région pleine est proche de 1.0 ; un blob présentant des concavités (un fer à cheval, une main aux doigts écartés) descend bien en dessous.

  • image.get_convexity() renvoie la convexité – le périmètre de l’enveloppe convexe divisé par le périmètre du blob. Un blob parfaitement convexe vaut 1.0 ; les blobs déchiquetés ou entaillés sont plus bas.

  • image.get_major_axis_line() et image.get_minor_axis_line() renvoient des objets Line le long des grand et petit axes du blob, dérivés du rectangle pivoté d’aire minimale.

  • image.get_enclosing_circle() renvoie un Circle qui englobe le blob – utile lorsqu’une étape en aval veut un cercle à dessiner ou à comparer.

  • image.get_enclosed_ellipse() renvoie le quintuplet (cx, cy, rx, ry, rotation) pour une ellipse inscrite dans le rectangle d’aire minimale du blob. Les valeurs alimentent directement draw_ellipse().

5.25.7. Apprentissage automatique d’un seuil

Un détecteur de blobs ne vaut que les seuils avec lesquels il est exécuté, et le travail consistant à trouver le bon seuil pour une couleur cible constitue un problème à part entière. Deux schémas courants réduisent ce travail.

Le premier est la sélection interactive dans l’IDE : capturer une trame, tracer un rectangle autour d’un exemple de la couleur cible et laisser l’éditeur de seuil de l’IDE (threshold editor) indiquer les bornes LAB qu’il observe. Ces bornes sont déposées dans le script comme seuils de find_blobs() et le détecteur est prêt.

Le second est l’auto-apprentissage programmatique : une routine d’étalonnage exécutée sur la caméra capture une trame, prend un histogramme d’une zone connue où se trouve la cible (get_histogram() avec roi=), et lit la plage de valeurs de la zone à partir de l’histogramme avec get_percentile(). Le 5e centile fixe la borne basse de chaque canal et le 95e sa borne haute, en ignorant les pixels aberrants isolés aux deux extrémités. Sur une image RGB565, un seul appel de centile rapporte les trois canaux LAB à la fois, de sorte que les deux appels produisent les six nombres attendus par find_blobs() :

h = img.get_histogram(roi=patch)
lo = h.get_percentile(0.05)
hi = h.get_percentile(0.95)
threshold = (lo.l_value, hi.l_value,
             lo.a_value, hi.a_value,
             lo.b_value, hi.b_value)