左を入れると右が出るDeep3Dで深度推定しちゃおう

はじめに

どうも、そこまでお久しぶりではないはずです。
今回は『Deep3D: Fully Automatic 2D-to-3D Video Conversion with Deep Convolutional Neural Networks』という論文を読んで、勉強がてらChainerで感謝の再実装したので、ブログにまとめていきます。年末年始の10連休のうちの3, 4日での成果になります。
最終的には『Single View Stereo Matching』を実装したいのですが、今回はその一部のDeep3Dを実装しました。

概要

ざっくり言ってしまえば、2D画像から3D画像を生成することを目的とした論文となります。(2016年の論文なのでそんなに新しいものではないです)

より具体的にはこの論文では左側の入力2D画像を用いて、ちょっとカメラ位置を横にずらした右側の入力2D画像を生成し、その二つを合成することで3D画像を作成しています。

「それなら普通にカメラ2個用意しちゃった方が早くない?」と思った方もいらっしゃるかとは思いますが、論文によれば、「コストの問題」や「強制された遠近法(実際には遠くにある大きな物体を、近くにある小さな物体のように見せかけたり、その逆をするトリック)のような特殊な撮影ができない」という点でカメラ2個を使うのは難しいそうです。

前者については、映画用のカメラともなるとかなり高額なものになるので、難しいのは想像しやすいかとは思います。

強制された遠近法については「forced perspective」でGoogle画像検索してみると色々と画像が出てくるので、それをみるとイメージが湧くかと思いますが、
後者が難しい理由はちょっと私にはよく分かりませんでした。

映画のワンシーン(?)で実際にDeep3Dを使って3D化した際のやってみた結果が公開されていて、これをみるとなんとなくどういう結果が出るのかわかるかと思います。

Deep3D_TF/frodo.gif at master · JustinTTL/Deep3D_TF · GitHub

論文曰く、応用先としては3D映画の作成やVR市場での利用を想定しているようです。

Deep3Dのアーキテクチャf:id:gat-chin321:20190109012618p:plain

Deep3Dの全体のアーキテクチャとしては以下の図の通りになっています。

f:id:gat-chin321:20190109012618p:plain
全体のアーキテクチャ(Deep3Dの原著論文より参照)

Deep3Dは雑に言ってしまうと以下の流れで推論を行います。

1. VGG16に左画像を通して、各Pooling層の出力をDeconvolutionしたものを取り出して、要素ごとに足し合わせたものをさらにDeconvolutionして特徴として利用する。
2. Selection LayerというLayerに放り込むと右画像が出力される。

それでは以降で、それぞれについて詳しく見て行きましょう。

VGG16

今回利用するのはニューラルネットをやっている人ならだいたい知っているであろうVGG16です。

論文: https://arxiv.org/pdf/1409.1556.pdf

詳細については省きますが、今回のアーキテクチャでは以下の部分に相当します。

f:id:gat-chin321:20190110224502p:plain
VGG16の部分の図

このVGG16はDeep3Dの論文では特徴抽出に利用します。

VGG16部分は具体的には以下の流れで処理して行きます。

1. VGG16のそれぞれのPooling層の出力をDeconvolution層に通します。(この時の最終層のfully connected layerはDeconvolutionを通した後に画像サイズ×5の手順で出す確率的Disparity Mapの枚数)
2. 1で出てきたものを足し合わせます。
3. もう一度Deconvolution層を通します。
4. そのあとにConvolution層を通してからSoftmaxを通して確率的Disparity Mapを作成します。

以上が以下の図の部分で行う処理になります。

f:id:gat-chin321:20190110230024p:plain
Deconvolutionの部分の図

初期化(Bilinear Interpolation by Deconvolution)

Bilinear Interpolationと等しくなるようにDeconvolutional layerを初期化すると学習が容易になるということが経験的にわかったらしいため、これにより初期化を行なっています。
初期化の式は下記の通りです。

 C = \frac{2S-1-(S mod 2)}{2S}

 w_{ij} = (1- |\frac{i}{S-C}|) (1- |\frac{j}{S-C}|)

 w_{ij}: i行目j列目のカーネルの重み

 S: スケール(定数)

正直なところ、なぜこの初期化が上手くいくのかは良くわからない。

Reconstruction with Selection Layer

