仕上げは AI との協働でやっています。事実と表現は著者本人が確認しています。
Gaussian Splatting (3DGS) を触ってみたいと思っていたのですが、論文を先に読もうとすると数式のところで止まってしまって、なかなか手元で動かすところまで辿り着けていませんでした。Colab でひとまず動かしてみたら、「何を入れて何が出てくるか」が自分の画面で見える分、論文の方も後から読みやすくなったので、その順序で進めた記録を残しておきます。
理屈の整理は他の方の解説記事の方が圧倒的に詳しいので、ここでは「最初の30分で何をすると、何が動くのか」というところに絞って書きます。
触ってみた環境
| 項目 | 値 |
|---|---|
| 実行環境 | Google Colab (GPU ランタイム: T4 で動きました) |
| 学習データ | NeRF Synthetic dataset (公式が推奨しているもの) |
| 所要時間 | データ取得5分 + 学習15-30分 + レンダリング5分くらい |
3DグラフィクスやNeRFを事前に知っている必要はなくて、Python の pip install が読める程度の前提でいけます。詰まりやすいところは後ろにまとめてあります。
Gaussian Splatting は何をしているのか
ざっくり言うと「写真の集合から、空間に浮かぶ何百万個の透明な楕円体 (= ガウス) を配置して、どの角度から見ても元の写真と矛盾しないようにパラメータを調整する」という処理をしています。
教科書的な3Dモデル (= 三角形ポリゴンの集まり) ではなく、位置・色・透明度・形を持った点の群れでシーンを表しているのが特徴で、毛皮や煙のような「面で表現するのが苦手なもの」は比較的素直に表現できるようです。
NeRF (Neural Radiance Fields) と比べると違いが分かりやすかったので並べておきます。
| 項目 | NeRF | Gaussian Splatting |
|---|---|---|
| シーン表現 | ニューラルネットの重み (= ブラックボックス) | 明示的な3Dガウス点群 (= ファイルとして取り出せる) |
| レンダリング速度 | 遅い (1フレーム数秒〜) | 速い (リアルタイムで回せる) |
| 学習速度 | 遅い (数時間〜) | 速い (数十分〜) |
| 後工程との相性 | あまり良くない | Blender 等のツールに乗せやすい |
NeRF が「シーンを暗記したAI」だとすると、Gaussian Splatting は「シーンを点群として書き出したファイル」に近いかなと思いました。学習が速いというのもありがたいのですが、出力が点群のファイルとして取り出せるので、後で別のツールに渡せるというのが個人的には大きい差でした。
データの流れを先に整理しておく
実装に入る前に、何を入れて何が出てくるかを整理しておきます。
[入力]
├── 画像ファイル群 (PNG/JPG) 1シーンにつき30〜300枚くらい
├── カメラ情報 (transforms_train.json) 各画像がどこから撮影されたかの位置・向き
└── 初期点群 (points3D.ply) COLMAPデータでは付く。NeRF Syntheticでは無く、ランダム初期化で動く
↓ 学習プロセス (反復最適化)
[中間表現]
ガウス点群: 数十万〜数百万個
各点が持つパラメータ: 位置(x,y,z) / 色(RGB) / 透明度 / 形 (共分散行列)
↓ レンダリング
[出力]
任意の視点から見た2D画像 (リアルタイムで生成可能)
ここで気を付けたいのは、カメラ情報 JSON が無いと学習が成立しないということでした。「ただの写真フォルダ」だけ持っていても始められなくて、各写真が撮影された位置・向きの情報を別途用意する必要があります。
実世界の写真から始めたい場合は COLMAP という Structure-from-Motion ツールで先にこの JSON を作る工程が入りますが、今回は NeRF Synthetic dataset を使う = JSON が最初から付属しているデータセットを使うので、その準備を飛ばせます。最初に触ってみるならこちらが楽だと思います。
Colab で動かす手順
公式リポジトリ (graphdeco-inria/gaussian-splatting) の README は本格的に書かれているのですが、Colab で一発実行するには少し読み解きが要ります。実際に動かしてみると、最初に書かれているデータ取得 URL がすでにリンク切れだったり、Google Drive 版の取得が弾かれたりと、手順の途中で何度か止まりました。ここでは私が一度通したセルをそのまま並べておきます。新しい Colab ノートブックを開いて、ランタイムのタイプを GPU (T4) にしてから、上から順にコピペで実行する想定です。
1. GPU が割り当たっているか先に確認する
ここで GPU 名が表示されなければ、ランタイムのタイプを変更してから接続し直します。後ろの工程はすべて GPU 前提です。
!nvidia-smi --query-gpu=name,memory.total --format=csv
import torch
print(torch.__version__, torch.cuda.is_available(), torch.version.cuda)
2. リポジトリを clone する
diff-gaussian-rasterization と simple-knn はサブモジュールなので、--recursive を付け忘れると後のビルドで「ディレクトリが空」と言われて詰まります。
!git clone --recursive https://github.com/graphdeco-inria/gaussian-splatting
%cd /content/gaussian-splatting
3. CUDA 拡張モジュールをビルドする
ここが一番落ちやすいので、ビルド前に Colab 側の CUDA と torch 側の CUDA を表示して、目で揃っているか確認してから install します。
!nvcc --version | tail -1
import torch; print('torch CUDA:', torch.version.cuda)
!pip install -q ./submodules/diff-gaussian-rasterization
!pip install -q ./submodules/simple-knn
!pip install -q plyfile tqdm
4. データセットを取得する
公式チュートリアルにある cseweb.ucsd.edu の nerf_synthetic.zip は、私が試した時点ではリダイレクト先が 404 で取れませんでした。Google Drive 版も gdown がアクセス制限で弾かれることが多いです。そこで、展開済みのディレクトリ構造でミラーされている HuggingFace のデータセットから lego だけを落とす形にしたら安定しました。
!pip install -q huggingface_hub
from huggingface_hub import snapshot_download
path = snapshot_download(
repo_id='pablovela5620/nerf-synthetic-mirror',
repo_type='dataset',
allow_patterns=['lego/**'],
local_dir='/content/data',
)
!ls /content/data/lego
5. 学習を回す
T4 (15GB) では 7000 iterations が安全圏でした。NeRF Synthetic は背景が白なので --white_background を付けます。
!python train.py -s /content/data/lego --iterations 7000 --white_background --eval
6. レンダリングして結果画像を出力する
学習結果は output/<ランダムな runid>/ に出ます。runid を手で探さなくて済むように、最新の run を自動で拾ってからレンダリングしました。
import glob, os
RUN = sorted(glob.glob('/content/gaussian-splatting/output/*'), key=os.path.getmtime)[-1]
!python render.py -m {RUN}
output/<runid>/test/ours_7000/renders/ に各視点の画像が書き出されます。実際に lego を 7000 iterations で回して、test 視点の何枚かを並べたのが下の画像です。背景の白とノイズはまだ残っていますが、ショベルカーの形がどの角度からも再構成できているのが分かります。
ここで形になっていれば学習は効いています。点群そのものを回して見たいときは、次の point_cloud.ply を SuperSplat に放り込みます。
詰まりやすいところ
実際に触ってみると何箇所か手が止まる場面があったので、対処法と一緒に残しておきます。
CUDA ビルドが失敗する
diff-gaussian-rasterization のビルドが落ちる場合、ほぼ Colab の CUDA バージョンと PyTorch のバージョンが噛み合っていないことが原因でした。!nvcc --version と python -c "import torch; print(torch.version.cuda)" の両方を出して比べると、ずれていることが分かります。ずれていたら PyTorch を入れ直すと通ります。
データの初期点群 (points3D.ply) で混乱した
最初、COLMAP データの説明を読んでいて「各シーンに sparse/0/points3D.ply が要る」と思い込んでいたのですが、これは実世界の写真を COLMAP に通したときの話でした。今回の NeRF Synthetic は transforms_*.json を持っているので初期点群のファイルは要らず、train.py がランダムな初期点群から始めてくれます。自前の写真から始めるときに COLMAP の点群が要る、と切り分けて理解したら混乱が解けました。
メモリ不足 (CUDA out of memory)
ガウス点の数が増えすぎると VRAM が枯渇します。Colab の無料 T4 (15GB) では --iterations 7000 くらいまでが安全圏で、30000 まで回そうとすると落ちることがありました。最初は 7000 で止めて、回るのを確認してから iterations を増やしていく順序の方が事故が少ないです。
データ取得 URL が当てにならない
冒頭にも書きましたが、ここが一番つまずきました。公式チュートリアルの wget URL はリダイレクト先が 404、Google Drive 版は gdown がアクセス制限で弾かれる、という状態でした。手元では HuggingFace のミラーから lego だけ落とす形に切り替えて安定しましたが、配布元は時期によって生き死にが変わるので、動かす前にまずデータが落ちてくるかだけ先に確認するのが結局一番早かったです。
出力された3Dシーンの見方
学習が終わると output/<runid>/point_cloud/iteration_<N>/point_cloud.ply に最終的なガウス点群が出ます。これを以下のいずれかで見ます。
-
SuperSplat: ブラウザで開ける
.plyビューア。ドラッグ&ドロップで読めるので一番ハードルが低いです - Blender + Gaussian Splatting アドオン: 後工程で別の3Dシーンと合成するときに有用
- 公式リポジトリ付属の SIBR ビューア: Linux/Windows のローカル実行が必要
最初に動かしてみるなら SuperSplat が楽だと思います。.ply をブラウザに投げ込むだけで、3D空間として回して確認できます。
触ってみて感じたこと
最初に動かす前は「3Dの何かを生成する重そうな処理」というイメージだったのですが、Colab で一度回してみると、「写真とカメラ情報を投入する → 反復最適化が走る → 点群ファイルが出る」という流れ自体はそこまで複雑ではなくて、むしろデータの準備の方が手間というのが見えてきました。7000 iterations のレンダリング結果で、別々の角度から見た lego がどれも同じショベルカーとして再構成されているのを見たときが、一番「ああ、ちゃんと3Dになっている」と実感できたところです。
論文を先に読もうとして数式で止まっていたのが、動かしてから読むと「あの計算がこのパラメータか」という順序で読めるようになって、入口の足場ができた感覚があります。理屈の理解を後回しにすることに抵抗がある人もいるかと思いますが、今回のように動かしてから取りに行く順序も悪くないなと思いました。
次に触ってみるなら
最初の一回が動いた後、深掘りする方向としては以下の3つが見えています。
-
自前のデータで動かす: 自分のスマホで撮った動画から3Dシーンを作るところまでやる。COLMAP で
transforms.jsonを作る工程が増えます - Modal で動かす: Colab は12時間でランタイムが切れてしまうので、本格的に学習を回すなら Modal の方が向いていそうです。GPU を秒課金で長時間使えるのが大きい
- パラメータの意味を取りに行く: scomupさんの解説記事 を読みながら学習率やガウス分割の閾値を触ってみる。動かしながら読む方が頭に入りやすいです
