5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

試してみる Monocular Depth Estimation: MiDaS on Livebook

Last updated at Posted at 2022-01-13

1.Prologue

職業柄、最近 Deep Learningを応用した単眼視深度推定(Monocular Depth Estimation)がとても気になっている。カメラのみを用いて深度(Z距離)測定する方法といえば、2台(二眼視)またはそれ以上(多眼視)のカメラを用いて三角測量を行うステレオ・ビジョンがポピュラーなのだが、単眼視とはなんともユニークで話ある。その仕組みと、どの程度現場で使えるモノなのかの感触を掴みたいと思っている。

ところで、先日Qiitaのある記事がきっかけで、拙作 cimg_exのデモを Livebook上に書いて動かしてみる機会があった。やってみると、これがまぁ Jupyter上での作文作業と同じく、Elixirコードの動作をリアルタイムに確認しつつその説明文を起こすことができ、とてもお手軽かつストレス・フリーな体験であった。

そんな訳で、Livebook上でちょこっと「単眼視深度推定」をデモってみようかと思い立ったのである。道具箱には、かれこれ2年ほど前から取り組んでいる TensorFlow liteの Elixir拡張モジュール tfl_interpとその取り巻き ― cimg_exなど ― が入っている。学習済みの tfliteモデルさえ入手できれば、Python不要でサクッとデモることができる筈だ。

さて、肝心の学習済み tfliteモデルの入手だが、昨今の情勢ではホットな Deep Learningモデルは Pytorchで記述されてることが多く、その場合 Pytorchモデルを "Pytorch → ONNX → TensorFlow → TensorFlow lite"とコンバートして tfliteモデルを得ることになる[* 1]。面倒な作業である…のだが、幸い MiDaSと言うモデルが tflite版も提供していたので、これを使わせて貰うことにした。

MiDaS:
Towards Robust Monocular Depth Estimation: Mixing Datasets for Zero-shot Cross-dataset Transfer

MiDaSモデルの内部を少し覗いてみると、RefineNetが用いられており、どうやら Semantic Segmentationの技術を応用しているようだ。Deep Learningの技術は日進月歩で進化しているので、キャッチアップするのも大変である。

RefineNet (Multi-Path Refinement Network):ディープラーニングによるSemantic Segmentation手法

[* 1]余談になるが、YOLOXには tfliteモデルが用意されていなかった。そのため、まさしくこのコンバートの連鎖を辿ることとなった。そして厄介なことに、コンバートが成功する確約も、得られた tfliteモデルが正しく動作する確約も無いのだ。

Requirement:

