MoGe-2 で1枚の画像から面の向きと3Dを推定する — Apple Siliconで検証
こんにちは、皆さん。
1枚の写真から、物体までの距離だけでなく、道路が上を向いている、建物の壁が横を向いている、といった「面の向き」まで取得できると、3D化や照明処理へ使える情報が増えます。
さて、今日は MoGe-2 ViT-S Normal を Apple Silicon で動かし、surface normal の見え方、CPUとMPSの速度、同時に得られる3D情報との整合性を検証しました。
先に結果を書くと、Apple M1 Max のMPSでは推論中央値が 212.13 ms で、CPUの 1,259.34 ms より 5.94倍高速 でした。道路、卓上物体、群衆の3画像で面の向きと物体境界を目視でき、道路、猫、女の子の1枚絵から、カメラを左右へ動かす2.5D動画も作成できました。
MoGe-2とは
MoGe-2 は、Microsoft Research の研究者らが 2025年6月10日 に公開した単眼geometry推定モデルです。単眼とは、ステレオカメラではなく1枚のRGB画像だけを使う、という意味です。論文は同年7月3日に arXiv へ投稿されました。
初代MoGeが画像内の相対的な3D形状を得意としていたのに対し、MoGe-2はメートル単位のscale、細部の鋭さ、normal推定をまとめて強化しています。
surface normal(法線)は、各画素に写る面がどちらを向いているかを表す3次元ベクトルです。この記事のカラフルなnormal画像では、そのx・y・z方向をRGBへ割り当てています。色は物体の種類ではなく、面の向きを示します。
今回検証する内容
公式のnormal対応モデルで最小の Ruicheng/moge-2-vits-normal を使い、次を確認します。
- Apple M1 Max のCPUとMPSで追加patchなしに動くか
- 同じFP32モデルでMPSがCPUよりどの程度速いか
- 道路、卓上物体、群衆のnormalを目視できるか
- 直接予測したnormalと、3D point mapから計算したnormalがどの程度一致するか
- point mapをtexture付き3D meshへ変換できるか
MPSは、MacのGPUをPyTorchから使うbackendです。対象のlabはこちらです。
kiarina/labs/2026/07/16/moge2-surface-normal-apple-silicon
検証環境の再現
Apple Silicon Mac、mise、uv、初回のモデル・共有画像取得にインターネット接続が必要です。
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/kiarina/labs.git
cd labs
git sparse-checkout set .gitignore .mise/tasks Makefile mise.toml 2026/07/16/moge2-surface-normal-apple-silicon
mise -C 2026/07/16/moge2-surface-normal-apple-silicon run
初回は固定revisionのcheckpointを取得し、SHA-256を検証します。道路画像からGLBと4秒のBlender動画まで作る場合は、Blender 5.1.2とFFmpegを用意して次を実行します。
mise -C 2026/07/16/moge2-surface-normal-apple-silicon run render-video
使用したモデル、仕組み、ライセンス
使用した学習済みモデルは1つです。複数のAIモデルを順に呼ぶ構成ではありません。1つのcheckpoint内で、画像特徴を読むencoderと、各出力を作るheadが役割分担します。
RGB画像
-> DINOv2 ViT-S encoder: 画像全体と細部の特徴を抽出
-> shared neck: 解像度の異なる特徴へ展開
-> points head: 各画素の3D座標
normal head: 各画素の面の向き
mask head: 信頼できる画素
scale head: メートル単位へ合わせるscale
-> metric point map / depth / normal / mask / camera intrinsics
-> (任意)point map + 元画像texture -> GLB mesh
headは、共有した特徴から目的別の値を出す小さな出力部分です。point mapは各画素をカメラ座標系の3D点へ変換したもの、camera intrinsicsは画角や焦点距離に関係するカメラ内部情報です。
| 項目 | 固定値 |
|---|---|
| checkpoint | Ruicheng/moge-2-vits-normal/model.pt |
| parameters | 35,103,656 |
| file size | 140,550,416 bytes |
| SHA-256 | 79a16621928c2bf0ed04659218c55c01075e950507f40bb3332fb4c873d3e1dc |
| Hugging Face revision | 679230677b4d282c6f304189a93e98e14f085902 |
| MoGe repository commit | 07444410f1e33f402353b99d6ccd26bd31e469e8 |
ライセンスは次のとおりです。
- MoGeのコード: MIT License
-
moge/model/dinov2に含まれるDINOv2コード: Apache License 2.0 - 使用したHugging Face checkpoint: MITとして表示
checkpointそのものはlabへcommitせず、実行時に固定revisionから取得します。再配布や製品組み込みでは、利用時点の公式条件も確認してください。
検証方法
入力は短辺384へ縮小し、resolution_level=5、FP32、batch 1に固定しました。速度は道路画像で3回warm-upした後に10回測定し、モデル・画像の読み込み、前処理、保存は含めていません。warm-upは初回だけ発生する準備処理の影響を減らす事前実行です。
直接予測normalの比較相手は正解データではありません。同じ推論で得たpoint mapの隣接3D点から面の向きを計算し、画素ごとの角度差を測りました。2つの出力が内部でどの程度整合するかを見るテストです。
before / after
左が入力、右がMoGe-2の直接予測normalです。
道路
路面、左右の建物、自動車、樹木が異なる向きとして分かれました。空は無効領域として黒くなっています。
卓上物体
机や本の広い面は色がまとまり、カップとボトルでは曲面に沿って色が連続的に変わりました。
群衆
顔や肩の丸み、重なった人物の境界が残りました。一方、遠景や細かな人物には不安定な縞も見られました。
検証結果
CPUとMPSの速度
| backend | mean | median | min | max | std dev |
|---|---|---|---|---|---|
| PyTorch CPU | 1,272.74 ms | 1,259.34 ms | 1,244.37 ms | 1,340.16 ms | 28.24 ms |
| PyTorch MPS | 211.58 ms | 212.13 ms | 208.75 ms | 214.68 ms | 1.88 ms |
中央値ではMPSがCPUより 5.94倍高速 でした。MPSの約212 msは、モデル推論だけなら約4.71 FPSに相当します。同じ入力に対するCPUとMPSのnormal角度差は平均0.0063度、中央値0度、最大0.0396度でした。
記事執筆時に同じコマンドで再実行すると、CPUの中央値は1,299.96 ms、MPSは175.88 msで、MPSが7.39倍高速でした。model hash、normal角度差、3画像の整合性データは元の測定と一致しました。推論時間は実行時の状態で変動するため、速度は単一の固定値ではなく、今回の2回の測定範囲として見る必要があります。
2種類のnormalの整合性
| image | valid pixels | mean | median | p90 |
|---|---|---|---|---|
| street | 90.80% | 30.75° | 18.69° | 78.55° |
| objects | 100.00% | 14.41° | 7.05° | 35.86° |
| crowd | 98.63% | 37.94° | 33.04° | 73.47° |
卓上画像の広い平面ではよく一致し、物体境界、細い構造、樹木、群衆、遠景で差が大きくなりました。point map側のnormalは隣接画素の差から計算するため、奥行きが急に変わる境界をまたぐと崩れやすくなります。この角度差を、直接予測normalの精度とは断定できません。
3枚の画像を3D動画に変換
公式CLIでpoint mapを三角形へ変換し、元画像をtextureにしたGLBを生成しました。さらにBlenderで元のカメラ位置から左右へ移動し、視差が見える4秒の動画にしました。
掲載しているAPNGは表示用に512x288、10 FPSへ軽量化しています。labには1280x720、30 FPSの元MP4が生成されます。
車のある道路
手前の自動車、道路、左右の建物、人物の間に視差が出ました。カメラを横へ動かすだけでも、1枚の画像が奥行きを持つsurfaceへ変換されたことが分かります。
vertices: 233,120
triangles: 449,292
dimensions: 約30.75 x 114.21 x 21.15 m
estimated horizontal FoV: 60.52°
GLB size: 13,342,320 bytes
猫のキャラクター
ピンクの猫が木、ベンチ、花壇より手前へ配置され、はっきりした視差が出ました。頭部には緩やかな丸みがありますが、耳、手、蝶ネクタイ、胴体はカメラから見えていた面を中心にした薄い形です。
女の子のキャラクター
女の子、ベンチ、街灯、看板、背景の木々が異なる奥行きへ分かれました。一方、髪、腕、脚は独立した完全な立体ではなく、輪郭に沿う薄いsurfaceとして見えます。
3つのmeshの実測値です。
| image | vertices | triangles | FoV x / y | GLB size |
|---|---|---|---|---|
| street | 233,120 | 449,292 | 60.52° / 32.53° | 13,342,320 bytes |
| cat | 312,105 | 599,514 | 54.27° / 42.05° | 17,893,416 bytes |
| girl | 325,649 | 621,216 | 60.30° / 47.08° | 18,731,304 bytes |
道路ではカメラを左右へ±0.8 m、猫と女の子では被写体が近く推定されたため±0.5 m動かしました。これは見えている面を奥行き方向へ配置した 2.5D です。2.5Dとは、元視点の近くでは立体的に見えても、物体の裏側まで復元した完全な3Dではない、という意味です。
3例とも小さな視点移動には使えましたが、空、木の葉、髪、物体境界ではtriangleを除去した黒い穴が見えます。写実画像だけでなくアニメ調画像も動かせる一方、細い輪郭やstylized shapeの3D解釈には限界がありました。
試行中にわかったこと
最初はdepth画像の上下左右の差だけからnormalを作り、角度差の中央値が113〜143度になりました。この方法は透視投影とカメラ内部情報を無視していました。metric point mapの3D点から計算する方法へ変えると、比較可能な向きになりました。
また、公式実装はCPUとMPSでFP32 autocast未対応のwarningを出しましたが、autocastが自動的に無効になり、patchなしで推論は完了しました。
検証環境は次のとおりです。
machine: MacBook Pro (Apple M1 Max, 64 GB, arm64)
OS: macOS 26.5.2
Python: 3.12.10
PyTorch: 2.13.0
OpenCV: 5.0.0
NumPy: 2.5.1
Blender: 5.1.2
FFmpeg: 8.1.2
結果の考察
専門的な数値は上に載せたとおりですが、簡単に読むと次の3点です。
-
MacのGPUで実用的に待てる速度になった
MPSでは1枚約176〜212 msでした。リアルタイム動画にはまだ遅いものの、画像編集や3D化の前処理なら使いやすい速度です。
-
1回の推論から複数の3D情報を得られた
depthだけでなく、metric point map、normal、mask、camera intrinsicsが同時に得られます。別モデルをつなぐ必要がなく、各出力を組み合わせやすい構成です。
-
境界と見えない面には限界がある
normalは広い面や曲面を捉えましたが、細い構造や遠景は不安定でした。1枚の画像から作るmeshも、裏側を含む完全な3D空間ではありません。
本検証は正解normal・depthのない生成画像3枚、速度測定は道路画像1枚、短辺384、FP32、1台のM1 Maxに限られます。公式benchmark、実写、ViT-B/L、FP16、Core ML、Apple Neural Engine、動画の時間的一貫性、消費電力、正解データに対する精度は未検証です。
検証後の感想
1枚の画像から道路の向き、物体の丸み、メートル単位のpoint mapまで一度に得られるのは、思っていた以上に扱いやすい結果でした。特に、normal専用headの出力はpoint mapから単純計算したものより境界が滑らかで、直接予測する意味が見えました。
一方、3D meshは小さく視点を動かす用途には面白いものの、自由に歩き回れる空間ではありません。
1枚の絵にほんの少し視点の動きを加えたいとき、こんな使い方も楽しそうですね。





