サーフェス法線
サーフェス法線推定は、見えている各面が向く方向を予測します。LibreYOLOはnormalタスクとして提供し、元画像のキャンバス上にunit vectorの密なfieldを返します。
定義
normal タスクは1枚のRGB画像から、pixelごとに3成分のunit vectorを予測します。そのpixelにある面が向く方向です。深度と異なり、出力に任意のスケールはないため、2つの推論結果を位置合わせなしで直接比較できます。
推論によって result.normal_map という NormalMap ペイロードが埋められ、元画像キャンバス上の (H, W, 3) float32配列を保持します。result.normals からも同じものにアクセスできます。ベクトルはLibreYOLOのOpenCV camera frameを使い、+x は右、+y は下、+z はsceneの奥を表します。ベクトルはカメラを向くため、正面を向く面の値は (0, 0, -1) になります。.assert_normalized() はすべてのpixelが有限で、許容誤差内のunit lengthであることを確認します。result.boxes は空のままなので、conf、iou、max_det は効果がなく、Results.plot() はこのタスクに対応します。
モデル
2つのファミリーが normal を提供します。
MoGe-2は専用ファミリーです。3つのencoderサイズを持つ、単一forwardの単眼geometry modelです。LibreYOLOはこれらのチェックポイントを独自のorganizationへコピーしません。読み込むと、固定されたrevisionの公式リポジトリから一致するサイズをダウンロードし、記録済みSHA-256に対して検証します。
LibreMODUSはany-to-anyモデルの1つの対象として法線を生成し、RGB画像ではなく深度マップを入力することもできます。modus 追加パッケージと、認証済みの自身のHugging Faceアカウントが必要です。val() も export() も提供しないため、以下の検証およびエクスポートセクションには該当しません。
推論
MoGe-2の重みは初回使用時にダウンロードされ、ローカルにキャッシュされます。
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE, save=True) normals = result.normal_mapprint(normals.data.shape) # (H, W, 3) float32 unit vectornormals.assert_normalized() # unit lengthでないpixelがあれば例外を送出from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE) # OpenCV camera frame: +xは右、+yは下、+zはsceneの奥# カメラを向く面は(0, 0, -1)に近い値field = result.normals.datah, w = field.shape[:2]print(field[h // 2, w // 2])from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE) # plot()はfieldをrenderし、normalとedgeの結果向けに定義されているresult.plot().save("normals.png")imgsz はViT encoderのpatch sizeで割り切れる必要があり、LibreYOLOは実行開始前に確認します。画像リストの推論では、画像ごとに1回のforward passを実行します。このタスクにはstacked-batchの高速経路がありません。ソース、ストリーミング、結果の処理については推論を参照してください。
データセット形式
法線の検証では、各画像を同じ解像度・同じstemの3チャンネル16-bit PNG、および任意のvalidity maskと対応付けます。
dataset/
data.yaml
images/
val/room.jpg
normals/
val/room.png
masks/
val/room.pngpath: dataset
train: images/train
val: images/val
normals_dir: normals
masks_dir: masks
nc: 1
names: {0: normal}対象PNGは正確に3チャンネルの uint16 で、チャンネルはRGBとして保存されます。decodeは n = png / 65535 * 2 - 1 の後、各ベクトルを再正規化し、decode後のベクトルは推論結果と同じOpenCV camera frameを使います。mask pixelがnonzeroなら有効です。maskファイルがない場合は、有限でzeroでないすべてのdecode済みベクトルが有効です。無効およびpadding対象のpixelは内部で (0, 0, 0) として保持され、どの指標にも寄与しません。完全な仕様についてはデータセット形式を参照してください。
学習
どちらのnormalファミリーにも学習実装はありません。両方で train() が NotImplementedError を送出します。MoGe-2のページには、推論、検証、エクスポート向けに固定された公式チェックポイントへのリンクがあります。
検証
val() は、データセットが有効と示すpixelについて、推論ベクトルと正解ベクトルの角度を測定します。
from libreyolo import LibreYOLO model = LibreYOLO("LibreMoGe2s-normal.pt")metrics = model.val(data="my-dataset.yaml", imgsz=518) print(metrics["metrics/mean_angular_error"]) # degreeprint(metrics["metrics/median_angular_error"]) # degreeprint(metrics["metrics/within_11_25"]) # pixelのpercentageprint(metrics["metrics/within_22_5"], metrics["metrics/within_30"])metrics/mean_angular_error と metrics/median_angular_error はdegree単位の角度で、小さいほど優れています。metrics/within_11_25、metrics/within_22_5、metrics/within_30 は、角度誤差がそれぞれ11.25、22.5、30 degree以内となる有効pixelの割合で、大きいほど優れています。単位に注意してください。この3つはfractionではなくpercentageです。fitness は metrics/within_11_25 を100で割った値で、best-checkpoint選択をほかのすべてのタスクと同じ [0, 1] スケールに合わせます。
エクスポート
エクスポート済みnormal modelはファイル接尾辞に基づいて LibreYOLO() から再度読み込めるため、.onnx ファイルはチェックポイントと同様に動作し、同じ Results を返します。
from libreyolo import LibreYOLO model = LibreYOLO("LibreMoGe2s-normal.pt")model.export(format="onnx", imgsz=518)from libreyolo import LibreYOLO, SAMPLE_IMAGE # ファクトリーはファイル接尾辞で経路を選ぶため、エクスポート済み成果物も# 通常のチェックポイントと同様に読み込まれ、同じResultsオブジェクトを返すmodel = LibreYOLO("LibreMoGe2s-normal.onnx")result = model(SAMPLE_IMAGE) print(result.normal_map.data.shape)normalのエクスポートは、固定解像度・batch 1のランタイム仕様を使います。dynamic と1以外の batch は拒否され、imgsz はencoderのpatch sizeで割り切れる必要があります。形式ごとの対応範囲はMoGe-2ページと完全なエクスポートマトリックスにあります。エクスポートには、すべての形式で受け付ける引数が記載されています。