7.7. 正規化

ml.Model.predict() は入力のリストを受け取ります。これは一部のネットワークが複数の入力テンサーを持つためですが、リストには入力ごとの引数をインラインで運ぶ手段がありません -- 「この入力を (x, y, w, h) に切り抜くが他の入力はそのままにする」というkwargスロットは存在しないのです。ml.preprocessing.Normalization はそのギャップを埋めるラッパーです。Normalization インスタンスは1つの入力のパラメータを保持します。スクリプトはデフォルト以外のものが必要なときはいつでも、ラップされた入力をpredictリストに渡します。

これに手を伸ばす最も一般的な理由は、画像全体ではなくキャプチャされたフレームの特定の領域をネットワークに切り抜くためです。

7.7.1. パラメータ

Normalization(scale=(0.0, 1.0),
              mean=(0.0, 0.0, 0.0),
              stdev=(1.0, 1.0, 1.0),
              roi=None)
  • roi -- リサイズ前に切り抜くソースフレーム内の (x, y, w, h) 矩形。デフォルトはフレーム全体です。Normalization のほとんどの用途はこのパラメータだけを設定します。

  • scale -- 浮動小数点入力テンサーが正規化後に期待する (min, max) 範囲。ピクセル範囲 0..255 がこの範囲に線形にマッピングされます。一般的な値は、ReLUで学習されたネットワークでは (0.0, 1.0)、対称的に正規化されたネットワークでは (-1.0, 1.0) です。

  • mean -- スケーリング後に画像から減算されるチャンネルごとの (R, G, B) 平均。ネットワークが学習されたチャンネル統計に一致します -- ImageNet由来のネットワークでは (0.485, 0.456, 0.406) が代表的な例です。グレースケールネットワークは、標準的な 0.299*R + 0.587*G + 0.114*B を使って平均を輝度値に縮約します。

  • stdev -- 平均が減算された後に画像が除算されるチャンネルごとの (R, G, B) 標準偏差で、これもネットワークの学習統計に一致します。グレースケールネットワークでは同じ方法で輝度に縮約されます。

7.7.2. パラメータが重要になるとき

scalemeanstdev は、ネットワークの input_dtypeint8 または uint8 の場合は無視されます。整数入力のネットワークでは、切り抜かれた画像バイトがテンサーに直接書き込まれ、ネットワーク自身の input_scaleinput_zero_point が整数から実数への変換を処理します。これら3つのパラメータは、ネットワークが浮動小数点入力を期待する場合にのみ重要です。

roi はすべてのケースで読み取られます -- 入力dtypeに関係なく、ソースフレームのどの部分がネットワークに到達するかを制御します。

7.7.3. ROIとリサイズ

ROIはソース寸法からネットワークの入力寸法へバイリニアにスケーリングされます。画像はデスティネーションの中央に配置され、スケーリングはデスティネーションを埋めます -- アスペクト比は保持されません。正方形のネットワーク入力に与えられた非正方形のROIは、水平方向または垂直方向に引き伸ばされて出力されます。

引き伸ばしが重要かどうかはネットワークによります。MediaPipeファミリー(BlazeFace、FaceLandmarks、HandLandmarks、MoveNet)のような顔検出・ランドマークモデルは正方形の切り抜きに対して学習されており、入力アスペクト比がずれると急速に劣化します。それらの場合、アプリケーションは正方形のROIを与える必要があります -- window() を通じて正方形のフレームサイズでキャプチャするか、roi= パラメータで切り抜くかのいずれかです。YOLOファミリーの物体検出器は通常、ランダムな引き伸ばしを含むオーグメンテーションで学習されており、精度をあまり損なわずに非正方形のROIを受け入れます。キャプチャしたフレーム全体をそのまま渡しても通常は問題ありません。

ネットワークの入力寸法がROIと正確に一致する場合、スケールはコピーに崩れ、これが最も安価なケースです。

7.7.4. デフォルトの上書き

predict() は各 image.Image 入力を Normalization() で自動的にラップします -- 上記のデフォルトパラメータです。カメラに同梱されているほとんどのモデルは、デフォルトが既にカバーするピクセル範囲に対して学習されているため、一般的なケースは画像を直接渡すことです:

result = model.predict([img])

カスタムROIを使用するには -- 最も一般的な上書きです -- ROIを設定した Normalization を構築し、画像をそれにバインドします:

from ml.preprocessing import Normalization

norm = Normalization(roi=(80, 60, 160, 120))
result = model.predict([norm(img)])

ネットワークの学習時のチャンネル統計に一致させるには、浮動小数点パラメータを設定します:

norm = Normalization(scale=(0.0, 1.0),
                     mean=(0.485, 0.456, 0.406),
                     stdev=(0.229, 0.224, 0.225))

result = model.predict([norm(img)])

Normalization インスタンスを画像に対して呼び出すと、エンジンがそこからテンサーを埋める新しいバインド済みインスタンスが返されます。バインド済みインスタンスは、生の画像の代わりにpredictが受け付けるものであり、入力ごとのオブジェクトであるため、複数入力のネットワークは同じpredictリスト内で異なるROIを持つ画像を混在させることができます。

アプリケーションが既にテンサー形式で生成した入力 -- ペリフェラルからのバッファ、別のパイプラインで計算された ndarray、画像でない数値データ -- を期待するネットワークの場合は、Normalization を完全にスキップして、ndarrayまたはそれを生成する呼び出し可能オブジェクトを渡します。predict() はそれらをラップせずにエンジンへ通過させます。