7.3. Hello BlazeFace¶
BlazeFace è una rete neurale per il rilevamento dei volti tratta dalla collezione MediaPipe di Google. Una singola chiamata di inferenza restituisce un rettangolo di delimitazione attorno a ogni volto rilevato insieme a sei punti caratteristici del viso – occhio destro, occhio sinistro, naso, bocca, orecchio destro, orecchio sinistro. Ogni OpenMV Cam fornita con supporto per le reti neurali porta il modello blazeface_front_128.tflite in flash, quindi eseguire un rilevatore di volti end-to-end richiede poche righe di Python.
7.3.1. Lo script completo¶
import csi
import ml
from ml.postprocessing.mediapipe import BlazeFace
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.VGA)
csi0.window((400, 400))
model = ml.Model("/rom/blazeface_front_128.tflite",
postprocess=BlazeFace(threshold=0.4))
while True:
img = csi0.snapshot()
for (x, y, w, h), score, keypoints in model.predict([img]):
img.draw_rectangle((x, y, w, h), color=(0, 255, 0))
ml.utils.draw_keypoints(img, keypoints, color=(255, 0, 0))
Questo è l’intero rilevatore di volti. Non c’è altro; lo script cattura un frame, lo passa al modello, percorre l’elenco restituito di rilevamenti e ridisegna nel frame il rettangolo di delimitazione di ciascun volto più i suoi sei punti caratteristici. L’anteprima dell’IDE mostra i riquadri e i punti caratteristici in tempo reale.
7.3.2. Cosa fa ogni riga¶
Le prime tre righe importano i moduli di cui lo script ha bisogno. csi è l’interfaccia del sensore della camera; ml è il modulo di machine learning di cui tratta il resto di questo capitolo; BlazeFace è il post-processore che trasforma i tensori di output grezzi di BlazeFace nell’elenco di bounding box e punti caratteristici su cui lo script itera.
Le cinque righe successive configurano il sensore. La camera viene reimpostata a uno stato noto, impostata sul colore RGB565, sulla risoluzione VGA, e poi finestrata a un quadrato di 400 per 400. La finestratura è importante: BlazeFace è stato addestrato su ritagli quadrati, e fornirgli un input quadrato allinea le proporzioni attese dalla rete con ciò che essa vede nel frame catturato.
La riga di caricamento del modello apre il file del modello:
model = ml.Model("/rom/blazeface_front_128.tflite",
postprocess=BlazeFace(threshold=0.4))
ml.Model legge il file al percorso indicato – /rom/ è un filesystem residente in flash trattato più avanti – e restituisce un oggetto modello su cui lo script eseguirà le inferenze. La parola chiave postprocess= registra il post-processore BlazeFace; senza di essa, predict restituirebbe i tensori di output grezzi della rete e l’applicazione dovrebbe decodificarli a mano. Con essa, predict restituisce direttamente il risultato decodificato. L’argomento threshold=0.4 sul post-processore imposta la confidenza minima che la rete deve riportare prima che un rilevamento venga mantenuto; valori più bassi catturano volti più deboli al costo di un maggior numero di falsi positivi.
Le restanti quattro righe sono il ciclo principale. Ogni passaggio cattura un frame e chiede al modello cosa vede:
img = csi0.snapshot()
for (x, y, w, h), score, keypoints in model.predict([img]):
img.draw_rectangle((x, y, w, h), color=(0, 255, 0))
ml.utils.draw_keypoints(img, keypoints, color=(255, 0, 0))
predict() prende un elenco di input (qui, una sola immagine catturata) e restituisce un elenco di tuple di rilevamento. Ogni tupla contiene il rettangolo di delimitazione (x, y, w, h), una confidenza score tra zero e uno, e un ndarray (6, 2) di coordinate dei punti caratteristici – l’occhio destro, l’occhio sinistro, il naso, la bocca, l’orecchio destro e l’orecchio sinistro, in quest’ordine. La chiamata di disegno usa draw_rectangle() – la stessa primitiva con cui terminava ogni rilevatore classico del capitolo sulle immagini – per tracciare il contorno del volto. ml.utils.draw_keypoints() è un piccolo helper delle utilità ml che contrassegna ogni keypoint con una croce nella sua posizione (x, y).
7.3.3. Cosa lo script non dice¶
Lo script consiste in sette righe eseguibili di lavoro di inferenza oltre agli import e alla configurazione del sensore, ma all’interno di quelle sette righe avviene una grande quantità di aritmetica. Il frame RGB565 catturato di 400 per 400 diventa un tensore quantizzato a 8 bit di 128 per 128 prima di raggiungere la rete; la rete esegue centinaia di operazioni a fronte di decine di migliaia di pesi; i tensori risultanti di punteggi di confidenza e di offset dei riquadri diventano un elenco ordinato di bounding box non sovrapposti con i punti caratteristici associati prima che predict restituisca il risultato. Ognuna di queste trasformazioni è qualcosa che l’applicazione può controllare se necessario, e diverse di esse devono essere regolate per qualsiasi modello non predefinito.
Le quattro sottosezioni successive aprono quelle trasformazioni. In ordine:
Il modulo ml – cosa espone
ml.Modeluna volta caricato un modello, e dove risiede effettivamente il file del modello sulla camera.La pipeline di inferenza – le quattro fasi di ogni chiamata
predict().I motori di inferenza – i percorsi CPU e NPU che eseguono l’aritmetica della rete.
Decodifica dell’output – i post-processori che convertono i tensori di output grezzi nei rilevamenti su cui questo script ha iterato.
Alla fine del capitolo il lettore sarà in grado di scrivere lo script equivalente per un modello non fornito con la camera, decodificare un tensore il cui post-processore non esiste ancora, e ragionare sul perché un particolare modello giri a 30 FPS su una camera e a 3 FPS su un’altra.