7.3. Hello BlazeFace

BlazeFace är ett neuronnät för ansiktsdetektering från Googles MediaPipe-samling. Ett enda inferensanrop returnerar en begränsningsrektangel runt varje detekterat ansikte tillsammans med sex ansiktslandmärken – höger öga, vänster öga, näsa, mun, höger öra, vänster öra. Varje OpenMV Cam som levereras med stöd för neuronnät bär blazeface_front_128.tflite-modellen på flashminnet, så att köra en komplett ansiktsdetektor tar bara några rader Python.

7.3.1. Det fullständiga skriptet

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))

Det är hela ansiktsdetektorn. Det finns inget mer i den; skriptet fångar en bildruta, lämnar den till modellen, går igenom den returnerade listan med detekteringar och ritar varje ansiktes begränsningsrektangel plus dess sex landmärken tillbaka in i bildrutan. IDE-förhandsvisningen visar rutorna och landmärkena i realtid.

7.3.2. Vad varje rad gör

De första tre raderna importerar de moduler som skriptet behöver. csi är gränssnittet till kamerasensorn; ml är maskininlärningsmodulen som resten av detta kapitel handlar om; BlazeFace är efterbehandlaren som omvandlar BlazeFace:s råa ut-tensorer till den begränsningsruta och landmärkeslista som skriptet itererar över.

De följande fem raderna konfigurerar sensorn. Kameran återställs till ett känt tillstånd, ställs in på RGB565-färg, ställs in på VGA-upplösning och fönstras sedan till en kvadrat på 400 gånger 400. Fönstret spelar roll: BlazeFace tränades på kvadratiska beskärningar, och att ge den en kvadratisk indata anpassar nätverkets förväntade bildförhållande till vad det ser i den fångade bildrutan.

Raden som laddar modellen öppnar modellfilen:

model = ml.Model("/rom/blazeface_front_128.tflite",
                 postprocess=BlazeFace(threshold=0.4))

ml.Model läser filen på den angivna sökvägen – /rom/ är ett flashresident filsystem som behandlas senare – och returnerar ett modellobjekt som skriptet kommer att köra inferenser mot. Nyckelordet postprocess= registrerar BlazeFace-efterbehandlaren; utan det skulle predict returnera nätverkets råa ut-tensorer och tillämpningen skulle behöva avkoda dem för hand. Med det returnerar predict det avkodade resultatet direkt. Argumentet threshold=0.4 på efterbehandlaren anger den minsta konfidens som nätverket måste rapportera innan en detektering behålls; lägre värden fångar svagare ansikten på bekostnad av fler falska positiva.

De återstående fyra raderna är huvudslingan. Varje varv genom den fångar en bildruta och frågar modellen vad den ser:

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() tar en lista med indata (här en fångad bild) och returnerar en lista med detekteringstupler. Varje tupel innehåller begränsningsrektangeln (x, y, w, h), en konfidens score mellan noll och ett, och en (6, 2)-ndarray med landmärkeskoordinater – höger öga, vänster öga, näsa, mun, höger öra och vänster öra i den ordningen. Ritanropet använder draw_rectangle() – samma primitiv som varje klassisk detektor i bildkapitlet slutade med – för att rama in ansiktet. ml.utils.draw_keypoints() är en liten hjälpfunktion från ml-verktygen som markerar varje nyckelpunkt med ett kors vid dess (x, y)-position.

7.3.3. Vad skriptet inte säger

Skriptet är sju körbara rader inferensarbete bortom importerna och sensoruppsättningen, men en hel del aritmetik sker inuti dessa sju rader. Den fångade RGB565-bildrutan på 400 gånger 400 blir en kvantiserad 8-bitars tensor på 128 gånger 128 innan den når nätverket; nätverket kör hundratals operationer mot tiotusentals vikter; de resulterande tensorerna av konfidenspoäng och rutförskjutningar blir en rangordnad lista med icke-överlappande begränsningsrutor med tillhörande landmärken innan predict returnerar. Var och en av dessa transformationer är något som tillämpningen kan styra om den behöver, och flera av dem måste justeras för varje modell som inte är standard.

De följande fyra delavsnitten går igenom dessa transformationer öppet. I ordning:

  • The ml module – vad ml.Model exponerar när en modell väl är laddad, och var modellfilen faktiskt lever på kameran.

  • The inference pipeline – de fyra stegen i varje predict()-anrop.

  • Inference engines – CPU- och NPU-vägarna som kör nätverkets aritmetik.

  • Decoding the output – efterbehandlarna som omvandlar råa ut-tensorer till de detekteringar som detta skript itererade över.

I slutet av kapitlet kan läsaren skriva motsvarande skript för en modell som inte levererades med kameran, avkoda en tensor vars efterbehandlare ännu inte existerar, och resonera kring varför en viss modell körs på 30 FPS på en kamera och 3 FPS på en annan.