7.3. Hello BlazeFace¶
BlazeFace ist ein neuronales Netz zur Gesichtserkennung aus Googles MediaPipe-Sammlung. Ein einzelner Inferenzaufruf liefert ein Begrenzungsrechteck um jedes erkannte Gesicht zusammen mit sechs Gesichts-Landmarken – rechtes Auge, linkes Auge, Nase, Mund, rechtes Ohr, linkes Ohr. Jede OpenMV Cam, die mit Unterstützung für neuronale Netze ausgeliefert wird, trägt das Modell blazeface_front_128.tflite im Flash, sodass die Ausführung eines durchgängigen Gesichtsdetektors nur wenige Zeilen Python benötigt.
7.3.1. Das vollständige Skript¶
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))
Das ist der gesamte Gesichtsdetektor. Mehr steckt nicht dahinter; das Skript erfasst ein Einzelbild, übergibt es dem Modell, durchläuft die zurückgegebene Liste von Erkennungen und zeichnet das Begrenzungsrechteck jedes Gesichts samt seiner sechs Landmarken zurück in das Einzelbild. Die Vorschau in der IDE zeigt die Rahmen und Landmarken in Echtzeit.
7.3.2. Was jede Zeile bewirkt¶
Die ersten drei Zeilen importieren die Module, die das Skript benötigt. csi ist die Schnittstelle zum Kamerasensor; ml ist das Modul für maschinelles Lernen, um das es im Rest dieses Kapitels geht; BlazeFace ist der Post-Processor, der die rohen Ausgangstensoren von BlazeFace in die Liste aus Begrenzungsrahmen und Landmarken umwandelt, die das Skript durchläuft.
Die nächsten fünf Zeilen konfigurieren den Sensor. Die Kamera wird auf einen bekannten Zustand zurückgesetzt, auf RGB565-Farbe gesetzt, auf VGA-Auflösung gesetzt und dann auf ein 400-mal-400-Quadrat gefenstert. Das Fenstern ist wichtig: BlazeFace wurde auf quadratischen Ausschnitten trainiert, und eine quadratische Eingabe bringt das vom Netz erwartete Seitenverhältnis mit dem in Übereinstimmung, was es im erfassten Einzelbild sieht.
Die Zeile zum Laden des Modells öffnet die Modelldatei:
model = ml.Model("/rom/blazeface_front_128.tflite",
postprocess=BlazeFace(threshold=0.4))
ml.Model liest die Datei unter dem angegebenen Pfad – /rom/ ist ein flash-residentes Dateisystem, das später behandelt wird – und liefert ein Modellobjekt zurück, gegen das das Skript Inferenzen ausführt. Das Schlüsselwort postprocess= registriert den BlazeFace-Post-Processor; ohne es würde predict die rohen Ausgangstensoren des Netzes zurückgeben und die Anwendung müsste sie von Hand decodieren. Mit ihm gibt predict das decodierte Ergebnis direkt zurück. Das Argument threshold=0.4 am Post-Processor legt die minimale Konfidenz fest, die das Netz melden muss, bevor eine Erkennung beibehalten wird; niedrigere Werte erfassen schwächere Gesichter auf Kosten von mehr Fehldetektionen.
Die verbleibenden vier Zeilen sind die Hauptschleife. Jeder Durchlauf erfasst ein Einzelbild und fragt das Modell, was es sieht:
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() nimmt eine Liste von Eingaben entgegen (hier ein erfasstes Bild) und gibt eine Liste von Erkennungs-Tupeln zurück. Jedes Tupel enthält das Begrenzungsrechteck (x, y, w, h), einen Konfidenzwert score zwischen null und eins und ein (6, 2)-ndarray von Landmarken-Koordinaten – das rechte Auge, das linke Auge, die Nase, den Mund, das rechte Ohr und das linke Ohr in dieser Reihenfolge. Der Zeichenaufruf verwendet draw_rectangle() – dasselbe Primitiv, mit dem jeder klassische Detektor im Bildkapitel endete –, um das Gesicht zu umranden. ml.utils.draw_keypoints() ist ein kleiner Helfer aus den ml-Utilities, der jeden Schlüsselpunkt mit einem Kreuz an seiner (x, y)-Position markiert.
7.3.3. Was das Skript nicht sagt¶
Das Skript umfasst über die Importe und die Sensoreinrichtung hinaus sieben lauffähige Zeilen Inferenzarbeit, aber innerhalb dieser sieben Zeilen geschieht eine ganze Menge Arithmetik. Das erfasste 400-mal-400-RGB565-Einzelbild wird zu einem 128-mal-128 quantisierten 8-Bit-Tensor, bevor es das Netz erreicht; das Netz führt Hunderte von Operationen gegen Zehntausende von Gewichten aus; die resultierenden Tensoren aus Konfidenzwerten und Box-Offsets werden zu einer geordneten Liste nicht überlappender Begrenzungsrahmen mit angehängten Landmarken, bevor predict zurückkehrt. Jede dieser Transformationen ist etwas, das die Anwendung steuern kann, wenn sie es benötigt, und mehrere davon müssen für jedes Nicht-Standard-Modell abgestimmt werden.
Die nächsten vier Unterabschnitte öffnen diese Transformationen Schritt für Schritt. Der Reihe nach:
Das ml-Modul – was
ml.Modelbereitstellt, sobald ein Modell geladen ist, und wo die Modelldatei auf der Kamera tatsächlich liegt.Die Inferenz-Pipeline – die vier Stufen jedes
predict()-Aufrufs.Inferenz-Engines – die CPU- und NPU-Pfade, die die Arithmetik des Netzes ausführen.
Decodieren der Ausgabe – die Post-Processoren, die rohe Ausgangstensoren in die Erkennungen umwandeln, die dieses Skript durchlaufen hat.
Am Ende des Kapitels kann der Leser das entsprechende Skript für ein Modell schreiben, das nicht mit der Kamera ausgeliefert wurde, einen Tensor decodieren, dessen Post-Processor noch nicht existiert, und nachvollziehen, warum ein bestimmtes Modell auf einer Kamera mit 30 FPS und auf einer anderen mit 3 FPS läuft.