MEMOcho- (original) (raw)

Era3D: High-Resolution Multiview Diffusion using Efficient Row-wise Attention

multiview diffusionは1 viewあたりの解像度が256x256のものが多く、それだと顔などの細かい部分が描けないという問題がある。era3dは1 viewあたり512で生成できるようで気になったので調べてみる。

era3dの目的

camera条件の推定と固定

問題意識としては、既存のmultiview diffusionでは学習時/推論時のref imageのcamera条件が一致していないと歪んだ画像が生成されること。

学習時に固定(や狭い範囲)のfocalやcamera/objectのdistance、elevationなどでrenderingした画像を使っているためで、当然その範囲を外れた画像をref imageに使う場合は推論がうまくいかなくなる。

より広い条件の学習データを使って学習すればOKという簡単な問題でもなく、範囲を広くすれば学習自体が難しくなってしまう。既存の方法では暗黙的な形で以下のような学習をmodelが行っていることになる

そもそも一枚の画像からそのcamera条件を推定するのは難しい問題であり、2つ目の角度が異なる画像を生成するだけでも難しいのに余計に負荷のかかる状況となっている。

なのでこれを解決するために、ref imageのelevationとfocalを予測するmoduleを追加して補助し、生成される画像のcamera条件は固定にすることで学習難易度を下げて生成自体に集中できるようにする。

常にelevation=0としてmultiviewを生成するよう学習する

the input images are allowed to have arbitrary focal lengths and elevations while generated images are with orthogonal cameras and fixed viewpoints of 0 elevations.

azimuth(水平方向のcamera角度)も任意の値で条件づけできるわけではなく、ref imageから[0, 45, 90, -45, -90, 180]分回転した角度の6 viewを生成

when processing an input image captured at elevation α and azimuth β, Era3D produces a set of multiview images at an azimuth of {β, β + 45, β + 90, β − 45, β − 90, β + 180} and an elevation of 0.

epipolar attentionの計算量低減

elevation=0の画像のみを生成する形に固定したことで、epipolar attentionの範囲を限定して計算量を下げる。

そもそもepipolar lineとは
エピポーラ幾何 - Wikipedia

wikipediaより引用

言葉で書くとわかりづらいが、3d空間上でカメラAとオブジェクトXを通る直接を、カメラBが見る視界(2d)上にレンダリングした際のAX、のこと。さらに詳しい解説はこちらのqiitaなど
【コンピュータビジョン】ネコと学ぶエピポーラ幾何 #Python - Qiita

ということで、2つの視点から同じ物体を撮った画像があった場合、片方の画像上のある点Xは他方の画像上のepipolar line上に存在する。

なぜ計算量が下がるのか

epipolar attentionはこちらで提案されたもののよう
[2303.17598] Consistent View Synthesis with Pose-Guided Diffusion Models

詳細は読んでないものの、attentionする範囲をepipolar lineに限定して計算効率や対応点の予測精度を上げてmultiviewの一貫性を向上させるものだと考えられる。

era3dでは以下の理由により2 view間のepipolar lineは同じrowにのみ存在するので、さらにattentionの計算量が減らせる

perspectiveとorthogonal cameraについて床井研究室 - 第5回 座標変換

正しく理解できたかはわからないが、orthogonalの場合以下のようになると考えられる

era3dの論文より引用

Kは斜めになるので少なくともSより大きな値が入ることは確実だが、対角線となる場合が最大という認識でいいのだろうか(それだと1.4倍程度しか変わらないのでもっと別の意味の値かも?)

resource削減具合

xformerを使う場合の比較で解像度256 -> 512における違いは

と解像度が上がってもメモリ消費がほぼ変わらず、512における処理時間がかなり短縮されているのがわかりやすい。

気になる点としては、入力としてorthogonal cameraによるレンダリング画像を使う必要がある点。遠近感がそれほどない画像であれば通常のカメラで撮影した画像でもある程度問題ないのだろうか。

生成してみる

以前も使ったunitychanの正面画像を使って生成してみた斜め画像がこちら
TADAをimage-3dへ拡張 - MEMOcho-

違和感なく斜めの画像が生成できているように見える。normalも生成できるよう学習されているのは3d生成に使う上ではよい。

生成されるviewはinputとして与えた画像(から推定したcamera pose)を正面として前後左右4 view + 前からの斜め2 viewと固定なので、single imageを与える問題設定でreconstructすると複雑なobjectでは厳しいかもしれない。というのとrendering側もorthogonal cameraを使う必要があるのを忘れそう。

ただ、input imageにおけるcameraのelevationやdistanceの条件を合わせなくても、model側で予測して決まったviewを出してくれるというのは、様々な条件の入力に対してうまくreconstructするには重要だと思われる。

ModuleNotFoundError: No module named 'torchvision.transforms.functional_tensor'

というエラーになるので調べてみるとだいぶ前からissueになってる様子

[BREAKING FIX] [torchvision 0.17] Change functional_tensor import by TornjV · Pull Request #650 · XPixelGroup/BasicSR · GitHub

issueはcloseとなっているが、PRを見るとopenedのままで、どうやらbackward compatibilityのためのちょっとした修正が反映されてなくてそのままっぽい

Fix import error for rgb_to_grayscale by sipsmehta · Pull Request #677 · XPixelGroup/BasicSR · GitHub

ただ、これだけなら別PRにして入れればよいだけなので何か別の問題があるのかもしれない

ひとまず個人的に使いたいのでforkしてこの修正だけ最新リリースの1.4.2に加えたブランチを作って使うことにした

https://github.com/y-kamiya/BasicSR/tree/fix/latest-torchvision