この部分がDeep3Dの肝となるアーキテクチャっぽいです。
まずは一般的な2D to 3Dの変換の目的変数をいかに解いていたかをみていきましょう。

一般的な2D to 3Dの変換のタスク設計について

Disparity(図では D)を以下の式により求めていたようです。

 D = \frac{B(Z-f)}{Z}

 B: 2つのカメラ間の距離
 Z: Depth Map
 D: Disparity Map
 カメラから焦点面までの直線距離

この式はどこから出てきたのかは以下の図を使って解説していきます。

f:id:gat-chin321:20190203184149p:plain
一般的な2Dto3D変換のタスク設計の図解

この式は大抵の方が中学生のころ、やる意味もわからずにやらされた記憶のあるであろう相似の関係を利用して導出しています。

まず左側の「物体が焦点面よりも奥にある場合」についてですが、三角形abeと三角形acdを考えます。

この時、辺beと辺cdは平行なので、角abe=角acdかつ角aeb=角adcであることから、2つの角が等しくなり、これは相似条件を満たします。

そのため、三角形abeと三角形acdは相似なので、3組の辺の比が等しい関係と1組の辺の比と高さの比が等しくなる関係を利用すると、以下が言える。

 D: B = Z-f: f

これを Dについて解くと、

 D = \frac{B(Z-f)}{f}

となる。

次に右側の「物体が焦点面よりも手前にある場合」についても同様に辺beと辺cdは平行であることを利用すれば、相似となり、同様の式が出てきます。

ただ、Dの値はZ-fが負の値となるため、Dは負の方向に移動する点には注意が必要です。

この式を利用して色々やっていたのが従来の手法とこの論文では記載されていました。(と言ってもこの論文は2016年のものなのでそこそこ古いが)

Deep3Dでの変換のタスク設計について

では、ここでは実際にどういうタスク設計にして解いたかを解説していきます。

この論文では、右側のカメラの画像は左側のカメラの画像を水平方向にずらしたものであることを利用しています。

この右側のカメラの画像と左側のカメラの画像との関係は以下の式で表現できます。

 O_{i, j+D_{i, j}} = I_{i, j}

 O_{i, j}: 右側のカメラ画像の位置(i, j)のピクセル
 I_{i, j}: 左側のカメラ画像の位置(i, j)のピクセル
 D_{i, j}: 位置(i, j)のDisparity(ずれ量)

ただこの式では、 Dに関して微分可能でないため、ニューラルネットでは学習できません。

そこで、それぞれのピクセルごとにDisparityの値が dである確率の値 D^{d}_{i, j}を推定することにしました。

また、左側のカメラ画像を dの値だけ横にスライドさせたものを I^d_{i, j}とすると、

 I^d_{i, j} = I_{i, j-d}

と表現できます。

これらを用いると、右側のカメラ画像 O_{i, j} I^d_{i, j}とその確率マップである D^d_{i,j}をかけてそれぞれの dを加算したものとして以下のように表現できるようになります。

 O_{i, j} = \sum_{d} I^d_{i, j} D^d_{i, j}

この式では、 D^d_{i, j}に関して微分可能な式となり、ニューラルネットでも学習できるようになりました。

おそらくこの言葉だけだと何を言っているのかわからないかと思いますので、論文に記載されている図を利用して簡単に解説してみます。

f:id:gat-chin321:20190203193717p:plain
selection layer図解

まず、Disparityの値が-16~16までの確率マップ(Disparity Map)を33個作成します。

このDisparity Mapがやっていることは-16に注目した場合、-16ピクセルずらした画像とDisparityの値が-16である確率のピクセルごとの積をとると、Disparityの確率値が高いピクセルだけが残ります。

これを-15, -14…16と同じように33個全てに対して行なった後のそれぞれの結果に対して、ピクセルごとの和をとると右側画像になるように学習させています。

loss関数

Loss関数はL1ノルムとして、以下のlossにしています。

 L = |O - Y|
 O: 右側カメラ画像の予測データ
 Y: 右側カメラ画像の正解データ(実際の右側カメラ画像)

以下の論文で、ピクセル単位の予測タスクではL2 lossよりもL1 lossが優れていると言われていたのが、L1 lossにした理由らしいです。
[1511.05440] Deep multi-scale video prediction beyond mean square error

