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 où – 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ïde – x, 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 forme – pixels, 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 fusion – code, 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 coins – corners, 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.
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.3. Filtrer la recherche¶
Une trame capturée contient généralement des pixels qui correspondent au seuil pour des raisons autres que l’objet auquel l’application s’intéresse : reflets spéculaires, objets d’arrière-plan lointains, pixels de bruit d’image qui tombent par hasard dans la plage LAB. Les arguments nommés de find_blobs() constituent la première ligne de défense.
roi restreint la recherche à une région de la trame, comme le fait toute autre méthode du module image. Une application qui sait que l’objet ne peut apparaître que dans la moitié inférieure du champ de vision passe roi=(0, h//2, w, h//2) et ignore tout ce qui se trouve au-dessus ; le temps économisé est réinvesti dans la cadence d’images.
area_threshold et pixels_threshold filtrent tous deux les blobs trop petits pour qu’on s’en soucie. area_threshold élimine les blobs dont la boîte englobante a une aire inférieure à ce nombre de pixels (utile pour filtrer le bruit dispersé) ; pixels_threshold élimine les blobs qui ont moins que ce nombre de pixels réussis (utile pour filtrer les blobs grands mais clairsemés, comme un motif pointillé seuillé dont un ou deux pixels correspondent çà et là). Les deux valeurs par défaut sont 10 ; en les portant à des centaines pour une cible de premier plan de quelques centimètres de large, on élimine la moindre tache de petit bruit.
x_stride et y_stride définissent le pas en pixels que le scanner effectue pendant qu’il cherche un blob à tracer. Le pas n’est pas la résolution du tracé – le tracé suit toujours la frontière réelle du blob au pixel près – mais il contrôle la rapidité avec laquelle le balayage trouve un pixel de départ. Lorsque l’on sait que les blobs sont grands (une cible colorée de la taille d’un poing à une trentaine de centimètres de la caméra, faisant facilement une centaine de pixels de large), x_stride=4, y_stride=4 divise le temps de balayage par seize sans perte pratique de détection. Lorsque les blobs sont petits (une balise LED lointaine, de quelques pixels de large), les pas doivent rester à 1 pour éviter de les enjamber complètement. invert inverse le test de seuil : la correspondance devient non-correspondance et la routine renvoie les blobs de pixels échoués à la place.
threshold_cb est une fonction de rappel Python invoquée sur chaque blob après le seuillage mais avant la construction de la liste de résultats finale. La fonction de rappel reçoit le blob et renvoie True pour le conserver ou False pour le rejeter. C’est l’endroit où appliquer des filtres Python arbitraires sur des propriétés que les arguments nommés n’exposent pas directement – une densité minimale, une plage de rotation spécifique, une combinaison personnalisée de bits de code après fusion. Les arguments nommés sont des filtres en code natif et s’exécutent rapidement ; la fonction de rappel s’exécute en Python et est plus lente mais illimitée dans ce qu’elle peut exprimer.
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 de1.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 vaut1.0; les blobs déchiquetés ou entaillés sont plus bas.image.get_major_axis_line()etimage.get_minor_axis_line()renvoient des objetsLinele long des grand et petit axes du blob, dérivés du rectangle pivoté d’aire minimale.image.get_enclosing_circle()renvoie unCirclequi 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 directementdraw_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)