デモに用いた環境ほかは以下の通り。

  • Windows WSL2/Ubuntu 20.04.2
  • Elixir 1.13.1 (compiled with Erlang/OTP 24)
  • Erlang/OTP 24 [erts-12.1.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
  • CMake version 3.18 or later [* 2]
  • unzip 6.0-25ubuntu1
  • TensorFlow lite v2.7.0 (https://github.com/tensorflow/tensorflow.git)

[* 2]CMakeのインストール手順については、本文末の「補足2」を参照願いたい。

作業は WSL2/Ubuntuのホーム・ディレクトリ(~)下に "demo"ディレクトリを用意し、その中で行うこととした。

~$ mkdir demo
~$ cd demo

主要なファイルは次の様に配置される予定。

 ~/demo
├── livebook
│   └── model_opt.tflite - MiDaSモデルファイル
└── tfl_interp
    ├── lib   - Elixirソースコード
    ├── src   - tfl_interpの C++ソースコード
    ├── priv
    │   └── tfl_interp - TensorFlow liteの Elixir拡張モジュール(実行形式)
    ├── CMakeLists.txt - tfl_interpを cmakeでビルドする為の CMakeLists
    └── _build
         └── dev
             └── .cmake_build - tfl_interpや TensorFlow liteをビルドするための作業ディレクトリ

2.Install 'Livebook'

ローカルPCへのLivebookのインストールは下の通り (インストール先は ~/demo/livebook)
ここでは、Livebookが依存する Elixirモジュールの入手までを行っておく: mix deps.get -- only prod

~/demo$ git clone https://github.com/elixir-nx/livebook.git
~/demo$ cd livebook
~/demo/livebook$ mix deps.get --only prod

~/demo/livebook$ cd ..

3.Install 'tfl_interp' and download "MiDaS" tflite model

拙作の tfl_interpは、一般の Elixirモジュールと少々取り扱いが異なる所がある。

一般の Elixirモジュールの場合は、メイン・アプリの "mix.exs"の依存モジュール・リストに登録し、おもむろに mix deps.get mix compileを実行すれば、自動的に当該モジュールのコンパイルも行われるのだが、tfl_interpでは意図的に自動コンパイルが行われない様に取り扱うこととしている。

その理由は、tfl_interpの backendである TensorFlow liteのビルドと、それに先立つ TensorFlowファイルセットのダウンロードに、数十分オーダーの時間を要するからだ。つまり、一般の Elixirモジュールと同じように取り扱うと、tfl_interpを利用した新たな Deep Leraningアプリ・プロジェクトを開始する度に、もれなく数十分のコーヒーブレイクが付いてくることになるのだ…冗談じゃない。間抜け過ぎるだろ。

そんな訳で、tfl_interpはローカルPC上で単独のプロジェクトとして一度だけビルドし、目的アプリからはそのビルド成果物を "mix deps"の Pathオプション(:path)で借用することにした。


具体的なインストール手順は下の通り (インストール先は ~/demo/tfl_interp)

livebookと同じディレクトリ階層に tfl_interpのファイルセットをダウンロードし[* 3]、mix cmake --configでビルド&インストールを行う。先にも触れた通り、インストールが完了するまでに数十分の時間を要する。紅茶とお菓子と雑誌を用意してから取り掛かろう。Pathオプションについては後述。

2022/2/2追記
tfl_interp v0.1.3 でビルド・スクリプトに少し変更が入りました。
下記の手順の最後の行"mix cmake --config"を実行する前に、"~/demo/tfl_interp/mix.exs"ファイルの project/0の中にある次の行を削除またはコメント・アウトして下さい。

   compilers: [:cmake] ++ Mix.compilers(),

~/demo$ wget --no-check-certificate https://github.com/shoz-f/tfl_interp/archive/main.tar.gz
~/demo$ mkdir tfl_interp && tar xzvf main.tar.gz -C tfl_interp --strip-components 1
~/demo$ rm main.tar.gz
~/demo$ cd tfl_interp
~/demo/tfl_interp$ mix deps.get
~/demo/tfl_interp$ mix cmake --config

tfl_interpのビルド&インストールが成功すれば、tfl_interp/privに実行形式ファイルが置かれている筈なのでチェックしよう。OKのようだ、次へ進もう。:wink:

~/demo/tfl_interp$ ls -l priv
合計 5444
-rwxr-xr-x 1 shozo shozo 5573816  1月 17 01:52 tfl_interp

~/demo/tfl_interp$ cd ..

[* 3]git clone https://github.com/shoz-f/tfl_interpでも差し支えない。URLは hexにも登録している。


併せて MiDaSの学習済みモデルをダウンロードする (インストール先は ~/demo/livebook)

~/demo$ wget -P livebook https://github.com/isl-org/MiDaS/releases/download/v2_1/model_opt.tflite

4.Run a demo of Midas inference

さて、準備が整ったので Livebookを起動する: MIX=prod mix phx.server

ターミナルに表示された URL - この例では"http://localhost:8080/?token=aergubyrpc6hak7hq6dqtryhwukosq4p" - をブラウザで開き、下の画面が表示されれば成功だ。

~/demo$ cd livebook
~/demo/livebook$ MIX_ENV=prod mix phx.server
[Livebook] Application running at http://localhost:8080/?token=aergubyrpc6hak7hq6dqtryhwukosq4p

さて、まっさらな状態から Livebookの notebookを書くならば、ここで画面右上の[New notebook]ボタンを押して始めればよい。以下、記事の要所々々に掲載したコードを notebookに入力し実行することになる。

尤も、この記事の中のコードは一度 Livebook上で試しており、コードにこの記事の文章を書き加えた notebookを Gistに保存している。お手軽にそれをインポートして試すこともできる。この場合は、画面右上の[Import]ボタンを押そう。

notebookの保存場所: https://gist.github.com/shoz-f/02ba7f08e260c0d8688c82fabe7adc81


ここから先がデモの Elixirコードだ。まず最初に、依存モジュールを Mix.installでインストールする。リストの書き方は、mix depsのそれと同じだ。先述した通り、tfl_interpに関しては hexリポジトリではなく、ローカルPC上にインストールした tfl_interpのパスをPathオプションで指定する。

コード・セルの左上の実行ボタンを押そう。

Mix.install([
  {:mix_cmake, "~> 0.1"},
  {:poison, "~> 3.1"},
  {:cimg, "== 0.1.6"},
  {:nx, "~> 0.1.0"},
  {:kino, "~> 0.3.1"},
  {:download, "~> 0.0.4"},
  {:tfl_interp, path: "../tfl_interp"}
])

どうでも良いことなのだが、Pathオプションで指定したモジュールは、いつものように depsディレクトリ下にコピーが取られることはなく、直接_buildディレクトリ下にバイトコード他が生成されるようだ。


MiDaSによる深度推定のコードは下の通り。

tfl_interpでは、tfliteモデル毎にモジュールを定義し、use文で TflInterpモジュールが用意する裏方機能を取り込むように書く。このモジュールが Deep Learningの推論モジュールとなる。モデル・ファイル(*.tflite)は、useのオプション・パラメタmodel:にそのパスをセットして裏方に渡す。

今回のコードでは、CImg画像を受け取り深度推定結果を返す関数apply/1一つだけを定義している。大概の Deep Learning推論ルーチンは、apply/1と同様に (a)前処理、(b)推論実行、(c)後処理の3つの処理で出来ている。それぞれの処理を詳しく見る前に、MiDaSモデルの入出力仕様を確認しておこう。

MiDaSモデルの入出力仕様

  • 入力tensor[0]:
    RGB画像 W:256pix×H:256pixの各色プレーンの輝度値を{float-32bit, -2.0~2.0}に正規化し、NHWC軸並びでシリアライズしたバイナリ列を入力テンソルに与える。
  • 出力tensor[0]:
    入力画像の各画素位置の相対深度推定値を格納した float-32bit 256x256配列を、NHWC軸並びでシリアライズしたバイナリ列。(※注)入力画像に依って、相対深度推定値が取りうる値の範囲は変化する。

それでは、コードを見ていこう。

(a)前処理 - コード中 "# preprocess"から始まるブロック

CImg.resize/2で入力画像imgを W:256pix×H:256pixの画像にリサイズした後に、CImg.to_flat/2で各色プレーンの輝度値を {-2.0~2.0}の範囲の float-32bitに正規化&シリアライズしたバイナリbin(numpy形式)を作成する。

(b)推論実行 - コード中 "# prediction"から始まるブロック

TflInterp.set_input_tensor/3でバイナリbinMiDaSモデルの入力 tensor[0]にセットし、TflInterp.invoke/1で推論を実行する。その後、TflInterp.get_ouput_tensor/2で推論結果の出力tensor[0]をバイナリとして取り出し、float-32bit 256x256の Nx.Tensor outputsに変換する。

(c)後処理 - コード中 "# postprocess"から始まるブロック

MiDaSモデルの入出力仕様で見た通り、Nx.Tensor outputsに格納されている相対深度推定値は、その取りうる値の範囲が入力画像に依存して変動してしまう。推論結果を利用するアプリケーションにしてみればなんとも迷惑な話だ。そこで、相対深度推定値の最小値min、最大値maxを用いて正規化を行い、CImg.create_from_bin/6で 8bit W:256pix×H:256pixのモノクロ画像に変換する。

defmodule Midas do
  use TflInterp, model: "model_opt.tflite"

  @midas_shape {256, 256}

  def apply(img) do
    # preprocess
    bin =
      img
      |> CImg.resize(@midas_shape)
      |> CImg.to_flat(range: {-2.0, 2.0})

    # prediction
    outputs =
      __MODULE__
      |> TflInterp.set_input_tensor(0, bin.data)
      |> TflInterp.invoke()
      |> TflInterp.get_output_tensor(0)
      |> Nx.from_binary({:f, 32})
      |> Nx.reshape({256, 256})

    # postprocess
    [min, max] =
      [Nx.window_min(outputs, {256, 256}), Nx.window_max(outputs, {256, 256})]
      |> Enum.map(&Nx.squeeze/1)
      |> Enum.map(&Nx.to_number/1)

    _result =
      outputs
      |> Nx.subtract(min)
      |> Nx.divide(max - min)
      |> Nx.to_binary()
      |> CImg.create_from_bin(256, 256, 1, 1, "<f4")
  end
end

上記の Midasモジュールを利用するには、裏方にいる GenServerを起動し、tfl_interpの実体である Port拡張モジュールを spawnする必要がある。

Midas.start_link([])

以上で全てだ。


では早速 MiDaS深度推定を試してみよう。入力画像は、Woods Hole Oceanographic Institutionのサイトから愛くるしいペンギンの写真をお借りした。

Woods Hole Oceanographic Institution: https://www.whoi.edu

Woods Hole Oceanographic Institution is the world's leading, independent non-profit organization dedicated to ocean research, exploration, and education. Our scientists and engineers push the boundaries of knowledge about the ocean to reveal its impacts on our planet and our lives.

File.rm("graphics-SIPEX_P_Kimball-IMG_9445-1920x1280-1-2-1200x679.jpg")

img =
  Download.from(
    "https://www.whoi.edu/wp-content/uploads/2021/08/graphics-SIPEX_P_Kimball-IMG_9445-1920x1280-1-2-1200x679.jpg"
  )
  |> elem(1)
  |> CImg.load()

CImg.to_jpeg(img)
|> Kino.Image.new(:jpeg)

Midas.apply/1は結果としてモノクロ画像を出力する。それをそのまま表示するのはなんとも無粋な眺めである。そこで、少々色気を付け加えたいと思う。cimg_ex v0.1.6では未公開の関数を用いて、モノクロ画像をカラフルなヒートマップ画像に変換する関数heatmap/1を用意しよう。

heatmap = fn img ->
  with {:ok, data} = CImg.NIF.cimg_map_color(img, "jet", 0) do
    %CImg{handle: data}
  end
end

MiDaS深度推定の実行結果は下の様になる。一番手前のペンギンが濃いオレンジで、奥に行くほど青に変わっていくのがわかる。期待通りの動作しているようだ。

Midas.apply(img)
|> CImg.resize({1200, 679})
|> heatmap.()
|> CImg.to_jpeg()
|> Kino.Image.new(:jpeg)

5.Epilog

さて、一応それらしく動作するデモが実現できたかなと思う。
まだきちんと理解していないのだが、MiDaSが推定する深度は絶対的なモノではなく、相対的でシーン中のオブジェクトの前後関係が分ると言った程度のモノの様に思う。とは言え、たった一枚の写真から、それも短時間にここまで深度を推定できるとは、20世紀の技術者である小生とっては驚異である。それにしても、いったいどういう原理で深度を推定しているのだろう。もっともっと勉強せねば(^^;)。

(完)

補足1 - tfliteモデルのプロファイル確認機能について

Midasモジュールが利用可になった後ならば、次の様にして tfliteモデルのプロファイルを確かめることができる。

TflInterp.info(Midas)
{:ok,
 %{
   "XNNPack" => true,
   "class" => 0,
   "exe" => "/home/shoz/.cache/mix/installs/elixir-1.13.1-erts-12.1.4/52d5348d716c5977338f33f965dd9349/_build/prod/lib/tfl_interp/priv/tfl_interp",
   "inputs" => [%{"dims" => [1, 256, 256, 3], "name" => "Const", "type" => "FLOAT32"}],
   "label" => "none",
   "model" => "model_opt.tflite",
   "outputs" => [
     %{
       "dims" => [1, 256, 256, 1],
       "name" => "midas_net_custom/sequential/re_lu_9/Relu",
       "type" => "FLOAT32"
     }
   ],
   "thread" => 4
 }}

補足2 - Ubuntuに最新のCMakeをインストールする手順

Ubuntuへの CMake verion 3.18以降のインストールは、残念ながらapt install cmakeではできない。以下の手順に沿って CMakeのサイトからソースコードをダウロードして、手元でビルド&インストールすることになる。念の為に、bashの hashをリセットしておこう。

~/demo$ wget https://github.com/Kitware/CMake/releases/download/v3.22.1/cmake-3.22.1.tar.gz
~/demo$ tar xvzf cmake-3.22.1.tar.gz
~/demo$ cd cmake-3.22.1
~/demo/cmake-3.22.1$ ./bootstrap
~/demo/cmake-3.22.1$ make
~/demo/cmake-3.22.1$ sudo make install
~/demo/cmake-3.22.1$ hash -r

cmakeのバージョン確認方法は次の通り。

~/demo$ cmake --version
cmake version 3.22.1

CMake suite maintained and supported by Kitware (kitware.com/cmake).

無事インストールできたようだ:wink:

参考:本家CMakeサイト

  1. インストール手順 "Installing CMake"
  2. ダウンロード・ファイル置き場所 "Get the Software"
5
1
14

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?