上記の論文を読んだわけではないのですが、今回のタスクだと画面の端っこ周りの予測が困難なので、その部分が異常値となって、L2 lossにすると不必要にlossが高くなってしまう気もするので、このlossの選択に違和感は感じなかったです。

学習方法やハイパーパラメータ

  • Batchsize: 64
  • 学習回数: 100,000 iteration
  • 学習率: 初期値0.002で20000 iterationごとに1/10
  • Weight Decay: なし
  • Dropout: 0.5でVGG16のfully connected layerの後
  • 左側の画像は432×180ピクセルにresizeして、サイズ384×160ピクセルにrandom crop

実装上の注意点(高解像度化)

元々この論文の提案手法の応用先は映画です。

当時の映画は少なくとも1920ピクセル×1080ピクセルの解像度の画像で学習しなければなりませんが、1920ピクセル×1080ピクセルのままやろうとするとかなり計算時間がかかり、GPUのメモリが厳しい感じになってしまいます。
論文では計算時間とメモリを節約するためにアスペクト比を保った432×180(と書いているけど多分実際は384×160)ピクセルに減らしているが、そのままだと映画への応用では許容できない解像度になっています。

この問題を解決するために、この論文では予測Disparity Mapを拡大して、元の高解像度の左画像とかけて足して、元の高解像度の右画像を生成するようにしています。
こうすると、単純に予測結果を4倍に単純に拡大した時の予測よりも良い画質を保ってくれるらしいです。

実装

以下が私が実装したコードになります。(もしかしたら実験の際に動かなくて修正してたりするかもしれないので、動かなかったらすみません)

GitHub - pesuchin/Deep3D-chainer

環境はColaboratoryで頑張りました。もう二度とやりたくないのでGPU欲しい。
論文では3D映画を利用したようですが、今回私が利用したデータセットはKITTIになります。フレームワークはChainerを使いました。
今回自分で実験したのはresizeせずに直接random crop使ってるので、本家の実装に忠実ではないです。(実験回し終わってから気づきました)。
あと前述の高解像度にも対応していないです。
100,000 iterationは回してられないので、lossがサチってそうなタイミングで止めています。

Disparityの範囲は-16〜16ピクセルと0〜64ピクセルで実験してみました。

0~64ピクセルにして実験してみた理由は『Single View Stereo Matching』を参考にしただけです。

実験結果

以下はDisparityを-16ピクセル〜16ピクセルまでを予測するようにした場合の結果となります。

f:id:gat-chin321:20190203193320p:plain
-16ピクセル〜16ピクセルのDisparityでの結果(左からDisparity Mapのargmax, 左側のカメラ画像, 右側のカメラ画像の予測結果, 実際の右側のカメラ画像)

f:id:gat-chin321:20190203193345p:plain
-16ピクセル〜16ピクセルのDisparityでの結果2(左からDisparity Mapのargmax, 左側のカメラ画像, 右側のカメラ画像の予測結果, 実際の右側のカメラ画像)

Disparity Mapのargmaxの図を見てみると比較的遠いピクセルのDisparityは上手く推定できているように見えますが、-16ピクセル〜16ピクセルのDisparityしか取れないので、至近距離の物体のDisparityが全く取れていない傾向があります。

論文で記載があった3D映画のデータでは問題なかったのかもしれませんが、KITTIでは至近距離のピクセルのDisparityが大きいので結構厳しいようです。

0ピクセル〜64ピクセルのDisparityをとるようにすると、それっぽいDisparity Mapのargmaxが取れているように見えます。

f:id:gat-chin321:20190203195725p:plain
0ピクセル〜64ピクセルのDisparityでの結果(左からDisparity Mapのargmax, 左側のカメラ画像, 右側のカメラ画像の予測結果, 実際の右側のカメラ画像)

f:id:gat-chin321:20190203195807p:plain
0ピクセル〜64ピクセルのDisparityでの結果(左からDisparity Mapのargmax, 左側のカメラ画像, 右側のカメラ画像の予測結果, 実際の右側のカメラ画像)

終わりに

深度推定は結果が目で見てわかりやすくて楽しいので割とおすすめの分野です。

今回のDeep3Dはカメラパラメータを全く意識しなくても実装できる比較的簡単なアルゴリズムになっているので、割と初心者向けだと思います。

最後にあまり期待はしていませんが、リモートワークで地方から働ける会社を探していますので、そういう会社があれば是非お声がけください。