5.5. Regioni e maschere¶
Per impostazione predefinita, ogni operazione del modulo image tocca tutti i pixel della sua immagine sorgente. Questo è il comportamento più semplice da descrivere ed è quello giusto quando il compito dell’algoritmo riguarda davvero l’intero frame – una correzione uniforme del colore, un istogramma globale, una fase di codifica per la trasmissione. Ma nella pratica la maggior parte degli algoritmi vuole guardare meno di così. Un tracciatore di blob che osserva un marcatore colorato si interessa alla parte della scena in cui il marcatore può apparire, non alla parete dietro di esso. Una fase di pulizia morfologica è sicura solo sui pixel che uno stadio precedente ha contrassegnato come candidati. Un rilevatore di volti potrebbe essere eseguito solo all’interno del bounding box che un rilevatore più grossolano ha già ristretto. Il modulo image supporta questo lavoro attraverso due meccanismi che limitano un’operazione a un sottoinsieme di pixel: le regioni di interesse rettangolari e le maschere binarie. Si combinano liberamente, e quasi ogni metodo che tocca i pixel accetta l’uno o l’altro – o entrambi – come argomento keyword.
5.5.1. Regioni di interesse¶
Una regione di interesse è un rettangolo di pixel indicato dalla quadrupla (x, y, w, h) introdotta nella pagina delle coordinate. Circa trenta metodi della superficie accettano un argomento keyword roi; quando è presente, l’operazione viene eseguita solo sui pixel all’interno di quel rettangolo e lascia intatto il resto dell’immagine. Quando roi è None o viene omesso, l’operazione viene eseguita sull’intera immagine – come se fosse stato passato roi=(0, 0, width, height).
Nel codice la keyword si affianca a qualsiasi altro argomento accetti l’operazione:
# Compute a histogram over a centred crop of the image.
h = img.get_histogram(roi=(64, 64, 128, 128))
La prima cosa che le ROI offrono è il controllo dei falsi positivi. Un tracciatore di colore che guarda solo il tavolo non si attiverà mai sulla maglietta che gli passa davanti; un rilevatore di bordi che viene eseguito solo all’interno dell’area di lavoro definita non riporterà mai i bordi del supporto della camera stessa. Ridurre l’area di ricerca alla parte della scena a cui l’algoritmo è effettivamente interessato è il miglioramento più economico che una pipeline può apportare alla propria affidabilità.
La seconda cosa che offrono è la pipeline da grossolano a fine. Gli oggetti risultato del rilevamento – un blob, un rect, un apriltag e così via – espongono i propri bounding box come la stessa quadrupla (x, y, w, h) che roi accetta. Così un primo stadio grossolano può restituire un bounding box, il box viene inserito direttamente nella roi dello stadio successivo, e il secondo stadio viene eseguito sull’area più ristretta. Ogni progressivo restringimento sia accelera lo stadio successivo sia ne rende i risultati più affidabili, perché lo spazio di ricerca è già stato filtrato.
5.5.2. Maschere binarie¶
Un rettangolo è la forma giusta quando l’area di interesse è allineata agli assi. Quando non lo è – una regione curva, una non convessa, i pixel che uno stadio precedente ha classificato come «corrispondenze» – all’operazione deve essere indicato di limitarsi invece a un pattern arbitrario di pixel. Il meccanismo per questo è una maschera binaria: una Image separata, delle stesse dimensioni della sorgente, usata come interruttore on/off per ogni pixel. Un pixel diverso da zero nella maschera dice «includi il pixel sorgente corrispondente»; un pixel a zero dice «lascia inalterato il pixel sorgente».
Una maschera è solitamente un’immagine BINARY – il formato a un bit per pixel che esiste esattamente per questo scopo – ma funziona qualsiasi immagine a canale singolo, perché il consumatore tratta come on qualsiasi valore diverso da zero.
I metodi di filtraggio, sogliatura e aritmetici accettano un argomento keyword mask. La forma è la stessa per ciascuno: un’immagine binaria allocata separatamente, delle stesse dimensioni della sorgente, passata come parametro.
Le ROI e le maschere si combinano. Passandole entrambe, l’operazione viene eseguita solo sui pixel che sono all’interno della ROI e attivi nella maschera. I due meccanismi offrono al codice applicativo leve indipendenti – una per l’area di interesse rettangolare, una per il pattern arbitrario al suo interno – senza che nessuna delle due forme erediti vincoli dall’altra.
Una ROI confina un’operazione a un rettangolo allineato agli assi. Una maschera la restringe ulteriormente a un pattern arbitrario di pixel. Le due si combinano: vengono modificati solo i pixel all’interno della ROI e attivi nella maschera.¶
5.5.3. Costruire le maschere¶
Tre metodi di Image costruiscono geometrie di maschera comuni in place azzerando i pixel al di fuori della regione scelta:
mask_rectangle()mantiene un rettangolo.mask_circle()mantiene un cerchio.mask_ellipse()mantiene un’ellisse.
Ciascuno prende (x, y, w, h) (per il rettangolo e l’ellisse) oppure (x, y, radius) (per il cerchio). Chiamando uno qualsiasi di essi senza argomenti la geometria viene centrata e dimensionata per riempire l’immagine, che è la forma a cui un’applicazione ricorre quando l’obiettivo è un semplice ovale o cerchio a tutta immagine che non nasconde nulla se non gli angoli.
mask = image.Image(img.width(), img.height(), image.BINARY)
mask.clear() # start from all zeros
mask.mask_ellipse() # centred, full-size oval
Le maschere interessanti raramente derivano dai soli metodi mask_*. Provengono dagli stadi precedenti della pipeline: una fase di sogliatura produce un’immagine binaria i cui pixel diversi da zero contrassegnano le corrispondenze, esattamente la forma giusta da fornire all’argomento mask= dello stadio successivo. Una fase di pulizia morfologica raffina quella maschera senza cambiarne la forma. Tutto ciò che si risolve in un’immagine a canale singolo è di per sé una maschera valida.
5.5.4. Come le operazioni modificano l’immagine¶
Un pattern visibile in ogni frammento di codice delle ultime pagine – l’operazione che restituisce la stessa img per il concatenamento – vale la pena di essere esplicitato in modo che non debba essere ripetuto ogni volta che viene introdotto un nuovo metodo. Tre famiglie di metodi compaiono sulla superficie di Image, ciascuna delle quali tratta l’immagine sorgente in modo diverso:
I metodi operativi modificano i pixel della sorgente in place e restituiscono la stessa immagine per il concatenamento. Le famiglie di disegno, aritmetica, soglia e filtro si comportano tutte in questo modo.
img.gaussian(1)sfocaimge restituisce la stessaimg; riassegnare –img = img.gaussian(1)– è innocuo ma superfluo.I metodi di conversione operano in place per impostazione predefinita allo stesso modo dei metodi operativi, ma accettano
copy=Trueecopy_to_fb=Trueper allocare un’immagine risultato separata quando la sorgente deve essere preservata. Le conversioni di formato e le copie geometriche sono i membri principali di questa famiglia.I metodi di ispezione leggono i pixel e restituiscono un oggetto risultato – un elenco di caratteristiche rilevate, un istogramma, un insieme di statistiche – senza modificare affatto l’immagine sorgente.
Questa tricotomia è coerente su tutta la superficie. Sapere a quale famiglia appartiene un metodo dice all’applicazione cosa aspettarsi da una chiamata: se i pixel della sorgente sopravvivranno intatti, se verrà allocata un’immagine risultato separata, e se il valore restituito è la sorgente stessa o qualcos’altro.