7.3. Hello BlazeFace¶
BlazeFace は、Google の MediaPipe コレクションに含まれる顔検出ニューラルネットワークです。1 回の推論呼び出しで、検出された各顔を囲むバウンディング矩形と、6 つの顔のランドマーク(右目、左目、鼻、口、右耳、左耳)が返されます。ニューラルネットワークのサポート付きで出荷されるすべての OpenMV Cam は、フラッシュ上に blazeface_front_128.tflite モデルを搭載しているため、エンドツーエンドの顔検出器を実行するのに必要な Python コードはわずか数行です。
7.3.1. 完全なスクリプト¶
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))
これが顔検出器のすべてです。これ以外には何もありません。スクリプトはフレームをキャプチャし、それをモデルに渡し、返された検出のリストをたどり、各顔のバウンディング矩形と 6 つのランドマークをフレームに描き戻します。IDE のプレビューには、ボックスとランドマークがリアルタイムで表示されます。
7.3.2. 各行が行っていること¶
最初の 3 行は、スクリプトが必要とするモジュールをインポートします。csi はカメラセンサーのインターフェイスです。ml は本章の残りで扱う機械学習モジュールです。BlazeFace は、BlazeFace の生の出力テンソルを、スクリプトが反復処理するバウンディングボックスとランドマークのリストに変換するポストプロセッサです。
次の 5 行はセンサーを設定します。カメラは既知の状態にリセットされ、RGB565 カラーに設定され、VGA 解像度に設定され、その後 400×400 の正方形に ウィンドウ(windowed) されます。このウィンドウ処理が重要です。BlazeFace は正方形のクロップで学習されているため、正方形の入力を与えることで、ネットワークが期待するアスペクト比と、キャプチャされたフレームで見えるものとが一致します。
モデル読み込みの行は、モデルファイルを開きます。
model = ml.Model("/rom/blazeface_front_128.tflite",
postprocess=BlazeFace(threshold=0.4))
ml.Model は指定されたパスのファイルを読み込み(/rom/ は後で説明するフラッシュ常駐のファイルシステムです)、スクリプトが推論を実行するモデルオブジェクトを返します。postprocess= キーワードは BlazeFace ポストプロセッサを登録します。これがなければ、predict はネットワークの生の出力テンソルを返し、アプリケーションがそれを手作業でデコードしなければなりません。これがあれば、predict はデコード済みの結果を直接返します。ポストプロセッサの threshold=0.4 引数は、検出が保持される前にネットワークが報告しなければならない最小の信頼度を設定します。値を低くするとより薄い顔も捉えられますが、誤検出が増えるという代償があります。
残りの 4 行がメインループです。ループの各パスは 1 フレームをキャプチャし、何が見えるかをモデルに尋ねます。
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() は入力のリスト(ここでは 1 枚のキャプチャ画像)を受け取り、検出タプルのリストを返します。各タプルは、バウンディング矩形 (x, y, w, h)、0 から 1 の間の信頼度 score、そしてランドマーク座標の (6, 2) の ndarray(右目、左目、鼻、口、右耳、左耳の順)を保持します。描画呼び出しは draw_rectangle()(画像の章で classical な検出器がすべて最後に使ったのと同じプリミティブ)を使って顔を縁取ります。ml.utils.draw_keypoints() は ml ユーティリティの小さなヘルパーで、各キーポイントをその (x, y) 位置に十字でマークします。
7.3.3. スクリプトが語らないこと¶
このスクリプトは、インポートとセンサー設定を除けば推論作業として 7 行の実行可能なコードですが、その 7 行の内部では非常に多くの計算が行われています。キャプチャされた 400×400 の RGB565 フレームは、ネットワークに到達する前に 128×128 の量子化された 8 ビットテンソルになります。ネットワークは数万個の重みに対して数百もの演算を実行します。結果として得られる信頼度スコアとボックスオフセットのテンソルは、predict が返る前に、ランドマークが付与された重複のないバウンディングボックスのランク付きリストになります。これらの変換のいずれもが、必要であればアプリケーションが 制御できる ものであり、デフォルト以外のモデルではそのいくつかを調整しなければなりません。
次の 4 つの小節では、それらの変換を順に解き明かしていきます。順番は次のとおりです。
ml モジュール — モデルが読み込まれると
ml.Modelが何を公開するか、そしてモデルファイルが実際にカメラ上のどこに存在するか。推論パイプライン — すべての
predict()呼び出しの 4 つのステージ。推論エンジン — ネットワークの計算を実行する CPU と NPU の経路。
出力のデコード — 生の出力テンソルを、このスクリプトが反復処理した検出に変換するポストプロセッサ。
この章を読み終える頃には、読者はカメラに同梱されていないモデルに対して同等のスクリプトを書き、まだポストプロセッサが存在しないテンソルをデコードし、なぜ特定のモデルがあるカメラでは 30 FPS で動作し別のカメラでは 3 FPS なのかを推論できるようになります。