├── .gitignore ├── README.md ├── dataset └── .gitkeep ├── img ├── diff_distort.png ├── feature_extraction.png ├── feature_matching.png └── pointcloud.png ├── notebook ├── 01_train_nerf_local.ipynb ├── 02_render_nerf_local.ipynb └── 03_render_nerf_colab.ipynb ├── requirements.txt ├── results └── ckpt ├── setup.py └── src └── nerf_tutorial ├── __init__.py ├── colmap_utils.py ├── datasets.py ├── extrinsics.py ├── intrinsics.py ├── nerf.py ├── nerf_utils.py ├── radiance_field.py └── visualization_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .ipynb_checkpoints/ 3 | nohup.out 4 | results/* 5 | dataset/* 6 | nerf_tutorial.egg-info/ 7 | Dockerfile 8 | 9 | !.gitkeep 10 | !ckpt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # COLMAPとNeRFを使った3次元復元 2 | ## Google Colab用ノートブック 3 | - 学習済みモデルによるレンダリング [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](http://colab.research.google.com/github/ALBERT-Inc/NeRF-tutorial/blob/main/notebook/03_render_nerf_colab.ipynb) 4 | 5 | ## 1: COLMAPの紹介と各種コマンド 6 | 7 | COLMAPはSfM用のソフトウェアです。カメラパラメータの推定に加え、ソフトウェア単体でも3次元復元ができます。インストールは[githubのリリースページ](https://github.com/colmap/colmap/releases)からOSに合わせて実行ファイルをダウンロードできます。復元の一部過程にGPU(cuda)を利用する場合、ソースコードからのビルドが必要になるようです。 8 | 9 | 操作はGUIとCUIの両方に対応しており、GUIではソフトウェア上の各種ダイアログで細かな設定ができます。CUIでは、コマンドライン引数の形で推定に使うパラメータを設定できます。コマンドライン引数を利用する場合は `colmap <操作名> -h` で各操作における引数が確認できます。 10 | 11 | ### 1.1: COLMAPの前提 12 | SfMは、2次元の画像から3次元の情報を復元するタスクです。しかし、画像は撮影された時点で2次元に落とし込まれているため、例えば奥行きのような情報が消えてしまいます。失われた情報を1枚の画像のみから復元することは不可能なため、少なくとも2枚以上の画像間で視差をとる必要が出てきます。このように、複数の画像を入力として、被写体の座標情報やカメラの撮影情報を推定するツールがCOLMAPになります。 13 | 14 | ### 1.2: プロジェクトの初期化 15 | 想定しているディレクトリ初期構成は以下の通りです。本リポジトリでは、<project_dir>は`NeRF-tutorial/dataset/`以下を想定しています。 16 | ``` 17 | 18 | . 19 | ├── 20 | └── 21 | ├── Image0.jpg 22 | ├── Image1.jpg 23 | ├── … 24 | └── ImageN.jpg 25 | ``` 26 | - GUI `File > New project` 27 | DatabaseはNewを選択し、database.dbなど適切な名前を入力します。Imagesはselectを選択して<image_dir> を指定します。saveを押すとプロジェクトが作成できます。 28 | - CUI 29 | 特になし。 30 | 31 | ### 1.3: 特徴点抽出 32 | [SIFTアルゴリズム](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_feature2d/py_sift_intro/py_sift_intro.html)を用いた画像内の特徴的な部分(特徴点)の抽出と、復元に利用するカメラモデル(カメラ内部パラメータの細かさ)を指定します。 33 | 34 | SfMにおいて複数画像間の視差を取る際、画像中のピクセル全部を比較するのは効率が非常に悪いので、都合の良い点を画像中から見つけることを考えます。都合の良い点としては、画像を画像たらしめる特徴を持つ点であったり、他の画像と比較できる点が望ましいです。例えば、画像中の特徴点として、物体の角(コーナー)や模様のような情報が使えます。COLMAPの場合、特徴点抽出手法として画像のスケール変化(同じ点に注目しても、拡大されていた場合には特徴で無くなるような変化)に対して頑健なSIFTを用いています。SIFTアルゴリズムでは、画像内の二次元座標と、それに対応する近辺の局所特徴量(128次元)をセットで獲得します。以下の図はSIFTを利用して特徴点抽出をおこなった結果になります。 35 | 36 | ![./img/feature_extraction.png](./img/feature_extraction.png) 37 | 38 | [カメラの内部パラメータ](https://jp.mathworks.com/help/vision/ug/camera-calibration.html)には、焦点距離f、画像中心cx/cy、半径方向の歪みパラメータk1、k2、k3、円周方向の歪みパラメータp1、p2、p3などがあります。 39 | この内、カメラモデルによっては歪みパラメータであるkやpは省略されることもあります。 40 | 一般的なカメラパラメータモデルが持つ内部パラメータを以下の表にまとめています。 41 | 42 | | カメラモデル | パラメータ | 43 | | ---- | ---- | 44 | | SIMPLE_PINHOLE | f, cx, cy | 45 | | PINHOLE | fx, fy, cx, cy | 46 | | SIMPLE_RADIAL | f, cx, cy, k | 47 | | RADIAL | f, cx, cy, k1, k2 | 48 | | OPENCV | fx, fy, cx, cy, k1, k2, p1, p2 | 49 | | FULL_OPENCV | fx, fy, cx, cy, k1, k2, p1, p2, k3, k4, k5, k6 | 50 | 51 | その他カメラモデルがどのようなパラメータを返すかについては、[こちらのソースコード](https://github.com/colmap/colmap/blob/master/src/base/camera_models.h)のコメント部分に書かれています。 52 | カメラのレンズ歪み補正にOpenCVを利用する都合で、以降ではOPENCVというカメラモデルを想定します。 53 | なお、この時点のカメラの内部パラメータ推定では、画像プロパティのEXIF情報等から焦点距離のみが取得され、その他のパラメータは後段の処理で推定されることとなります。 54 | 55 | - GUI `processing > Feature extraction` 56 | - CUI 57 | - 引数 58 | - database_path: データベース(sqlite3)書き出しパス 59 | - image_path: <image_dir>までのパス 60 | - ImageReader.camera_model: [COLMAPで用いるカメラ内部パラメータモデル](https://colmap.github.io/cameras.html) 61 | - SIMPLE_PINHOLE: 焦点距離f、画像中心cx/cyのみピンホールカメラモデル 62 | - デフォルトモデル 63 | - SIMPLE_RADIAL: SIMPLE_PINHOLE + 半径方向の歪みパラメータk1、k2 64 | - OPENCV: SIMPLE_RADIAL + 円周方向の歪みパラメータp1、p2 65 | - 歪み補正をOPENCVで行う場合、このモデルを利用する 66 | - SiftExtration.gpu_index=0: SIFTの計算に使うGPU番号 67 | - GPUの使用はSiftExtraction.use_gpuで指定。使用は1(デフォルト)、不使用は0 68 | 69 | ``` 70 | $ colmap feature_extractor \ 71 | --database_path=./database.db \ 72 | --image_path=.// \ 73 | --ImageReader.camera_model=OPENCV \ 74 | --SiftExtraction.gpu_index=0 75 | ``` 76 | 77 | ### 1.4: 特徴点マッチング 78 | 画像間で特徴点の一致を取る処理になります。 79 | 80 | 画像集合中の各ペアについて、ペアごとの局所特徴量を最近傍探索することでマッチする点を見つけます。この結果、写っているものの重なり(マッチ)が確認された画像のペアと、そのペアにおける特徴点同士の対応関係が獲得できます。以下の図は、視点の異なる2枚の画像について特徴点マッチングをおこなった結果になります。 81 | 82 | 83 | ![./img/feature_matching.png](./img/feature_matching.png) 84 | 85 | 画像内に多数含まれる特徴点を全ての画像ペアについて比較すると計算量が膨大になるため、画像類似度で絞り込みを行うことで対応枚数を減らす等の工夫もあるようです[[参考](https://www.tugraz.at/fileadmin/user_upload/Institute/ICG/Documents/courses/robotvision/2019/RV_SFM.pdf)]。 86 | 87 | - GUI `processing > Feature matching` 88 | - CUI 89 | - 引数 90 | - database_path: 特徴点抽出で作成した.dbファイルへのパス 91 | - SiftMatching.gpu_index: 特徴量抽出と同じくマッチングで使うGPUの番号 92 | 93 | ``` 94 | $ colmap exhaustive_matcher \ 95 | --database_path=./database.db \ 96 | --SiftMatching.gpu_index=0 97 | ``` 98 | 99 | 100 | ### 1.5: SfMの実行 101 | 特徴点マッチングで得られた画像ペアをもとに、条件を満たすような3次元点群を復元しながら、カメラの撮影位置(Translation)や回転(Rotation)を推定します。 102 | 103 | COLMAPのSfMでは、Incremental SfMという手法を採用しています。これは、複数画像から一度にSfMを行うのではなく、2枚の画像ペアに対して[三角測量](https://daily-tech.hatenablog.com/entry/2019/07/15/183302)を行ってベースとなる点群座標の推定、カメラ位置/回転の推定をした後、徐々に画像を追加して辻褄を合わせていく処理になります。そのため、初めに選出される2枚の画像ペアが精度に大きな影響を与えるようです。画像を追加した後に、全体の最適化として[バンドル調整](https://daily-tech.hatenablog.com/entry/2021/05/03/180350)という作業を行います。バンドル調整では、Incremental SfMで推定した各カメラパラメータを利用して、改めて画像中の点を3次元空間上に再投影し、その損失の最も小さな組み合わせを探索します。 104 | 105 | - GUI `reconstruction > Start reconstruction` 106 | - CUI 107 | - 引数 108 | - database_path: 特徴点抽出で作成した.dbファイルへのパス 109 | - image_path: <image_dir>へのパス 110 | - output_path: <sparse_dir>へのパス 111 | 112 | ``` 113 | $ colmap mapper \ 114 | --database_path=./database.db \ 115 | --image_path=./ \ 116 | --output_path ./sparse 117 | ``` 118 | 119 | ### 1.6: txtファイルへの書き出し 120 | <sparse_dir>以下にバイナリで保存されている各種パラメータはそのままでは使いづらいため、txtファイルに書き出します。txtファイルの記述ルールについては、txtファイルのヘッダー部分に書き出されています。 121 | 122 | - GUI `File > Export model as text` 123 | - CUI 124 | - 引数 125 | - input_path: <sparse_dir>以下のバイナリを含むフォルダ。sparse直下の場合やsparse/0/のような下位ディレクトリがある場合があります。 126 | - output_path: txtファイルを保存する場所 127 | - output_type: 書き出す形式の指定 128 | 129 | ``` 130 | $ colmap model_converter \ 131 | --input_path ./sparse/0/ \ 132 | --output_path ./ \ 133 | --output_type TXT 134 | ``` 135 | 136 | ## 2: NeRFを使った復元 137 | 以降では、COLMAPで推定したカメラパラメータを利用して、NeRFモデルの学習と3次元復元を行います。 138 | 本実装を参考に紹介していきます。 139 | 140 | ### 2.1: COLMAP推定結果(txtファイル)の読み取り 141 | COLMAP推定結果(txtファイル)には、`cameras.txt`、`images.txt`、`points3D.txt`の3ファイルが含まれます。それぞれのファイルには行単位で以下の内容が含まれています(詳細情報はtxtファイルのヘッダーに記述されています)。 142 | 143 | - cameras.txt: 撮影に利用したカメラに関する、カメラモデルの情報 144 | - CAMERA_ID: COLMAP内のカメラID (全て同じカメラの場合は1種類のみ) 145 | - MODEL: 推定時に指定したカメラモデル 146 | - WIDTH: 画像の横サイズ 147 | - HEIGHT: 画像の縦サイズ 148 | - PARAMS[]: カメラモデルごとに指定されるパラメータ 149 | - images.txt: SfMによって推定した画像ごとのカメラポーズ行列(2行ごとに記述) 150 | - IMAGE_ID: COLMAP内の画像ID 151 | - QW、QX、QY、QZ: クォータニオン表記のカメラ回転 152 | - TX、TY、TZ: カメラの移動 153 | - CAMERA_ID: 撮影に利用したカメラのカメラID 154 | - NAME: 画像ファイル名 155 | - POINTS2D[]: SfMで復元された点群に対応する画像内の点。(X、Y、points3d.txtでのID)で記述 156 | - points3D.txt: SfMの結果画像間で対応があるとされた点群 157 | - POINT3D_ID: 点群における点のID 158 | - X、Y、Z: 点の座標 159 | - R、G、B: 点の色 160 | - ERROR: SfMにおける推定誤差 161 | - TRACK[]: images.txtの画像との対応。(画像ID、images.txtでの点ID)で記述 162 | 163 | ### 2.2: NeRFのデータ前処理 164 | 165 | #### 2.2.1: 画像のレンズ歪み補正 166 | 167 | 画像は、カメラで撮影した際に「レンズ歪み」と呼ばれる歪みが発生します。レンズ歪みは、1.3節で推定したカメラの内部パラメータの内、半径方向の歪みパラメータk1、k2、円周方向の歪みパラメータp1、p2といった係数で表されます。しかし、NeRFで利用するカメラモデルは歪みを想定しないシンプルなピンホールカメラなので、画像から[レンズ歪みの要素を除去する](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_calib3d/py_calibration/py_calibration.html)必要があります。以下の画像は、歪み補正前と歪み補正後の画像を重ねて表示した結果になります。画像の歪みを取り除いたことで画像に若干の差が発生し、ボヤけていることが確認できます(部屋の奥の線が二重に描画されている点が顕著だと思います)。 168 | 169 | ![./img/diff_distort.png](./img/diff_distort.png) 170 | 171 | #### 2.2.2: ポーズ行列修正 172 | COLMAP出力の回転行列は世界座標系からカメラ座標系への変換を表す形式です。NeRFでは反対にカメラ座標系から世界座標系への座標変換を行うため、ポーズ行列を修正する必要があります。この修正では、元の座標変換と逆の変換を獲得するため、単純に逆行列を計算することになります。 173 | 174 | #### 2.2.3: 座標の正規化 175 | NeRFでは、シーンの世界座標にPositional Encodingの情報を付与してレンダリングを行います。Positional Encodingでは、sin関数やcos関数を使っているため、入力となる世界座標は-1から1の範囲に収まるように正規化する必要があります。世界座標の正規化には、COLMAPで復元されたカメラポーズ行列と点群を利用します。ポーズ行列内のカメラ位置(Translation)と、画像に対応づけられた点群との距離を求めることで、おおよそのレンダリング範囲が特定できます。レンダリング範囲の内、最も遠いレンダリング距離を使って世界座標を割ることで、-1から1の範囲に収まるようにします。 176 | 177 | ### 2.3: NeRFモデルの学習 178 | 本実装では、オリジナルのNeRFモデルに加えて、カメラパラメータの修正を行えるBARFモデルやNeRF--モデルを学習できます。(各モデルについては[ブログ記事](https://blog.albert2005.co.jp/2021/10/21/nerf-without-camera-parameters/)を参照してください)。実装されているモデルに関する設定は全てNeRFConfigクラスに渡す引数で制御します。NeRFConfigクラスについて、デフォルトの設定ではオリジナルNeRFモデルの設定となっていて、その他のモデルを呼び出す際の引数としては、それぞれ以下の引数を設定する必要があります。 179 | 180 | - カメラパラメータ調整全体 181 | - normalize_focals: 焦点距離を画像縦横サイズに対する比率で表すか否か 182 | - intrinsic_fixed: 内部パラメータを学習するか否か 183 | - extrinsic_fixed: 外部パラメータを学習するか否か 184 | - BARF関連 185 | - barf: BARFとして学習する否か 186 | - barf_start_epoch: BARFのcoarse-to-fine処理を始めるepoch数(alpha値の修正をし始めるタイミング) 187 | - barf_end_epoch: BARFのcoarse-to-fine処理を終えるepoch数(alpha値が上限である1に到達するタイミング) 188 | - NeRF--関連 189 | - nerfmm: NeRF--として学習するか否か 190 | - extrinsic_transf: NeRF--は内部的に回転ベクトル表現を利用する都合で取得したデータによっては左手/右手座標系変換が必要で、そのための4行4列の回転・並進行列を指定する 191 | 192 | オリジナルNeRFで学習する場合、NeRF--として学習する場合、BARFとして学習する場合について、それぞれ以下のようなconfigの呼び出しとなります。 193 | 194 | ```python 195 | # NeRF 196 | config = NeRFConfig() 197 | 198 | # NeRF-- 199 | config = NeRFConfig( 200 | nerfmm=True, extrinsic_fixed=False, intrinsic_fixed=False) 201 | 202 | # BARF 203 | config = NeRFConfig(barf=True, extrinsic_fixed=False) 204 | ``` 205 | 206 | Configの設定や、NeRF/各種カメラパラメータクラスの呼び出しをすることで、NeRFの学習ができます。オリジナルのNeRFを用いて200枚程度の画像データを2000epoch学習した場合、DGX A100の環境では7時間程度で終わります。オリジナルNeRFの場合、100epoch(30分程度)でも物体の大体の形状が分かるレンダリングができるようになります。 207 | 208 | ### 2.4: 3次元復元 209 | NeRFでは、学習でのフォワード処理と同じ手順による画像のレンダリングや、ニューラルネットワーク内部で利用される密度情報を使った点群復元ができます。後者の方法では、NeRFのネットワークが視線上の点に対するRGBの色と密度σを予測することを利用して、密度σが一定の閾値より大きな点のみを抽出します。以下の画像は、実際にNeRFモデルを利用して復元した会議室の点群になります。 210 | 211 | ![./img/pointcloud.png](./img/pointcloud.png) 212 | -------------------------------------------------------------------------------- /dataset/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ALBERT-Inc/NeRF-tutorial/2a21d84c6d13960ea55f8fdc8293d97a0d42b6f8/dataset/.gitkeep -------------------------------------------------------------------------------- /img/diff_distort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ALBERT-Inc/NeRF-tutorial/2a21d84c6d13960ea55f8fdc8293d97a0d42b6f8/img/diff_distort.png -------------------------------------------------------------------------------- /img/feature_extraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ALBERT-Inc/NeRF-tutorial/2a21d84c6d13960ea55f8fdc8293d97a0d42b6f8/img/feature_extraction.png -------------------------------------------------------------------------------- /img/feature_matching.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ALBERT-Inc/NeRF-tutorial/2a21d84c6d13960ea55f8fdc8293d97a0d42b6f8/img/feature_matching.png -------------------------------------------------------------------------------- /img/pointcloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ALBERT-Inc/NeRF-tutorial/2a21d84c6d13960ea55f8fdc8293d97a0d42b6f8/img/pointcloud.png -------------------------------------------------------------------------------- /notebook/02_render_nerf_local.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "c518ab82-8aed-4f2e-aece-9c255a93ccd5", 6 | "metadata": {}, 7 | "source": [ 8 | "# NeRFの推論" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "1c79fb82-82ff-4540-b95f-1f17a8059258", 14 | "metadata": {}, 15 | "source": [ 16 | "## ロード/ディレクトリ設定" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 1, 22 | "id": "6adb03d8-92f2-468e-83ba-6bb99cdd1032", 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "import os\n", 27 | "import torch\n", 28 | "import matplotlib.pyplot as plt\n", 29 | "from nerf_tutorial.intrinsics import Intrinsic\n", 30 | "from nerf_tutorial.extrinsics import PoseExtrinsic, RVecExtrinsic\n", 31 | "from nerf_tutorial.nerf import NeRFConfig, NeRF, NeRFLoss\n", 32 | "from nerf_tutorial.visualization_utils import IpywidgetsRenderer\n", 33 | "from nerf_tutorial.nerf_utils import render_nerf, extract_pointcloud" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "id": "ad69f6ce-04b8-4f95-91a0-b86395b52edb", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "base_dir = os.path.dirname(os.getcwd())\n", 44 | "out_dir = os.path.join(base_dir, \"results\", \"original_nerf\")" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "id": "09357ad3-b4a2-4be7-aede-cc5ba26d86db", 50 | "metadata": {}, 51 | "source": [ 52 | "## NeRFの呼び出し" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "id": "1333a78e-5652-4d1d-b516-5d6b9dde4232", 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "data": { 63 | "text/plain": [ 64 | "NeRFLoss(\n", 65 | " (nerf): NeRF(\n", 66 | " (rf_c): RadianceField(\n", 67 | " (layer0): Linear(in_features=60, out_features=256, bias=True)\n", 68 | " (layer1): Linear(in_features=256, out_features=256, bias=True)\n", 69 | " (layer2): Linear(in_features=256, out_features=256, bias=True)\n", 70 | " (layer3): Linear(in_features=256, out_features=256, bias=True)\n", 71 | " (layer4): Linear(in_features=256, out_features=256, bias=True)\n", 72 | " (layer5): Linear(in_features=316, out_features=256, bias=True)\n", 73 | " (layer6): Linear(in_features=256, out_features=256, bias=True)\n", 74 | " (layer7): Linear(in_features=256, out_features=256, bias=True)\n", 75 | " (sigma): Linear(in_features=256, out_features=1, bias=True)\n", 76 | " (layer8): Linear(in_features=256, out_features=256, bias=True)\n", 77 | " (layer9): Linear(in_features=280, out_features=128, bias=True)\n", 78 | " (layer10): Linear(in_features=128, out_features=128, bias=True)\n", 79 | " (layer11): Linear(in_features=128, out_features=128, bias=True)\n", 80 | " (layer12): Linear(in_features=128, out_features=128, bias=True)\n", 81 | " (rgb): Linear(in_features=128, out_features=3, bias=True)\n", 82 | " )\n", 83 | " (rf_f): RadianceField(\n", 84 | " (layer0): Linear(in_features=60, out_features=256, bias=True)\n", 85 | " (layer1): Linear(in_features=256, out_features=256, bias=True)\n", 86 | " (layer2): Linear(in_features=256, out_features=256, bias=True)\n", 87 | " (layer3): Linear(in_features=256, out_features=256, bias=True)\n", 88 | " (layer4): Linear(in_features=256, out_features=256, bias=True)\n", 89 | " (layer5): Linear(in_features=316, out_features=256, bias=True)\n", 90 | " (layer6): Linear(in_features=256, out_features=256, bias=True)\n", 91 | " (layer7): Linear(in_features=256, out_features=256, bias=True)\n", 92 | " (sigma): Linear(in_features=256, out_features=1, bias=True)\n", 93 | " (layer8): Linear(in_features=256, out_features=256, bias=True)\n", 94 | " (layer9): Linear(in_features=280, out_features=128, bias=True)\n", 95 | " (layer10): Linear(in_features=128, out_features=128, bias=True)\n", 96 | " (layer11): Linear(in_features=128, out_features=128, bias=True)\n", 97 | " (layer12): Linear(in_features=128, out_features=128, bias=True)\n", 98 | " (rgb): Linear(in_features=128, out_features=3, bias=True)\n", 99 | " )\n", 100 | " )\n", 101 | " (intrinsic): Intrinsic()\n", 102 | " (extrinsic): PoseExtrinsic()\n", 103 | ")" 104 | ] 105 | }, 106 | "execution_count": 3, 107 | "metadata": {}, 108 | "output_type": "execute_result" 109 | } 110 | ], 111 | "source": [ 112 | "device = torch.device(\"cuda:0\")\n", 113 | "\n", 114 | "# NeRF\n", 115 | "config = NeRFConfig()\n", 116 | "nerf = NeRF(**config.nerf_kwargs())\n", 117 | "\n", 118 | "# camera parameters\n", 119 | "ckpt = torch.load(\n", 120 | " os.path.join(out_dir, \"ckpt_last\"), \n", 121 | " map_location=\"cpu\"\n", 122 | ")[\"state_dict\"]\n", 123 | "pose = ckpt[\"extrinsic.pose\"].numpy()\n", 124 | "image_wh = ckpt[\"intrinsic.image_wh\"].numpy()\n", 125 | "fs = ckpt[\"intrinsic.fs\"].numpy()\n", 126 | "cxcy = ckpt[\"intrinsic.cxcy\"].numpy()\n", 127 | "\n", 128 | "intrinsic = Intrinsic(\n", 129 | " image_wh, focals=fs, cxcy=cxcy, **config.intrinsic_kwargs())\n", 130 | "\n", 131 | "if config.nerfmm:\n", 132 | " extrinsic = RVecExtrinsic(\n", 133 | " len(pose), pose, **config.extrinsic_kwargs())\n", 134 | "else:\n", 135 | " extrinsic = PoseExtrinsic(\n", 136 | " len(pose), pose, **config.extrinsic_kwargs())\n", 137 | "\n", 138 | "loss_func = NeRFLoss(nerf, intrinsic=intrinsic, extrinsic=extrinsic)\n", 139 | "loss_func.load_state_dict(ckpt)\n", 140 | "loss_func.to(device)" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "id": "f24543a9-f70d-41f9-b5dc-009a797592af", 146 | "metadata": {}, 147 | "source": [ 148 | "## レンダリング" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 4, 154 | "id": "0f51176c-2661-45be-b67f-19503293fab4", 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [ 158 | "idx = 0\n", 159 | "camera_parameters = {\n", 160 | " \"w\": 800,\n", 161 | " \"h\": 600,\n", 162 | " \"pose\": extrinsic[[idx]],\n", 163 | " \"device\": device,\n", 164 | "}\n", 165 | "\n", 166 | "W, H = image_wh\n", 167 | "cx, cy = cxcy\n", 168 | "fx, fy = fs\n", 169 | "\n", 170 | "camera_parameters[\"cx\"] = cx / W * camera_parameters[\"w\"]\n", 171 | "camera_parameters[\"cy\"] = cy / H * camera_parameters[\"h\"]\n", 172 | "camera_parameters[\"fx\"] = fx / W * camera_parameters[\"w\"]\n", 173 | "camera_parameters[\"fy\"] = fy / H * camera_parameters[\"h\"]" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 5, 179 | "id": "2004c0f2-85a2-4386-bfd9-89da8402420c", 180 | "metadata": {}, 181 | "outputs": [ 182 | { 183 | "data": { 184 | "image/png": "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\n", 185 | "text/plain": [ 186 | "
" 187 | ] 188 | }, 189 | "metadata": { 190 | "needs_background": "light" 191 | }, 192 | "output_type": "display_data" 193 | } 194 | ], 195 | "source": [ 196 | "# render single image\n", 197 | "img = render_nerf(nerf, camera_parameters, only_coarse=False)\n", 198 | "plt.imshow(img)\n", 199 | "plt.show()" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": 6, 205 | "id": "a089c628-fe2e-49f8-8845-2d07769b4d69", 206 | "metadata": {}, 207 | "outputs": [ 208 | { 209 | "data": { 210 | "application/vnd.jupyter.widget-view+json": { 211 | "model_id": "cb9a112efbea4dc2ac8a024441ec9624", 212 | "version_major": 2, 213 | "version_minor": 0 214 | }, 215 | "text/plain": [ 216 | "VBox(children=(Image(value=b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x03 \\x00\\x00\\x02X\\x08\\x02\\x00\\x00\\x00…" 217 | ] 218 | }, 219 | "metadata": {}, 220 | "output_type": "display_data" 221 | }, 222 | { 223 | "data": { 224 | "text/plain": [ 225 | "" 226 | ] 227 | }, 228 | "execution_count": 6, 229 | "metadata": {}, 230 | "output_type": "execute_result" 231 | } 232 | ], 233 | "source": [ 234 | "# renderer with controller\n", 235 | "IpywidgetsRenderer(nerf, camera_parameters, only_coarse=True)" 236 | ] 237 | }, 238 | { 239 | "cell_type": "markdown", 240 | "id": "c9211931-594d-4372-897b-8683755e7475", 241 | "metadata": {}, 242 | "source": [ 243 | "## 点群抽出" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 7, 249 | "id": "29192be9-74b4-4e24-a8f2-0a27c418dd2b", 250 | "metadata": {}, 251 | "outputs": [], 252 | "source": [ 253 | "pcd = extract_pointcloud(\n", 254 | " nerf, num_grid_edge=300, sigma_threshold=50, device=device)\n", 255 | "pcd.to_file(os.path.join(out_dir, \"nerf_repro.ply\"))" 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": null, 261 | "id": "e2813ee1-f400-4433-8bd8-e0a6c4a30f4d", 262 | "metadata": {}, 263 | "outputs": [], 264 | "source": [] 265 | } 266 | ], 267 | "metadata": { 268 | "kernelspec": { 269 | "display_name": "Python 3", 270 | "language": "python", 271 | "name": "python3" 272 | }, 273 | "language_info": { 274 | "codemirror_mode": { 275 | "name": "ipython", 276 | "version": 3 277 | }, 278 | "file_extension": ".py", 279 | "mimetype": "text/x-python", 280 | "name": "python", 281 | "nbconvert_exporter": "python", 282 | "pygments_lexer": "ipython3", 283 | "version": "3.6.9" 284 | } 285 | }, 286 | "nbformat": 4, 287 | "nbformat_minor": 5 288 | } 289 | -------------------------------------------------------------------------------- /notebook/03_render_nerf_colab.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "c518ab82-8aed-4f2e-aece-9c255a93ccd5", 6 | "metadata": { 7 | "id": "c518ab82-8aed-4f2e-aece-9c255a93ccd5" 8 | }, 9 | "source": [ 10 | "# NeRFの推論 on Google Colab" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "7823e269-9f2f-49ba-a424-6c3a6113e137", 16 | "metadata": { 17 | "id": "7823e269-9f2f-49ba-a424-6c3a6113e137" 18 | }, 19 | "source": [ 20 | "## 必要なモジュールのインストール" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "10008e9e-5ff8-4a24-be63-670e240d23b5", 27 | "metadata": { 28 | "id": "10008e9e-5ff8-4a24-be63-670e240d23b5" 29 | }, 30 | "outputs": [], 31 | "source": [ 32 | "!pip install pyntcloud" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "id": "uQD5xXIBF30E", 39 | "metadata": { 40 | "id": "uQD5xXIBF30E" 41 | }, 42 | "outputs": [], 43 | "source": [ 44 | "!git clone https://github.com/ALBERT-Inc/NeRF-tutorial.git\n", 45 | "%cd \"./NeRF-tutorial\"\n", 46 | "!pip install --editable ./\n", 47 | "%cd \"../\"\n", 48 | "import site\n", 49 | "site.main()" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "id": "1c79fb82-82ff-4540-b95f-1f17a8059258", 55 | "metadata": { 56 | "id": "1c79fb82-82ff-4540-b95f-1f17a8059258" 57 | }, 58 | "source": [ 59 | "## ロード/ディレクトリ設定" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "id": "6adb03d8-92f2-468e-83ba-6bb99cdd1032", 66 | "metadata": { 67 | "id": "6adb03d8-92f2-468e-83ba-6bb99cdd1032" 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "import os\n", 72 | "import torch\n", 73 | "import matplotlib.pyplot as plt\n", 74 | "from nerf_tutorial.intrinsics import Intrinsic\n", 75 | "from nerf_tutorial.extrinsics import PoseExtrinsic, RVecExtrinsic\n", 76 | "from nerf_tutorial.nerf import NeRFConfig, NeRF, NeRFLoss\n", 77 | "from nerf_tutorial.visualization_utils import IpywidgetsRenderer\n", 78 | "from nerf_tutorial.nerf_utils import render_nerf, extract_pointcloud" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "id": "ad69f6ce-04b8-4f95-91a0-b86395b52edb", 85 | "metadata": { 86 | "id": "ad69f6ce-04b8-4f95-91a0-b86395b52edb" 87 | }, 88 | "outputs": [], 89 | "source": [ 90 | "base_dir = os.getcwd()\n", 91 | "out_dir = os.path.join(base_dir, \"results\")\n", 92 | "ckpt_path = os.path.join(out_dir, \"ckpt\")\n", 93 | "\n", 94 | "if not os.path.exists(out_dir):\n", 95 | " os.makedirs(out_dir, exist_ok=True)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "TbzZPO8-KQr6", 102 | "metadata": { 103 | "id": "TbzZPO8-KQr6" 104 | }, 105 | "outputs": [], 106 | "source": [ 107 | "# download checkpoint file\n", 108 | "!wget \"https://github.com/ALBERT-Inc/NeRF-tutorial/blob/main/results/ckpt?raw=true\" -O $ckpt_path" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "id": "09357ad3-b4a2-4be7-aede-cc5ba26d86db", 114 | "metadata": { 115 | "id": "09357ad3-b4a2-4be7-aede-cc5ba26d86db" 116 | }, 117 | "source": [ 118 | "## NeRFの呼び出し" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "id": "1333a78e-5652-4d1d-b516-5d6b9dde4232", 125 | "metadata": { 126 | "id": "1333a78e-5652-4d1d-b516-5d6b9dde4232" 127 | }, 128 | "outputs": [], 129 | "source": [ 130 | "device = torch.device(\"cuda:0\")\n", 131 | "\n", 132 | "# NeRF\n", 133 | "config = NeRFConfig()\n", 134 | "nerf = NeRF(**config.nerf_kwargs())\n", 135 | "\n", 136 | "# camera parameters\n", 137 | "ckpt = torch.load(\n", 138 | " ckpt_path, map_location=\"cpu\")[\"state_dict\"]\n", 139 | "pose = ckpt[\"extrinsic.pose\"].numpy()\n", 140 | "image_wh = ckpt[\"intrinsic.image_wh\"].numpy()\n", 141 | "fs = ckpt[\"intrinsic.fs\"].numpy()\n", 142 | "cxcy = ckpt[\"intrinsic.cxcy\"].numpy()\n", 143 | "\n", 144 | "intrinsic = Intrinsic(\n", 145 | " image_wh, focals=fs, cxcy=cxcy, **config.intrinsic_kwargs())\n", 146 | "\n", 147 | "if config.nerfmm:\n", 148 | " extrinsic = RVecExtrinsic(\n", 149 | " len(pose), pose, **config.extrinsic_kwargs())\n", 150 | "else:\n", 151 | " extrinsic = PoseExtrinsic(\n", 152 | " len(pose), pose, **config.extrinsic_kwargs())\n", 153 | "\n", 154 | "loss_func = NeRFLoss(nerf, intrinsic=intrinsic, extrinsic=extrinsic)\n", 155 | "loss_func.load_state_dict(ckpt)\n", 156 | "loss_func.to(device)" 157 | ] 158 | }, 159 | { 160 | "cell_type": "markdown", 161 | "id": "f24543a9-f70d-41f9-b5dc-009a797592af", 162 | "metadata": { 163 | "id": "f24543a9-f70d-41f9-b5dc-009a797592af" 164 | }, 165 | "source": [ 166 | "## レンダリング" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "id": "0f51176c-2661-45be-b67f-19503293fab4", 173 | "metadata": { 174 | "id": "0f51176c-2661-45be-b67f-19503293fab4" 175 | }, 176 | "outputs": [], 177 | "source": [ 178 | "idx = 0\n", 179 | "camera_parameters = {\n", 180 | " \"w\": 300,\n", 181 | " \"h\": 200,\n", 182 | " \"pose\": extrinsic[[idx]],\n", 183 | " \"device\": device,\n", 184 | "}\n", 185 | "\n", 186 | "W, H = image_wh\n", 187 | "cx, cy = cxcy\n", 188 | "fx, fy = fs\n", 189 | "\n", 190 | "camera_parameters[\"cx\"] = cx / W * camera_parameters[\"w\"]\n", 191 | "camera_parameters[\"cy\"] = cy / H * camera_parameters[\"h\"]\n", 192 | "camera_parameters[\"fx\"] = fx / W * camera_parameters[\"w\"]\n", 193 | "camera_parameters[\"fy\"] = fy / H * camera_parameters[\"h\"]" 194 | ] 195 | }, 196 | { 197 | "cell_type": "code", 198 | "execution_count": null, 199 | "id": "2004c0f2-85a2-4386-bfd9-89da8402420c", 200 | "metadata": { 201 | "id": "2004c0f2-85a2-4386-bfd9-89da8402420c" 202 | }, 203 | "outputs": [], 204 | "source": [ 205 | "# render single image\n", 206 | "img = render_nerf(nerf, camera_parameters, only_coarse=False)\n", 207 | "plt.imshow(img)\n", 208 | "plt.show()" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "id": "a089c628-fe2e-49f8-8845-2d07769b4d69", 215 | "metadata": { 216 | "id": "a089c628-fe2e-49f8-8845-2d07769b4d69" 217 | }, 218 | "outputs": [], 219 | "source": [ 220 | "# renderer with controller\n", 221 | "IpywidgetsRenderer(nerf, camera_parameters, only_coarse=True)" 222 | ] 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "id": "c9211931-594d-4372-897b-8683755e7475", 227 | "metadata": { 228 | "id": "c9211931-594d-4372-897b-8683755e7475" 229 | }, 230 | "source": [ 231 | "## 点群抽出" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": null, 237 | "id": "29192be9-74b4-4e24-a8f2-0a27c418dd2b", 238 | "metadata": { 239 | "id": "29192be9-74b4-4e24-a8f2-0a27c418dd2b" 240 | }, 241 | "outputs": [], 242 | "source": [ 243 | "pcd = extract_pointcloud(\n", 244 | " nerf, num_grid_edge=100, sigma_threshold=50, device=device)\n", 245 | "pcd.to_file(os.path.join(out_dir, \"nerf_repro.ply\"))" 246 | ] 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": null, 251 | "id": "e2813ee1-f400-4433-8bd8-e0a6c4a30f4d", 252 | "metadata": { 253 | "id": "e2813ee1-f400-4433-8bd8-e0a6c4a30f4d" 254 | }, 255 | "outputs": [], 256 | "source": [] 257 | } 258 | ], 259 | "metadata": { 260 | "accelerator": "GPU", 261 | "colab": { 262 | "collapsed_sections": [], 263 | "name": "render_nerf.ipynb", 264 | "provenance": [] 265 | }, 266 | "kernelspec": { 267 | "display_name": "Python 3", 268 | "language": "python", 269 | "name": "python3" 270 | }, 271 | "language_info": { 272 | "codemirror_mode": { 273 | "name": "ipython", 274 | "version": 3 275 | }, 276 | "file_extension": ".py", 277 | "mimetype": "text/x-python", 278 | "name": "python", 279 | "nbconvert_exporter": "python", 280 | "pygments_lexer": "ipython3", 281 | "version": "3.6.9" 282 | } 283 | }, 284 | "nbformat": 4, 285 | "nbformat_minor": 5 286 | } 287 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | open3d 4 | Pillow 5 | imageio 6 | pyntcloud 7 | jupyterlab 8 | matplotlib 9 | scikit-image 10 | opencv-python 11 | torch==1.8.0+cu111 12 | torchvision==0.9.0+cu111 13 | torchaudio==0.8.0 14 | -------------------------------------------------------------------------------- /results/ckpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ALBERT-Inc/NeRF-tutorial/2a21d84c6d13960ea55f8fdc8293d97a0d42b6f8/results/ckpt -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='nerf_tutorial', 5 | version='0.1.0', 6 | description='Tutorial scripts for NeRF', 7 | url='https://github.com/ALBERT-Inc/NeRF-tutorial', 8 | 9 | packages=find_packages(where='src'), 10 | package_dir={'': 'src'}, 11 | ) 12 | -------------------------------------------------------------------------------- /src/nerf_tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from .nerf import NeRF, NeRFConfig, NeRFLoss 2 | from .nerf_utils import ( 3 | camera_parameters_to_rays, 4 | render_nerf 5 | ) 6 | from .intrinsics import Intrinsic 7 | from .extrinsics import RVecExtrinsic, PoseExtrinsic 8 | from .datasets import ( 9 | ImgSampleDataset, 10 | PosedDataset, 11 | collate_fn_sample, 12 | collate_fn_posed 13 | ) 14 | from .colmap_utils import ( 15 | extract_camera_info, 16 | extract_image_info, 17 | undistort_image, 18 | extract_points3D, 19 | calc_bds 20 | ) 21 | from .visualization_utils import plot_cameras, IpywidgetsRenderer 22 | -------------------------------------------------------------------------------- /src/nerf_tutorial/colmap_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cv2 3 | import numpy as np 4 | from PIL import Image 5 | from scipy.spatial.transform import Rotation as R 6 | 7 | 8 | def extract_camera_info(file_path): 9 | """extract camera information from colmap `cameras.txt` file. 10 | 11 | Args: 12 | file_path (str): path to `cameras.txt` file. 13 | Returns: 14 | cameras (list): list of camera informations. 15 | 16 | (example): [{'CAMERA_ID': '1', 17 | 'MODEL': 'OPENCV', 18 | 'WIDTH': 3000, 19 | 'HEIGHT': 2000, 20 | 'cmtx': array([[2.06903e+03, 0.00000e+00, 1.50000e+03], 21 | [0.00000e+00, 2.06827e+03, 1.00000e+03], 22 | [0.00000e+00, 0.00000e+00, 1.00000e+00]]), 23 | 'dist': array([ 0.00222674, -0.00248576, 0.00103703, 0.00043848])}, 24 | {'CAMERA_ID': '2', ...}] 25 | """ 26 | 27 | cameras = [] 28 | 29 | with open(file_path) as fr: 30 | fr.readline() 31 | keys = fr.readline().strip().replace(" ", "")[1:].split(",") 32 | pks = keys[-1] 33 | keys = keys[:-1] 34 | line = fr.readline().strip() 35 | num_data = int(line.replace(" ", "")[1:].split(":")[-1]) 36 | 37 | for idx in range(num_data): 38 | values = fr.readline().strip().split(" ") 39 | dict_values = {} 40 | for k, v in zip(keys, values): 41 | if k in ["WIDTH", "HEIGHT"]: 42 | v = int(v) 43 | dict_values[k] = v 44 | 45 | # reference: https://github.com/colmap/colmap/blob/master/src/base/camera_models.h 46 | if dict_values["MODEL"] == "OPENCV": 47 | # opencv: fx, fy, cx, cy, k1, k2, p1, p2 48 | pvs = [float(v) for v in values[len(keys):]] 49 | K = np.array([ 50 | [pvs[0], 0., pvs[2]], 51 | [ 0., pvs[1], pvs[3]], 52 | [ 0., 0., 1.], 53 | ]) 54 | dist_coeffs = np.array(pvs[4:]) 55 | dict_values["cmtx"] = K 56 | dict_values["dist"] = dist_coeffs 57 | else: 58 | raise NotImplementedError 59 | 60 | cameras.append(dict_values) 61 | 62 | return cameras 63 | 64 | 65 | def extract_image_info(file_path): 66 | """extract image information from colmap `images.txt` file. 67 | 68 | Args: 69 | file_path (str): path to `images.txt` file. 70 | Returns: 71 | images (list): list of image informations. 72 | 73 | (example): [{'CAMERA_ID': '1', 74 | 'NAME': 'DSC_0007.JPG', 75 | 'pose': array([[ 0.98186656, 0.12955013, -0.13840096, -3.13399 ], 76 | [-0.14768339, 0.98045887, -0.1299616 , 0.237271 ], 77 | [ 0.11885991, 0.14804447, 0.98181218, 4.55247 ], 78 | [ 0. , 0. , 0. , 1. ]])}, 79 | {'CAMERA_ID': '3', ...}] 80 | """ 81 | 82 | images = [] 83 | 84 | with open(file_path) as fr: 85 | fr.readline() 86 | keys = fr.readline().strip().replace(" ", "")[1:].split(",") 87 | fr.readline() 88 | line = fr.readline().strip() 89 | num_data = int(line.replace(" ", "")[1:].split(":")[1].split(",")[0]) 90 | 91 | for idx in range(num_data): 92 | values = fr.readline().strip().split(" ") 93 | dict_values = {} 94 | rotation_q = [] 95 | translation = [] 96 | for k, v in zip(keys, values): 97 | if k in ["QW", "QX", "QY", "QZ"]: 98 | rotation_q.append(float(v)) 99 | elif k in ["TX", "TY", "TZ"]: 100 | translation.append(float(v)) 101 | else: 102 | dict_values[k] = v 103 | 104 | quat = np.array([rotation_q[idx] for idx in [1, 2, 3, 0]]) 105 | rotation = R.from_quat(quat) 106 | translation = np.array(translation) 107 | 108 | rotation = rotation.as_matrix() 109 | t = translation[:, None] 110 | pose = np.concatenate([rotation, t], axis=-1) 111 | pose = np.concatenate([pose, np.array([0, 0, 0, 1])[None]], axis=0) 112 | dict_values["pose"] = pose 113 | 114 | images.append(dict_values) 115 | 116 | fr.readline() 117 | 118 | return images 119 | 120 | 121 | def undistort_image(image_info, camera_infos, image_dir, size=None): 122 | """undistort image using camera's distortion coefficients. 123 | 124 | Args: 125 | image_info (dict): dictionary of camera information. 126 | 127 | (example): {'CAMERA_ID': '1', 128 | 'NAME': 'DSC_0007.JPG', 129 | 'pose': array([[ 0.98186656, 0.12955013, -0.13840096, -3.13399 ], 130 | [-0.14768339, 0.98045887, -0.1299616 , 0.237271 ], 131 | [ 0.11885991, 0.14804447, 0.98181218, 4.55247 ], 132 | [ 0. , 0. , 0. , 1. ]])} 133 | 134 | camera_infos (list): list of camera informations. 135 | 136 | (example): [{'CAMERA_ID': '1', 137 | 'MODEL': 'OPENCV', 138 | 'WIDTH': 3000, 139 | 'HEIGHT': 2000, 140 | 'cmtx': array([[2.06903e+03, 0.00000e+00, 1.50000e+03], 141 | [0.00000e+00, 2.06827e+03, 1.00000e+03], 142 | [0.00000e+00, 0.00000e+00, 1.00000e+00]]), 143 | 'dist': array([ 0.00222674, -0.00248576, 0.00103703, 0.00043848])}, 144 | {'CAMERA_ID': '2', ...}] 145 | 146 | image_dir (str): path to image directory. 147 | size (tuple): image size to resize, like (resize_width, resize_height). 148 | Returns: 149 | img_undist (PIL.Image.Image): PIL image after undistort and resize. 150 | new_camera_matrix (numpy.array): camera intrinsic matrix after undistort and resize. 151 | 152 | """ 153 | 154 | file_name = image_info["NAME"] 155 | camera_id = image_info["CAMERA_ID"] 156 | 157 | img = Image.open(os.path.join(image_dir, file_name)) 158 | 159 | w, h = img.size 160 | camera_info = [ 161 | ci for ci in camera_infos 162 | if ci["CAMERA_ID"] == camera_id 163 | ][0] 164 | 165 | camera_matrix = camera_info["cmtx"] 166 | dist_coef = camera_info["dist"] 167 | new_camera_matrix, area_to_crop = cv2.getOptimalNewCameraMatrix( 168 | camera_matrix, dist_coef, (w, h), 1, (w, h)) 169 | 170 | new_img = cv2.undistort( 171 | np.array(img), camera_matrix, dist_coef, None, new_camera_matrix) 172 | 173 | x, y, w, h = area_to_crop 174 | img_undist = Image.fromarray(new_img[y:y+h, x:x+w]) 175 | 176 | if size is not None: 177 | img_undist = img_undist.resize(size) 178 | new_w, new_h = size 179 | 180 | camera_matrix_scaler = np.array([ 181 | [ new_w/w, 1, new_w/w], 182 | [ 1, new_h/h, new_h/h], 183 | [ 1, 1, 1], 184 | ], dtype=np.float32) 185 | new_camera_matrix = new_camera_matrix * camera_matrix_scaler 186 | 187 | return img_undist, new_camera_matrix 188 | 189 | 190 | def extract_points3D(file_path, image_infos): 191 | """extract reconstructed 3d-points from colmap's `points3D.txt` file. 192 | 193 | Args: 194 | file_path (str): path to `points3D.txt` file. 195 | image_infos (list): list of image informations extracted by `extract_image_info` func. 196 | Returns: 197 | xyzs (numpy.array): coordinates of points. this takes (num_points, 3) shape. 198 | rgbs (numpy.array): colors of points. this takes (num_points, 3) shape. 199 | visibility_matrix (numpy.array): correspondence of 3d-point and image. 200 | if element is 1, the 3d-point is in the image. 201 | else (0), the 3d-point is NOT in the image. 202 | this matrix takes (num_points, num_images) shape. 203 | """ 204 | 205 | xyzs, rgbs = [], [] 206 | num_points_header = "Number of points: " 207 | imageid2index = { 208 | info["IMAGE_ID"]: idx 209 | for idx, info in enumerate(image_infos) 210 | } 211 | 212 | with open(file_path) as fr: 213 | for i in range(2): 214 | fr.readline() 215 | 216 | # read num_points 217 | line = fr.readline().rstrip().split(", ")[0] 218 | start_idx = line.find(num_points_header) 219 | num_points = int(line[start_idx+len(num_points_header):]) 220 | visibility_matrix = np.zeros((num_points, len(image_infos))) 221 | 222 | for lid, line in enumerate(fr): 223 | line = line.rstrip().split(" ") 224 | xyzs.append(list(map(float, line[1:4]))) 225 | rgbs.append(list(map(int, line[4:7]))) 226 | 227 | visible_ids = np.array([ 228 | imageid2index[image_id] for image_id in line[8::2] 229 | ]) 230 | visibility_matrix[lid, visible_ids] = 1 231 | 232 | xyzs = np.array(xyzs, dtype=np.float32) 233 | rgbs = np.array(rgbs, dtype=np.float32) 234 | 235 | return xyzs, rgbs, visibility_matrix 236 | 237 | 238 | def calc_bds(xyzs, poses, visibility_matrix): 239 | """calculate requirements of rendering range. 240 | 241 | Args: 242 | xyzs (numpy.array): coordinates of points. this takes (num_points, 3) shape. 243 | poses (numpy.array): pose matrices. this takes (N_images, 4, 4) shape. 244 | visibility_matrix (numpy.array): correspondence of 3d-point and image. 245 | if element is 1, the 3d-point is in the image. 246 | else (0), the 3d-point is NOT in the image. 247 | this matrix takes (num_points, num_images) shape. 248 | Returns: 249 | bds_min (float): minimum distance for rendering. 250 | bds_max (float): maximum distance for rendering. 251 | """ 252 | 253 | xyzs = xyzs[:, None].transpose(2, 0, 1) 254 | poses = poses.transpose(1, 2, 0) 255 | 256 | # calc all distance between cameras and points. 257 | distance = xyzs - poses[:3, 3:4] 258 | distance_in_z = (distance * poses[:3, 2:3]).sum(axis=0) 259 | 260 | # use distance correspond to camera. 261 | distance_in_z = distance_in_z[visibility_matrix == 1] 262 | 263 | bds_min = np.percentile(distance_in_z, .1) 264 | bds_max = np.percentile(distance_in_z, 99.9) 265 | 266 | return bds_min, bds_max 267 | -------------------------------------------------------------------------------- /src/nerf_tutorial/datasets.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | 4 | 5 | class ImgSampleDataset(torch.utils.data.Dataset): 6 | """Dataset class for NeRF with image point sampling. 7 | 8 | Args: 9 | img (torch.tensor/numpy.array): array of images. 10 | num_sample (int): sample size for one datapoint. 11 | """ 12 | 13 | def __init__(self, img, num_sample=1024): 14 | self.img = torch.tensor(img, dtype=torch.float32) 15 | self.num_sample = num_sample 16 | 17 | def __len__(self): 18 | return len(self.img) 19 | 20 | def __getitem__(self, idx): 21 | img = self.img[idx] 22 | pixel_ids = \ 23 | torch.tensor( 24 | np.random.choice( 25 | np.arange(len(img)), 26 | size=self.num_sample, 27 | replace=False 28 | ), dtype=torch.long) 29 | 30 | img = img[pixel_ids] 31 | img_ids = torch.tensor([idx]*self.num_sample, dtype=torch.long) 32 | return img, pixel_ids, img_ids 33 | 34 | 35 | def collate_fn_sample(batch): 36 | imgs = torch.cat([d[0] for d in batch], dim=0) 37 | pixel_ids = torch.cat([d[1] for d in batch], dim=0) 38 | imgs_ids = torch.cat([d[2] for d in batch], dim=0) 39 | return imgs, pixel_ids, imgs_ids 40 | 41 | 42 | class PosedDataset(torch.utils.data.Dataset): 43 | """Dataset class for NeRF with pre-computed rays. 44 | 45 | Args: 46 | imgs (torch.tensor/numpy.array): array of images. 47 | os (torch.tensor/numpy.array): translation of camera. 48 | ds (torch.tensor/numpy.array): ray direction of camera. 49 | """ 50 | 51 | def __init__(self, imgs, os_, ds): 52 | self.imgs = torch.tensor(imgs, dtype=torch.float32) 53 | self.os = torch.tensor(os_, dtype=torch.float32) 54 | self.ds = torch.tensor(ds, dtype=torch.float32) 55 | 56 | def __len__(self): 57 | return len(self.os) 58 | 59 | def __getitem__(self, idx): 60 | img = self.imgs[idx] 61 | o = self.os[idx] 62 | d = self.ds[idx] 63 | return img, o, d 64 | 65 | 66 | def collate_fn_posed(batch): 67 | imgs = torch.stack([d[0] for d in batch], dim=0) 68 | os_ = torch.stack([d[1] for d in batch], dim=0) 69 | ds = torch.stack([d[2] for d in batch], dim=0) 70 | return imgs, os_, ds 71 | -------------------------------------------------------------------------------- /src/nerf_tutorial/extrinsics.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import numpy as np 4 | from scipy.spatial.transform import Rotation as R 5 | 6 | 7 | def rotvec2matrix(rotvec): 8 | """Convert rotation vector to rotatin matrix. 9 | Args: 10 | rotvec (Tensor, [N, 3] or [3,]): Rotation vector to convert to rotatin 11 | matrix. 12 | Returns: 13 | matrix (Tensorm [N, 3, 3] or [3, 3]): Rotation matrix. 14 | """ 15 | stacked = True 16 | if rotvec.dim() == 1: 17 | rotvec = rotvec[None] 18 | stacked = False 19 | 20 | _theta = torch.norm(rotvec, dim=1, keepdim=True) 21 | theta = torch.max(_theta, torch.full_like(_theta, 1e-16)) 22 | k = rotvec / theta 23 | 24 | k_x = k[:, 0] 25 | k_y = k[:, 1] 26 | k_z = k[:, 2] 27 | k_0 = torch.zeros_like(k_x) 28 | K = torch.stack([torch.stack([ k_0, -k_z, k_y], axis=1), # NOQA 29 | torch.stack([ k_z, k_0, -k_x], axis=1), # NOQA 30 | torch.stack([-k_y, k_x, k_0], axis=1)], 31 | axis=1) 32 | 33 | sin_theta = torch.sin(theta)[:, None] 34 | cos_theta = torch.cos(theta)[:, None] 35 | I = torch.eye(3, device=rotvec.device)[None] # NOQA 36 | 37 | matrix = I + sin_theta * K + (1 - cos_theta) * K @ K 38 | 39 | if not stacked: 40 | matrix = matrix[0] 41 | return matrix 42 | 43 | 44 | class RVecExtrinsic(nn.Module): 45 | """Extrinsic parameter class with Rotation Vector. 46 | 47 | Args: 48 | image_num (int): the number of training images. 49 | poses (numpy.array): pose matrices with (N, 4, 4) shape. 50 | fixed, transf: see NeRFConfig class for more details. 51 | """ 52 | 53 | def __init__(self, 54 | image_num, 55 | poses=None, 56 | fixed=False, 57 | transf=None): 58 | 59 | super().__init__() 60 | 61 | self.register_buffer( 62 | "pose", torch.tensor(poses, dtype=torch.float32)) 63 | 64 | if transf is None: 65 | self.register_buffer("transf", torch.tensor([ 66 | [ 1, 0, 0, 0], 67 | [ 0, 1, 0, 0], 68 | [ 0, 0, 1, 0], 69 | [ 0, 0, 0, 1], 70 | ], dtype=torch.float32)[None]) 71 | else: 72 | assert transf.shape == (4, 4) 73 | self.register_buffer("transf", transf[None]) 74 | 75 | if poses is None: 76 | self.translation = nn.Parameter( 77 | torch.zeros((image_num, 3), dtype=torch.float32), 78 | requires_grad=True 79 | ) 80 | self.rotation = nn.Parameter( 81 | torch.zeros((image_num, 3), dtype=torch.float32), 82 | requires_grad=True 83 | ) 84 | else: 85 | translation = poses[:, :3, 3] 86 | rotation = [(p@self.transf[0].numpy())[:3, :3] for p in poses] 87 | rotation = np.stack([ 88 | R.from_matrix(r).as_rotvec() for r in rotation]) 89 | 90 | if fixed: 91 | self.register_buffer( 92 | "translation", 93 | torch.tensor(translation, dtype=torch.float32) 94 | ) 95 | self.register_buffer( 96 | "rotation", 97 | torch.tensor(rotation, dtype=torch.float32) 98 | ) 99 | else: 100 | self.translation = nn.Parameter( 101 | torch.tensor(translation, dtype=torch.float32), 102 | requires_grad=True 103 | ) 104 | self.rotation = nn.Parameter( 105 | torch.tensor(rotation, dtype=torch.float32), 106 | requires_grad=True 107 | ) 108 | 109 | def cam2world(self, xyzw, image_ids): 110 | """transforms from image-plane coordinates to world cooordinates. 111 | 112 | Args: 113 | xyzw (torch.tensor): pixels in image-plane coorinates. 114 | this takes (N, 4) shape, 4 means (x, y, 1, 1). 115 | image_ids (torch.tensor): image ids corresponds to `xyzw`. 116 | this takes (N, ) shape. 117 | Returns: 118 | o (torch.tensor): camera origins in world coordinate. 119 | this takes (W*H, 3) shape. 120 | d (torch.tensor): camera directions in world coordinate. 121 | this takes (W*H, 3) shape. 122 | """ 123 | 124 | _o = np.zeros((len(xyzw), 4), dtype=np.float32) 125 | _o[:, 3] = 1. 126 | _o = torch.tensor(_o, dtype=torch.float32, device=xyzw.device) 127 | 128 | o = self.translation[image_ids] 129 | r = self.rotation[image_ids] 130 | r = rotvec2matrix(r) 131 | 132 | bottom = torch.tensor( 133 | [[[0, 0, 0, 1]]], dtype=torch.float32, device=o.device) 134 | 135 | pose = torch.cat([ 136 | torch.cat([r, o[..., None]], dim=2), 137 | bottom.repeat(len(r), 1, 1) 138 | ], dim=1) 139 | pose = torch.bmm(pose, self.transf.repeat(len(pose), 1, 1)) 140 | 141 | d = torch.bmm(pose, xyzw[..., None])[:, :, 0][:, :3] 142 | o = torch.bmm(pose, _o[..., None])[:, :, 0][:, :3] 143 | d = d - o 144 | d = d / torch.norm(d, dim=1, keepdim=True) 145 | return o, d 146 | 147 | def __getitem__(self, idx): 148 | if not isinstance(idx, list): 149 | idx = [idx] 150 | 151 | o = self.translation[idx] 152 | r = self.rotation[idx] 153 | r = rotvec2matrix(r) 154 | 155 | bottom = torch.tensor( 156 | [[[0, 0, 0, 1]]], dtype=torch.float32, device=o.device) 157 | 158 | pose = torch.cat([ 159 | torch.cat([r, o[..., None]], dim=2), 160 | bottom.repeat(len(r), 1, 1) 161 | ], dim=1) 162 | pose = torch.bmm(pose, self.transf.repeat(len(pose), 1, 1)) 163 | return pose 164 | 165 | 166 | class PoseExtrinsic(nn.Module): 167 | """Extrinsic parameter class with Rotation Matrix. 168 | 169 | Args: 170 | image_num (int): the number of training images. 171 | pose (numpy.array/torch.tensor): 172 | pose matrices with (N, 4, 4) shape. 173 | fixed: see NeRFConfig class for more details. 174 | """ 175 | 176 | def __init__(self, image_num, pose, fixed=False): 177 | super().__init__() 178 | if fixed: 179 | self.register_buffer( 180 | "pose", 181 | torch.tensor(pose, dtype=torch.float32) 182 | ) 183 | else: 184 | self.pose = nn.Parameter( 185 | torch.tensor(pose, dtype=torch.float32), 186 | requires_grad=True 187 | ) 188 | 189 | def cam2world(self, xyzw, image_ids): 190 | """transforms from image-plane coordinates to world cooordinates. 191 | 192 | Args: 193 | xyzw (torch.tensor): pixels in image-plane coorinates. 194 | this takes (N, 4) shape, 4 means (x, y, 1, 1). 195 | image_ids (torch.tensor): image ids corresponds to `xyzw`. 196 | this takes (N, ) shape. 197 | Returns: 198 | o (torch.tensor): camera origins in world coordinate. 199 | this takes (W*H, 3) shape. 200 | d (torch.tensor): camera directions in world coordinate. 201 | this takes (W*H, 3) shape. 202 | """ 203 | 204 | pose = self.pose[image_ids] 205 | 206 | _o = np.zeros((len(xyzw), 4), dtype=np.float32) 207 | _o[:, 3] = 1. 208 | _o = torch.tensor(_o, dtype=torch.float32, device=xyzw.device) 209 | 210 | d = torch.bmm(pose, xyzw[..., None])[:, :, 0][:, :3] 211 | o = torch.bmm(pose, _o[..., None])[:, :, 0][:, :3] 212 | d = d - o 213 | d = d / torch.norm(d, dim=1, keepdim=True) 214 | return o, d 215 | 216 | def __getitem__(self, idx): 217 | if not isinstance(idx, list): 218 | idx = [idx] 219 | return self.pose[idx] 220 | -------------------------------------------------------------------------------- /src/nerf_tutorial/intrinsics.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import numpy as np 4 | 5 | 6 | class Intrinsic(nn.Module): 7 | """Intrinsic parameter class. 8 | 9 | Args: 10 | image_wh (list[int]/torch.tensor/numpy.array): 11 | image size of training data. 12 | focals (list[float]/torch.tensor/numpy.array): 13 | focal lengths in [fx, fy] style. 14 | cxcy (list[float]/torch.tensor/numpy.array): 15 | image center coordinates in [cx, cy] style. 16 | fixed, normalize_focals: 17 | see NeRFConfig class for more details. 18 | """ 19 | 20 | def __init__(self, 21 | image_wh, 22 | focals=None, 23 | cxcy=None, 24 | fixed=False, 25 | normalize_focals=True): 26 | super().__init__() 27 | 28 | self.register_buffer( 29 | "image_wh", torch.tensor(image_wh, dtype=torch.float32) 30 | ) 31 | self.normalize_focals = normalize_focals 32 | W, H = image_wh 33 | 34 | if focals is None: 35 | if normalize_focals: 36 | self.fs = nn.Parameter( 37 | torch.ones(2, dtype=torch.float32), requires_grad=True 38 | ) 39 | else: 40 | self.fs = nn.Parameter( 41 | torch.tensor(image_wh, dtype=torch.float32), 42 | requires_grad=True 43 | ) 44 | else: 45 | assert normalize_focals is False 46 | 47 | if fixed: 48 | self.register_buffer( 49 | "fs", torch.tensor(focals, dtype=torch.float32) 50 | ) 51 | else: 52 | self.fs = nn.Parameter( 53 | torch.tensor(focals, dtype=torch.float32), 54 | requires_grad=True 55 | ) 56 | 57 | if cxcy is None: 58 | cxcy = torch.tensor([W*0.5, H*0.5], dtype=torch.float32) 59 | else: 60 | cxcy = torch.tensor(cxcy, dtype=torch.float32) 61 | self.register_buffer("cxcy", cxcy) 62 | 63 | def get_cam_pixels(self, device=None): 64 | W, H = self.image_wh 65 | 66 | v, u = np.mgrid[:H, :W].astype(np.float32) 67 | u = torch.tensor(u, dtype=torch.float32, device=device) 68 | v = torch.tensor(v, dtype=torch.float32, device=device) 69 | 70 | if self.normalize_focals: 71 | fs = ( 72 | self.fs * torch.tensor(self.image_wh, 73 | dtype=torch.float32, 74 | device=device) 75 | ) 76 | else: 77 | fs = self.fs 78 | cxcy = self.cxcy 79 | 80 | _x = (u - cxcy[0]) / fs[0] 81 | _y = (v - cxcy[1]) / fs[1] 82 | _z = torch.ones_like(_x, device=device) 83 | _w = torch.ones_like(_x, device=device) 84 | 85 | xyzw = torch.stack([_x, _y, _z, _w], dim=2) 86 | return xyzw.reshape(-1, 4) 87 | -------------------------------------------------------------------------------- /src/nerf_tutorial/nerf.py: -------------------------------------------------------------------------------- 1 | # slightly modified of https://github.com/ALBERT-Inc/blog_nerf/blob/master/NeRF.ipynb 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | from .radiance_field import RadianceField 7 | from .nerf_utils import ( 8 | position_encode, position_encode_barf, 9 | split_ray, sample_coarse, sample_fine, _pcpdf, ray 10 | ) 11 | 12 | 13 | class NeRFConfig(object): 14 | """Config class for NeRF. 15 | 16 | Args: 17 | dim_former (int): hidden dim in nerf network before sigma. 18 | dim_latter (int): hidden dim in nerf network after sigma. 19 | t_n (float): nearest distance of rendering range. 20 | t_f (float): farthest distance of rendering range. 21 | L_x (int): frequency size of `coordinate` positional encoding. 22 | L_d (int): frequency size of `direction` positional encoding. 23 | N_c (int): bin size of integral in nerf `coarse` rendering. 24 | N_f (int): bin size of integral in nerf `fine` rendering. 25 | c_bg (tuple[float]): background color of training images. 26 | fine_network (bool): whether to use fine network. 27 | normalize_focals (bool): whether to hold focal length value 28 | as aspect to the image size. 29 | intrinsic_fixed (bool): whether to adjust intrinsic parameters 30 | with network training. 31 | extrinsic_fixed (bool): whether to adjust extrinsic parameters 32 | with network training. 33 | extrinsic_transf (torch.tensor): matrix for transform coord-systems, 34 | like right-handed to left-handed. 35 | barf (bool): whether to use coarse-to-fine registration in BARF. 36 | barf_start_epoch (int): start epoch of coarse-to-fine registration. 37 | barf_end_epoch (int): end epoch of coarse-to-fine registration 38 | nerfmm (bool): whether to joint-optimize with camera parameters. 39 | """ 40 | 41 | def __init__(self, 42 | dim_former=256, 43 | dim_latter=128, 44 | t_n=0., 45 | t_f=1., 46 | L_x=10, 47 | L_d=4, 48 | N_c=64, 49 | N_f=128, 50 | c_bg=None, 51 | fine_network=True, 52 | normalize_focals=False, 53 | intrinsic_fixed=True, 54 | extrinsic_fixed=True, 55 | extrinsic_transf=None, 56 | barf=False, 57 | barf_start_epoch=400, 58 | barf_end_epoch=800, 59 | nerfmm=False): 60 | 61 | if (not nerfmm) and (extrinsic_transf is not None): 62 | msg = '`extrinsic_transf` should be None when `nerfmm` is False' 63 | raise ValueError(msg) 64 | 65 | self.dim_former = dim_former 66 | self.dim_latter = dim_latter 67 | self.t_n = t_n 68 | self.t_f = t_f 69 | self.L_x = L_x 70 | self.L_d = L_d 71 | self.N_c = N_c 72 | self.N_f = N_f 73 | self.c_bg = c_bg 74 | self.fine_network = fine_network 75 | self.barf = barf 76 | self.barf_start_epoch = barf_start_epoch 77 | self.barf_end_epoch = barf_end_epoch 78 | 79 | self.normalize_focals = normalize_focals 80 | self.intrinsic_fixed = intrinsic_fixed 81 | 82 | self.nerfmm = nerfmm 83 | self.extrinsic_transf = extrinsic_transf 84 | self.extrinsic_fixed = extrinsic_fixed 85 | 86 | def nerf_kwargs(self): 87 | """get NeRF-class-initialization kwargs. 88 | """ 89 | 90 | return { 91 | "dim_former": self.dim_former, 92 | "dim_latter": self.dim_latter, 93 | "t_n": self.t_n, 94 | "t_f": self.t_f, 95 | "L_x": self.L_x, 96 | "L_d": self.L_d, 97 | "N_c": self.N_c, 98 | "N_f": self.N_f, 99 | "c_bg": self.c_bg, 100 | "fine_network": self.fine_network, 101 | "barf": self.barf, 102 | } 103 | 104 | def intrinsic_kwargs(self): 105 | """get Intrinsic-class-initialization kwargs. 106 | """ 107 | 108 | return { 109 | "normalize_focals": self.normalize_focals, 110 | "fixed": self.intrinsic_fixed, 111 | } 112 | 113 | def extrinsic_kwargs(self): 114 | """get Extrinsic-class-initialization kwargs. 115 | """ 116 | 117 | if self.nerfmm: 118 | extrinsic_kwargs = { 119 | "fixed": self.extrinsic_fixed, 120 | "transf": self.extrinsic_transf, 121 | } 122 | else: 123 | extrinsic_kwargs = { 124 | "fixed": self.extrinsic_fixed, 125 | } 126 | 127 | return extrinsic_kwargs 128 | 129 | 130 | class NeRF(nn.Module): 131 | """rendering class of Neural Radiance Fields 132 | 133 | Args: see the document of NeRFConfig class for more details. 134 | """ 135 | 136 | def __init__(self, 137 | dim_former=256, 138 | dim_latter=128, 139 | t_n=0., 140 | t_f=1., 141 | L_x=10, 142 | L_d=4, 143 | N_c=128, 144 | N_f=128, 145 | c_bg=(1., 1., 1.), 146 | barf=False, 147 | rf=RadianceField, 148 | fine_network=False): 149 | super().__init__() 150 | self.t_n = t_n 151 | self.t_f = t_f 152 | self.L_x = L_x 153 | self.L_d = L_d 154 | self.N_c = N_c 155 | self.N_f = N_f 156 | self.c_bg = c_bg 157 | 158 | if barf: 159 | self.register_buffer( 160 | "alpha", torch.tensor(0., dtype=torch.float32)) 161 | 162 | self.pe = position_encode_barf 163 | input_ch = 6 * L_x + 3 164 | middle_ch = 6 * L_d + 3 165 | else: 166 | self.pe = position_encode 167 | input_ch = 6 * L_x 168 | middle_ch = 6 * L_d 169 | 170 | self.rf_c = rf( 171 | input_ch=input_ch, middle_ch=middle_ch, 172 | dim_former=dim_former, dim_latter=dim_latter) 173 | 174 | if fine_network: 175 | self.rf_f = rf( 176 | input_ch=input_ch, middle_ch=middle_ch, 177 | dim_former=dim_former, dim_latter=dim_latter) 178 | 179 | self.fine_network = fine_network 180 | 181 | def _device(self): 182 | return next(self.parameters()).device 183 | 184 | def _rgb_and_weight(self, o, d, t, N, network): 185 | batch_size = o.shape[0] 186 | 187 | x = ray(o, d, t) 188 | x = x.view(batch_size, N, -1) 189 | d = d[:, None].repeat(1, N, 1) 190 | 191 | x = x.view(batch_size * N, -1) 192 | d = d.view(batch_size * N, -1) 193 | 194 | # forward. 195 | rgb, sigma = self.radiance_field(x, d, network=network) 196 | 197 | rgb = rgb.view(batch_size, N, -1) 198 | sigma = sigma.view(batch_size, N, -1) 199 | 200 | delta = F.pad(t[:, 1:] - t[:, :-1], (0, 1), mode='constant', value=1e8) 201 | mass = sigma[..., 0] * delta 202 | mass = F.pad(mass, (1, 0), mode='constant', value=0.) 203 | 204 | alpha = 1. - torch.exp(- mass[:, 1:]) 205 | T = torch.exp(- torch.cumsum(mass[:, :-1], dim=1)) 206 | w = T * alpha 207 | return rgb, w 208 | 209 | def forward(self, o, d, only_coarse=False): 210 | batch_size = o.shape[0] 211 | device = o.device 212 | 213 | partitions = split_ray(self.t_n, self.t_f, self.N_c, batch_size) 214 | _t_c = sample_coarse(partitions) 215 | t_c = torch.tensor(_t_c) 216 | t_c = t_c.to(device) 217 | 218 | rgb_c, w_c = self._rgb_and_weight( 219 | o, d, t_c, self.N_c, network="coarse") 220 | C_c = torch.sum(w_c[..., None]*rgb_c, dim=1) 221 | 222 | if self.c_bg is not None: 223 | bg = torch.tensor(self.c_bg, device=device, dtype=torch.float32) 224 | bg = bg.view(1, 3) 225 | C_c += (1. - torch.sum(w_c, axis=1, keepdims=True)) * bg 226 | 227 | if self.fine_network and (not only_coarse): 228 | _w_c = w_c.detach().cpu().numpy() 229 | t_f = sample_fine(partitions, _w_c, _t_c, self.N_f) 230 | t_f = torch.tensor(t_f) 231 | t_f = t_f.to(device) 232 | 233 | rgb_f, w_f = self._rgb_and_weight( 234 | o, d, t_f, self.N_f + self.N_c, network="fine") 235 | C_f = torch.sum(w_f[..., None]*rgb_f, dim=1) 236 | if self.c_bg is not None: 237 | C_f += (1. - torch.sum(w_f, axis=1, keepdims=True)) * bg 238 | 239 | output = [C_c, C_f] 240 | else: 241 | output = [C_c] 242 | 243 | return output 244 | 245 | def radiance_field(self, x, d, network="coarse"): 246 | if network == "coarse": 247 | rf = self.rf_c 248 | elif network == "fine": 249 | assert hasattr(self, "rf_f") 250 | rf = self.rf_f 251 | else: 252 | msg = "`network` must be `fine` or `coarse`." 253 | raise ValueError(msg) 254 | 255 | if hasattr(self, "alpha"): 256 | x = self.pe(x, self.L_x, self.alpha) 257 | d = self.pe(d, self.L_d, self.alpha) 258 | else: 259 | x = self.pe(x, self.L_x) 260 | d = self.pe(d, self.L_d) 261 | 262 | rgb, sigma = rf(x, d) 263 | 264 | return rgb, sigma 265 | 266 | 267 | class NeRFLoss(nn.Module): 268 | """Loss for NeRF training. 269 | 270 | Args: 271 | nerf (torch.nn.Module): nerf model. 272 | intrinsic (torch.nn.Module): intrinsic parameters. 273 | extrinsic (torch.nn.Module): extrinsic parameters. 274 | """ 275 | 276 | def __init__(self, nerf, intrinsic=None, extrinsic=None): 277 | super().__init__() 278 | self.nerf = nerf 279 | self.intrinsic = intrinsic 280 | self.extrinsic = extrinsic 281 | 282 | def forward(self, inputs): 283 | device = self.nerf._device() 284 | 285 | if (self.intrinsic is not None) and (self.extrinsic is not None): 286 | C, perm_in_img, img_ids = inputs 287 | _d = self.intrinsic.get_cam_pixels(device=device)[perm_in_img] 288 | o, d = self.extrinsic.cam2world(_d, img_ids) 289 | else: 290 | C, o, d = inputs 291 | o = torch.tensor(o, dtype=torch.float32, device=device) 292 | d = torch.tensor(d, dtype=torch.float32, device=device) 293 | 294 | C = torch.tensor(C, dtype=torch.float32, device=device) 295 | 296 | output = self.nerf(o, d) 297 | 298 | loss = 0. 299 | for C_pred in output: 300 | loss += F.mse_loss(C_pred, C) 301 | return loss 302 | -------------------------------------------------------------------------------- /src/nerf_tutorial/nerf_utils.py: -------------------------------------------------------------------------------- 1 | # codes are almost from https://github.com/ALBERT-Inc/blog_nerf/blob/master/NeRF.ipynb 2 | 3 | import math 4 | import torch 5 | import numpy as np 6 | import pandas as pd 7 | from pyntcloud import PyntCloud 8 | 9 | 10 | def camera_parameters_to_rays( 11 | w, h, cx, cy, fx, fy, pose, device=None, **kwargs): 12 | """transform image pixels to camera origins and rays. 13 | this function do the same process 14 | with Intrinsic class and Extrinsic class. 15 | 16 | Args: 17 | w (int): width of rendering image. 18 | h (int): height of rendering image. 19 | cx (float): x value of image center for rendering. 20 | cx (float): y value of image center for rendering. 21 | fx (float): x value of focal length for rendering. 22 | fx (float): y value of focal length for rendering. 23 | pose (torch.tensor): pose matrix for rendering. 24 | this takes (1, 4, 4) shape. 25 | Returns: 26 | o (torch.tensor): camera origins in world coordinate. 27 | this takes (W*H, 3) shape. 28 | d (torch.tensor): camera directions in world coordinate. 29 | this takes (W*H, 3) shape. 30 | """ 31 | 32 | # intrinsic part 33 | v, u = np.mgrid[:h, :w].astype(np.float32) 34 | u = torch.tensor(u, dtype=torch.float32, device=device) 35 | v = torch.tensor(v, dtype=torch.float32, device=device) 36 | 37 | _x = (u - cx) / fx 38 | _y = (v - cy) / fy 39 | _z = torch.ones_like(_x, device=device) 40 | _w = torch.ones_like(_x, device=device) 41 | 42 | xyzw = torch.stack([_x, _y, _z, _w], dim=2) 43 | xyzw = xyzw.reshape(-1, 4) 44 | 45 | # extrinsic part 46 | _o = np.zeros((len(xyzw), 4), dtype=np.float32) 47 | _o[:, 3] = 1. 48 | _o = torch.tensor(_o, dtype=torch.float32, device=device) 49 | 50 | pose = pose.repeat(len(xyzw), 1, 1) 51 | d = torch.bmm(pose, xyzw[..., None])[:, :, 0][:, :3] 52 | o = torch.bmm(pose, _o[..., None])[:, :, 0][:, :3] 53 | d = d - o 54 | d = d / torch.norm(d, dim=1, keepdim=True) 55 | return o, d 56 | 57 | 58 | @torch.no_grad() 59 | def render_nerf( 60 | nerf, camera_parameters, bsz_eval=1024, only_coarse=False): 61 | """rendering function with nerf. 62 | 63 | Args: 64 | nerf (torch.nn.Module): nerf model. 65 | camera_parameters (dict): dictionary of camera parameters. 66 | bsz_eval (int): batch size for nerf inference. 67 | only_coarse (bool): only_coarse (bool): whether to use 68 | coarse network for rendering. 69 | Returns: 70 | Cs (numpy.array): rendered image array in (H, W, 3) shape. 71 | """ 72 | 73 | Cs = [] 74 | 75 | nerf.eval() 76 | o, d = camera_parameters_to_rays(**camera_parameters) 77 | 78 | num_data = len(o) 79 | if num_data % bsz_eval == 0: 80 | num_iter = num_data // bsz_eval 81 | else: 82 | num_iter = num_data // bsz_eval + 1 83 | 84 | for i in range(num_iter): 85 | start = i * bsz_eval 86 | end = min((i+1)*bsz_eval, num_data) 87 | 88 | C = nerf( 89 | o[start:end], d[start:end], only_coarse=only_coarse)[-1] 90 | Cs.append(C) 91 | 92 | Cs = torch.clamp(torch.cat(Cs), 0., 1.) 93 | Cs = Cs.reshape( 94 | camera_parameters["h"], camera_parameters["w"], 3) 95 | return Cs.cpu().detach().numpy() 96 | 97 | 98 | def _gen_3d_grid(size, xlim=(-1, 1), ylim=(-1, 1), zlim=(-1, 1)): 99 | x_num, y_num, z_num = size 100 | z, y, x = torch.meshgrid( 101 | torch.linspace(-1, 1, z_num, dtype=torch.float32), 102 | torch.linspace(-1, 1, y_num, dtype=torch.float32), 103 | torch.linspace(-1, 1, x_num, dtype=torch.float32), 104 | ) 105 | grid = torch.stack([z, y, x], dim=3) 106 | return grid 107 | 108 | 109 | @torch.no_grad() 110 | def extract_pointcloud( 111 | nerf, num_grid_edge=100, bsz_eval=1024, 112 | sigma_threshold=5.0, device=None): 113 | """extract point cloud in PyntCloud format from nerf. 114 | 115 | Args: 116 | nerf (torch.nn.Module): nerf model. 117 | num_grid_edge (int): the number of points in a grid edge. 118 | bsz_eval (int): batch size for nerf inference. 119 | sigma_threshold (float): threshold for density screening. 120 | device (torch.device): device to use in inference. 121 | Returns: 122 | cloud (pyntcloud.PyntCloud): pointcloud extracted. 123 | """ 124 | 125 | grid = _gen_3d_grid( 126 | (num_grid_edge, num_grid_edge, num_grid_edge)).reshape(-1, 3) 127 | 128 | grid_size = len(grid) 129 | if grid_size % bsz_eval == 0: 130 | iter_num = grid_size // bsz_eval 131 | else: 132 | iter_num = grid_size // bsz_eval + 1 133 | 134 | sigma = [] 135 | color = [] 136 | nerf.eval() 137 | for i in range(iter_num): 138 | start = i * bsz_eval 139 | end = min((i+1)*bsz_eval, grid_size) 140 | 141 | x_batch = grid[start:end].to(device) 142 | d_batch = - torch.ones_like(x_batch) / np.sqrt(3) 143 | d_batch[:, 1] = 0. 144 | c_batch, sigma_batch = \ 145 | nerf.radiance_field(x_batch, d_batch, network="fine") 146 | sigma.append(sigma_batch.cpu().detach().numpy()) 147 | color.append(c_batch.cpu().detach().numpy()) 148 | 149 | sigma = np.concatenate(sigma) 150 | color = np.concatenate(color) 151 | 152 | cond = (sigma >= sigma_threshold)[:, 0] 153 | _g = grid[cond].numpy() 154 | _c = color[cond] * 255. 155 | cloud = PyntCloud( 156 | pd.DataFrame(np.concatenate([_g, _c], axis=1), 157 | columns=['x', 'y', 'z', 'red', 'green', 'blue']) 158 | ) 159 | cloud.points[['red', 'green', 'blue']] = \ 160 | cloud.points[['red', 'green', 'blue']].astype(np.uint8) 161 | return cloud 162 | 163 | 164 | def position_encode(p, L): 165 | """Encode positions. 166 | Args: 167 | p (ndarray, [batch_size, dim]): Position. 168 | L (int): encoding param. 169 | Returns: 170 | ndarray [batch_size, dim * L]: Encoded position. 171 | """ 172 | # normalization. 173 | p = torch.tanh(p) 174 | 175 | batch_size = p.shape[0] 176 | i = torch.arange(L, dtype=torch.float32, device=p.device) 177 | a = (2. ** i[None, None]) * math.pi * p[:, :, None] 178 | s = torch.sin(a) 179 | c = torch.cos(a) 180 | e = torch.cat([s, c], axis=2).view(batch_size, -1) 181 | return e 182 | 183 | 184 | def position_encode_barf(p, L, alpha=0.): 185 | """Encode positions (BARF version). 186 | Args: 187 | p (ndarray, [batch_size, dim]): Position. 188 | L (int): encoding param. 189 | alpha (float): hy-pass rate. 190 | Returns: 191 | ndarray [batch_size, dim + dim * L]: Encoded position. 192 | """ 193 | # normalization. 194 | p = torch.tanh(p) 195 | alpha = L * alpha 196 | 197 | batch_size = p.shape[0] 198 | i = torch.arange(L, dtype=torch.float32, device=p.device) 199 | 200 | filter_mask = (alpha < i).repeat(2) 201 | all_path_mask = (alpha - i >= 1.).repeat(2) 202 | part_path_mask = filter_mask == all_path_mask 203 | w_part = (1 - torch.cos((alpha-i) * math.pi)) * 0.5 204 | w_part = w_part.repeat(2) 205 | 206 | a = (2. ** i[None, None]) * math.pi * p[:, :, None] 207 | s = torch.sin(a) 208 | c = torch.cos(a) 209 | e = torch.cat([s, c], axis=2) 210 | e = torch.where(filter_mask, torch.zeros_like(e), e) 211 | e = torch.where(part_path_mask, e*w_part, e) 212 | e = torch.cat([p, e.reshape(batch_size, -1)], axis=1) 213 | return e 214 | 215 | 216 | def split_ray(t_n, t_f, N, batch_size): 217 | """Split the ray into N partitions. 218 | partition: [t_n, t_n + (1 / N) * (t_f - t_n), ..., t_f] 219 | Args: 220 | t_n (float): t_near. Start point of split. 221 | t_f (float): t_far. End point of split. 222 | N (int): Num of partitions. 223 | batch_size (int): Batch size. 224 | Returns: 225 | ndarray, [batch_size, N]: A partition. 226 | """ 227 | partitions = np.linspace(t_n, t_f, N+1, dtype=np.float32) 228 | return np.repeat(partitions[None], repeats=batch_size, axis=0) 229 | 230 | 231 | def sample_coarse(partitions): 232 | """Sample ``t_i`` from partitions for ``coarse`` network. 233 | t_i ~ U[t_n + ((i - 1) / N) * (t_f - t_n), t_n + (i / N) * (t_f - t_n)] 234 | Args: 235 | partitions (ndarray, [batch_size, N+1]): Outputs of ``split_ray``. 236 | Return: 237 | ndarray, [batch_size, N]: Sampled t. 238 | """ 239 | t = np.random.uniform( 240 | partitions[:, :-1], partitions[:, 1:]).astype(np.float32) 241 | return t 242 | 243 | 244 | def _pcpdf(partitions, weights, N_s): 245 | """Sample from piecewise-constant probability density function. 246 | Args: 247 | partitions (ndarray, [batch_size, N_p+1]): N_p Partitions. 248 | weights (ndarray, [batch_size, N_p]): The ratio of sampling from each 249 | partition. 250 | N_s (int): Num of samples. 251 | Returns: 252 | numpy.ndarray, [batch_size, N_s]: Samples. 253 | """ 254 | batch_size, N_p = weights.shape 255 | 256 | # normalize weights. 257 | weights[weights < 1e-16] = 1e-16 258 | weights /= weights.sum(axis=1, keepdims=True) 259 | 260 | _sample = np.random.uniform( 261 | 0, 1, size=(batch_size, N_s)).astype(np.float32) 262 | _sample = np.sort(_sample, axis=1) 263 | 264 | # Slopes of a piecewise linear function. 265 | a = (partitions[:, 1:] - partitions[:, :-1]) / weights 266 | 267 | # Intercepts of a piecewise linear function. 268 | cum_weights = np.cumsum(weights, axis=1) 269 | cum_weights = np.pad(cum_weights, ((0, 0), (1, 0)), 270 | mode='constant') 271 | b = partitions[:, :-1] - a * cum_weights[:, :-1] 272 | 273 | sample = np.zeros_like(_sample) 274 | for j in range(N_p): 275 | min_j = cum_weights[:, j:j+1] 276 | max_j = cum_weights[:, j+1:j+2] 277 | a_j = a[:, j:j+1] 278 | b_j = b[:, j:j+1] 279 | mask = ((min_j <= _sample) & (_sample < max_j)).astype(np.float32) 280 | sample += (a_j * _sample + b_j) * mask 281 | 282 | return sample 283 | 284 | 285 | def sample_fine(partitions, weights, t_c, N_f): 286 | """Sample ``t_i`` from partitions for ``fine`` network. 287 | Sampling from each partition according to given weights. 288 | Args: 289 | partitions (ndarray, [batch_size, N_c+1]): Outputs of ``split_ray``. 290 | weights (ndarray, [batch_size, N_c]): 291 | T_i * (1 - exp(- sigma_i * delta_i)). 292 | t_c (ndarray, [batch_size, N_c]): ``t`` of coarse rendering. 293 | N_f (int): num of sampling. 294 | Return: 295 | ndarray, [batch_size, N_c+N_f]: Sampled t. 296 | """ 297 | t_f = _pcpdf(partitions, weights, N_f) 298 | t = np.concatenate([t_c, t_f], axis=1) 299 | t = np.sort(t, axis=1) 300 | return t 301 | 302 | 303 | def ray(o, d, t): 304 | """Returns points on the ray. 305 | Args: 306 | o (ndarray, [batch_size, 3]): Start points of the ray. 307 | d (ndarray, [batch_size, 3]): Directions of the ray. 308 | t (ndarray, [batch_size, N]): Sampled t. 309 | Returns: 310 | ndarray, [batch_size, N, 3]: Points on the ray. 311 | """ 312 | return o[:, None] + t[..., None] * d[:, None] 313 | -------------------------------------------------------------------------------- /src/nerf_tutorial/radiance_field.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/ALBERT-Inc/blog_nerf/blob/master/NeRF.ipynb 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | 7 | 8 | def _init_weights(m): 9 | if type(m) == nn.Linear: 10 | nn.init.kaiming_normal_(m.weight) 11 | nn.init.zeros_(m.bias) 12 | 13 | 14 | class RadianceField(nn.Module): 15 | """Radiance Field Functions. 16 | This is ``F_Theta`` in the paper. 17 | """ 18 | 19 | def __init__(self, input_ch, middle_ch, dim_former=256, dim_latter=128): 20 | super(RadianceField, self).__init__() 21 | self.layer0 = nn.Linear(input_ch, dim_former) 22 | self.layer1 = nn.Linear(dim_former, dim_former) 23 | self.layer2 = nn.Linear(dim_former, dim_former) 24 | self.layer3 = nn.Linear(dim_former, dim_former) 25 | self.layer4 = nn.Linear(dim_former, dim_former) 26 | self.layer5 = nn.Linear(dim_former+input_ch, dim_former) 27 | self.layer6 = nn.Linear(dim_former, dim_former) 28 | self.layer7 = nn.Linear(dim_former, dim_former) 29 | self.sigma = nn.Linear(dim_former, 1) 30 | self.layer8 = nn.Linear(dim_former, dim_former) 31 | self.layer9 = nn.Linear(dim_former+middle_ch, dim_latter) 32 | self.layer10 = nn.Linear(dim_latter, dim_latter) 33 | self.layer11 = nn.Linear(dim_latter, dim_latter) 34 | self.layer12 = nn.Linear(dim_latter, dim_latter) 35 | self.rgb = nn.Linear(dim_latter, 3) 36 | 37 | self.apply(_init_weights) 38 | 39 | def forward(self, x, d): 40 | """Apply function. 41 | Args: 42 | x (tensor, [batch_size, 3]): Points on rays. 43 | d (tensor, [batch_size, 3]): Direction of rays. 44 | Returns: 45 | rgb (tensor, [batch_size, 3]): Emitted color. 46 | sigma (tensor, [batch_size, 1]): Volume density. 47 | """ 48 | 49 | # forward 50 | h = F.relu(self.layer0(x)) 51 | h = F.relu(self.layer1(h)) 52 | h = F.relu(self.layer2(h)) 53 | h = F.relu(self.layer3(h)) 54 | h = F.relu(self.layer4(h)) 55 | h = torch.cat([h, x], axis=1) 56 | h = F.relu(self.layer5(h)) 57 | h = F.relu(self.layer6(h)) 58 | h = F.relu(self.layer7(h)) 59 | sigma = F.softplus(self.sigma(h)) 60 | h = self.layer8(h) 61 | h = torch.cat([h, d], axis=1) 62 | h = F.relu(self.layer9(h)) 63 | h = F.relu(self.layer10(h)) 64 | h = F.relu(self.layer11(h)) 65 | h = F.relu(self.layer12(h)) 66 | rgb = torch.sigmoid(self.rgb(h)) 67 | 68 | return rgb, sigma 69 | -------------------------------------------------------------------------------- /src/nerf_tutorial/visualization_utils.py: -------------------------------------------------------------------------------- 1 | import io 2 | import math 3 | import torch 4 | import numpy as np 5 | import ipywidgets as widgets 6 | import matplotlib.pyplot as plt 7 | 8 | from PIL import Image 9 | from functools import partial 10 | from .nerf_utils import render_nerf 11 | from scipy.spatial.transform import Rotation as R 12 | 13 | 14 | def plot_cameras(ax, poses, f, c, screen_size, label, 15 | alpha=0.3, c_cam_point="#00aa00", c_cam_line="#0a0000"): 16 | """plot cameras in the world-coordinate system respect to the poses. 17 | 18 | Args: 19 | ax (matplotlib.Axes): matplotlib axes to plot. 20 | poses (numpy.array): camera poses with (N, 4, 4) shape. 21 | f (list[float]): focal lengths in [fx, fy] style. 22 | c (list[float]): image center coordinates in [cx, cy] style. 23 | screen_size (list[float]): image size in [W, H] style. 24 | label (str): label of plotted lines. 25 | alpha (float): alpha value of plots. 26 | c_cam_point (str): color code of camera point. 27 | c_cam_line (str): color code of camera trajectory. 28 | """ 29 | 30 | W, H = screen_size 31 | 32 | # make grid 33 | v, u = torch.meshgrid(torch.arange(800), torch.arange(1200)) 34 | u = u.to(torch.float32) 35 | v = v.to(torch.float32) 36 | 37 | _x = (u - c[0]*0.5) / f[0] * 0.5 38 | _y = (v - c[1]*0.5) / f[1] * 0.5 39 | _z = torch.ones_like(u) * 1 40 | xyz = torch.stack([_x, _y, _z], dim=2) 41 | 42 | # get corner of screen in camera-coordinates 43 | lxty = xyz[0, 0] 44 | lxby = xyz[-1, 0] 45 | rxty = xyz[0, -1] 46 | rxby = xyz[-1, -1] 47 | scr_coords = torch.stack([lxty, lxby, rxty, rxby], axis=0) 48 | 49 | scr_coords_w = [] 50 | for p in torch.tensor(poses): 51 | t = p[:, -1][None][:, :3] 52 | r = p[:, :-1][None].repeat(len(scr_coords), 1, 1) 53 | r = r.permute(0, 2, 1)[:, :, :3] 54 | 55 | p = torch.bmm(scr_coords[:, None], r)[:, 0] 56 | p = p + t 57 | 58 | scr_coords_w.append(p) 59 | 60 | to_lxty = torch.stack([t[0], p[0]], dim=0).T 61 | to_lxby = torch.stack([t[0], p[1]], dim=0).T 62 | to_rxty = torch.stack([t[0], p[2]], dim=0).T 63 | to_rxby = torch.stack([t[0], p[3]], dim=0).T 64 | scr_square = p[[0, 1, 3, 2, 0]].T 65 | 66 | ax.scatter( 67 | t[0][0], t[0][1], t[0][2], 68 | color=c_cam_point, s=30, alpha=1. 69 | ) 70 | ax.plot( 71 | to_lxty[0], to_lxty[1], to_lxty[2], 72 | color=c_cam_point, alpha=alpha 73 | ) 74 | ax.plot( 75 | to_lxby[0], to_lxby[1], to_lxby[2], 76 | color=c_cam_point, alpha=alpha 77 | ) 78 | ax.plot( 79 | to_rxty[0], to_rxty[1], to_rxty[2], 80 | color=c_cam_point, alpha=alpha 81 | ) 82 | ax.plot( 83 | to_rxby[0], to_rxby[1], to_rxby[2], 84 | color=c_cam_point, alpha=alpha 85 | ) 86 | line, = ax.plot( 87 | scr_square[0], scr_square[1], scr_square[2], 88 | color=c_cam_line, alpha=alpha 89 | ) 90 | 91 | line.set_label(label) 92 | 93 | 94 | class IpywidgetsRenderer(object): 95 | """NeRF Rendering class with Ipywidgets. 96 | 97 | Args: 98 | nerf (torch.nn.Module): nerf model. 99 | camera_parameters (dict): dict of camera parameters. 100 | value (float): move/rotate value. 101 | only_coarse (bool): whether to use 102 | coarse network for rendering. 103 | """ 104 | 105 | def __init__(self, nerf, camera_parameters, 106 | value=0.1, only_coarse=False): 107 | self.nerf = nerf 108 | self.camera_parameters = camera_parameters 109 | self.value = value 110 | self.only_coarse = only_coarse 111 | self.movedirs2positions = { 112 | "UP": "header1", 113 | "FORWARD": "header2", 114 | "LEFT": "left", 115 | "RIGHT": "right", 116 | "DOWN": "footer1", 117 | "BACKWARD": "footer2", 118 | } 119 | self.rotdirs2positions = { 120 | "UP": "header", 121 | "LEFT": "left", 122 | "RIGHT": "right", 123 | "DOWN": "footer", 124 | } 125 | 126 | move_items, rotate_items = [], [] 127 | for k, v in self.movedirs2positions.items(): 128 | move_button = widgets.Button( 129 | description='Move {}'.format(k), 130 | layout=widgets.Layout(width='auto', grid_area=v) 131 | ) 132 | move_button.on_click(partial(self._move_button_clicked, k)) 133 | move_items.append(move_button) 134 | 135 | for k, v in self.rotdirs2positions.items(): 136 | rotate_button = widgets.Button( 137 | description='Rotate {}'.format(k), 138 | layout=widgets.Layout(width='auto', grid_area=v) 139 | ) 140 | rotate_button.on_click(partial(self._rotate_button_clicked, k)) 141 | rotate_items.append(rotate_button) 142 | 143 | m_controller_layout = widgets.Layout( 144 | width='35%', 145 | grid_template_rows='auto auto auto', 146 | grid_template_columns='50% 50%', 147 | grid_template_areas=''' 148 | "header1 header2" 149 | "left right" 150 | "footer1 footer2" 151 | ''' 152 | ) 153 | r_controller_layout = widgets.Layout( 154 | width='35%', 155 | grid_template_rows='auto auto auto', 156 | grid_template_columns='50% 50%', 157 | grid_template_areas=''' 158 | "header header" 159 | "left right" 160 | "footer footer" 161 | ''' 162 | ) 163 | self.image = widgets.Image( 164 | value=self._render_nerf( 165 | self.nerf, self.camera_parameters, 166 | only_coarse=self.only_coarse), 167 | format='png', 168 | width='70%' 169 | ) 170 | self.controller = widgets.HBox([ 171 | widgets.GridBox(children=move_items, layout=m_controller_layout), 172 | widgets.GridBox(children=rotate_items, layout=r_controller_layout) 173 | ]) 174 | viewer = widgets.VBox([self.image, self.controller]) 175 | display(viewer) 176 | 177 | def _render_nerf(self, nerf, camera_parameters, only_coarse=False): 178 | Cs = render_nerf(nerf, camera_parameters, only_coarse=only_coarse) 179 | img = Image.fromarray((Cs*255).astype(np.uint8)) 180 | img_bytes = io.BytesIO() 181 | img.save(img_bytes, format='PNG') 182 | img_bytes = img_bytes.getvalue() 183 | return img_bytes 184 | 185 | def _move_button_clicked(self, direction, e): 186 | device = self.camera_parameters["device"] 187 | move_value = self.value 188 | 189 | if direction == "UP": 190 | move_vec = torch.tensor( 191 | [0, 1, 0, 1], dtype=torch.float32, device=device) 192 | move_value *= -1. 193 | elif direction == "LEFT": 194 | move_vec = torch.tensor( 195 | [1, 0, 0, 1], dtype=torch.float32, device=device) 196 | move_value *= -1. 197 | elif direction == "RIGHT": 198 | move_vec = torch.tensor( 199 | [1, 0, 0, 1], dtype=torch.float32, device=device) 200 | elif direction == "DOWN": 201 | move_vec = torch.tensor( 202 | [0, 1, 0, 1], dtype=torch.float32, device=device) 203 | elif direction == "FORWARD": 204 | move_vec = torch.tensor( 205 | [0, 0, 1, 1], dtype=torch.float32, device=device) 206 | elif direction == "BACKWARD": 207 | move_vec = torch.tensor( 208 | [0, 0, 1, 1], dtype=torch.float32, device=device) 209 | move_value *= -1. 210 | else: 211 | raise ValueError("move direction is invalid") 212 | 213 | o_vec = torch.tensor( 214 | [0, 0, 0, 1], dtype=torch.float32, device=device) 215 | vecs = torch.stack([move_vec, o_vec], dim=0) 216 | pose = self.camera_parameters["pose"].repeat(len(vecs), 1, 1) 217 | _ds = torch.bmm(pose, vecs[..., None])[:, :, 0][:, :3] 218 | d = _ds[0] - _ds[1] 219 | d = d / torch.norm(d) 220 | 221 | move_matrix = torch.eye(4, device=device) 222 | move_matrix[:3, 3] = d * move_value 223 | self.camera_parameters["pose"] = \ 224 | torch.bmm(move_matrix[None], self.camera_parameters["pose"]) 225 | self.image.value = \ 226 | self._render_nerf( 227 | self.nerf, self.camera_parameters, 228 | only_coarse=self.only_coarse) 229 | 230 | def _rotate_button_clicked(self, direction, e): 231 | device = self.camera_parameters["device"] 232 | 233 | rot_value = self.value 234 | if direction == "UP": 235 | r_axis = torch.tensor( 236 | [1, 0, 0, 1], dtype=torch.float32, device=device) 237 | elif direction == "LEFT": 238 | r_axis = torch.tensor( 239 | [0, 1, 0, 1], dtype=torch.float32, device=device) 240 | rot_value *= -1. 241 | elif direction == "RIGHT": 242 | r_axis = torch.tensor( 243 | [0, 1, 0, 1], dtype=torch.float32, device=device) 244 | elif direction == "DOWN": 245 | r_axis = torch.tensor( 246 | [1, 0, 0, 1], dtype=torch.float32, device=device) 247 | rot_value *= -1. 248 | else: 249 | raise ValueError("move direction is invalid") 250 | 251 | o_vec = torch.tensor( 252 | [0, 0, 0, 1], dtype=torch.float32, device=device) 253 | vecs = torch.stack([r_axis, o_vec], dim=0) 254 | pose = self.camera_parameters["pose"].repeat(len(vecs), 1, 1) 255 | _ds = torch.bmm(pose, vecs[..., None])[:, :, 0][:, :3] 256 | 257 | r_axis = _ds[0] - _ds[1] 258 | r_axis = r_axis / torch.norm(r_axis) 259 | r_axis = r_axis.detach().cpu().numpy() 260 | 261 | rot_matrix = torch.eye(4, device=device) 262 | rot_matrix[:3, :3] = torch.tensor( 263 | R.from_rotvec(math.pi * rot_value * r_axis).as_matrix(), 264 | device=device 265 | ) 266 | self.camera_parameters["pose"][0, :3, :3] = \ 267 | torch.bmm( 268 | rot_matrix[None], 269 | self.camera_parameters["pose"] 270 | )[0, :3, :3] 271 | self.image.value = \ 272 | self._render_nerf( 273 | self.nerf, self.camera_parameters, 274 | only_coarse=self.only_coarse) 275 | --------------------------------------------------------------------------------