pip install git+https://github.com/y-kamiya/BasicSR.git@fix/latest-torchvision

でOK

TADAは元々text-3dの手法でimage条件付けの実装は含まれていない。 しかし、それだとstable diffusionが知ってるキャラクターしか作れないため、画像からavatar生成ができるよう実装を加えてみたのでメモ。

guidanceの部分でreference imageを元にした生成ができれば元と同じようにsdsを用いて3d生成が可能と考えられる。reference imageを元にした画像生成はいろいろ方法があるが、今回はimage条件付け可能なmultivew diffusionであるImageDreamを利用することにした。

ImageDreamについてはこちらに書いた
ImageDreamの調査 - MEMOcho-

実装調査

ImageDreamはthreestudio上に実装されており、guidance部分はここ

https://github.com/bytedance/ImageDream/blob/26c3972/threestudio/models/guidance/multiview_diffusion_guidance.py

ImageDreamもsdsによって3d生成する形なので、基本的にはこれをまるごと使うことが可能なはず。ただしmultiview diffusionであり、diffusionの推論を行う際にはImageDreamの生成条件に合わせたcamera poseの情報がcontextとして必要となる。

そして、camera poseを作っているのがこちら

https://github.com/bytedance/ImageDream/blob/26c3972e586f0c8d2f6c6b297aa9d792d06abebb/threestudio/data/random_multiview.py#L38

datasetとしてcamera poseを作り、その条件で3d表現からのレンダリングを実行、レンダリングした画像とそのcamera poseをguidanceのinputに与えてsdsという流れ。

TADA側も流れとしては同様になっている。ということで、datasetの部分もImageDreamのものに差し替えてしまうのが最も手間がかからなさそう。

実装

座標系合わせ

ImageDream側のdatasetとguidance用のclassを、TADAの学習時の処理から使うようにしてみたところ、レンダリング結果がこんな状態になる

これは座標系の問題

具体的にはelevation/azimuthの設定からcamera位置を決める部分の計算が異なる。それぞれの定義部分
https://github.com/TingtingLiao/TADA/blob/e8e39c048a69da9c63413643ff2af5caef845a4f/lib/provider.py#L214-L218 https://github.com/bytedance/ImageDream/blob/26c3972e586f0c8d2f6c6b297aa9d792d06abebb/threestudio/data/random_multiview.py#L118-L128

ちなみにopenglblender座標系の違いを表にまとめていてわかりやすかった

BlenderとUnityの座標系の違いを治す|KitanoMinami

角度のズレ

座標系は合わせることができたが、以下の2つの画像が水平方向に90度ズレる

レンダリングや生成条件として渡すcamera poseは当然座標系をあわせたものを使っている。

もう一度↑に貼ったcamera位置の定義を見てみる

TADA

centers = torch.stack([ radius * torch.sin(thetas) * torch.sin(phis), radius * torch.cos(thetas), radius * torch.sin(thetas) * torch.cos(phis), ], dim=-1) + shift # [B, 3]

ImageDream

camera_positions: Float[Tensor, "B 3"] = torch.stack( [ camera_distances * torch.cos(elevation) * torch.cos(azimuth), camera_distances * torch.cos(elevation) * torch.sin(azimuth), camera_distances * torch.sin(elevation), ], dim=-1, ) + shift

水平方向の角度はそれぞれphis, azimuthであり、これを0にして考えてみるとTADAはz軸、ImageDreamはx軸が基準となっていることがわかる。

それぞれのコードベースはこれを前提に処理しているはずなので同じcamera poseを与えれば、TADAのレンダリングはz軸からの角度、ImageDreamであればx軸からの角度として扱うために90度ズレる。

ちなみにelevationの角度の基準も違っており、片方は水平面から、他方は頭頂方向から見た角度で設定されている点も異なる。ただ、こちらの場合は設定値もこれに合わせて対応した値が設定される前提になっていたため問題なかった。

座標系合わせと角度ズレの修正部分のみならこれだけ

https://github.com/y-kamiya/TADA/commit/a3bc9dcdc49e0d75b9066627b28d0d3cb05a7c3f

https://github.com/y-kamiya/ImageDream/commit/0855cbbed409a77edbbc3009adef3270e2591dfc

multiview diffusionの条件づけに使われるのはc2wレンダリングの条件に使われるのはmvpなので、TADA側の処理で使われるmvpのみ-90度回転させた。

結果

3dキャラクターの正面絵ということで、高品質で簡単に手に入るunityちゃんを使ってみた。

3d素材を落としてきてunity上で表示してスクショしたもの

これをImageDreamのmultiview diffusionに与えて2d生成した結果がこちら(batch=3)

3d生成した結果の4 view

パラメータの調整などはしてないためいろいろと崩れているが、全体的な特徴については学習できていそう。手の先にちょろっと紐みたいなものが出ているのは初期poseの手の名残。本来は初期poseとref imageのposeは合わせておく必要があるが、とりあえずそのままでやってもこのくらいにはなった。

multiview diffusionから出てくる画像は1 viewあたり256x256で小さく、細かい部分は描けないためちゃんとやるなら工夫が必要そう。また、text-3dではなかった問題としてref imageと学習条件の不一致による崩れがある。条件をちゃんと合わせれば精度よく学習できると思われるが合わせるのはなかなか大変そう。

コード全体はこちら
https://github.com/y-kamiya/TADA/compare/ee9baa44bae626ca6e6157e69a992823cdeb28a1..e1355f6cda7a099dcc365189ad4c7a4fe5f6ff81 https://github.com/y-kamiya/ImageDream/compare/26c3972e586f0c8d2f6c6b297aa9d792d06abebb...b2a7575168a19712c2bec897d8531e632fbf6bd7