MiDaS Webcamを用いてリアルタイム単眼深度推定してみた

# MiDaSとは
Zero-shot (Fine-tuning なし) で使える単眼深度推定モデル
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/e5f9ba88-bc3b-3af2-6fa3-6b62b824d701.png' width=300 />
「[MIDAS](https://pytorch.org/hub/intelisl_midas_v2/)」より

[Pytorchのサイト](https://pytorch.org/hub/intelisl_midas_v2/)によると、
> MiDaS computes relative inverse depth from a single image. The repository provides multiple models that cover different use cases ranging from a small, high-speed model to a very large model that provide the highest accuracy. The models have been trained on 10 distinct datasets using multi-objective optimization to ensure high quality on a wide range of inputs.

和訳：MiDaSは、1枚の画像から相対的な逆深度を計算します。このリポジトリでは、小型の高速モデルから、最高の精度を実現する超大型モデルまで、さまざまなユースケースをカバーする複数のモデルを提供しています。これらのモデルは、多目的最適化を用いて10種類のデータセットで学習されており、幅広い入力に対して高い品質を確保しています。

個人的にいいなと思ったポイント

- Zero-shotでつかえる（深度範囲やスケールの変化に影響されないロバスト性）
- リアルタイム推定向けのライトなモデルがある
- ROSやモバイルにも対応している



https://arxiv.org/abs/1907.01341v3

https://pytorch.org/hub/intelisl_midas_v2/

https://github.com/intel-isl/MiDaS

# 環境構築
以下の環境で動作確認しました。今回はGPUを使用していません。

- CPU: Ryzen 5 3500
- OS: Windows10 Home

いつもはWSL2上でpyenv+pyenv-virtualenvでpythonの環境を構築するのですが、WSL2はUSBデバイスはサポートしていないよう（Webcamが認識されない）。そこで、今回はAnacondaを用いてWindows上にpython環境を構築していきます。Anacondaのインストール方法は省略します。

midasという名前の仮想環境（Python3.7.4）が作成します。

```
conda create -n midas python==3.7.4
```

仮想環境を有効化します。

```
conda activate midas
```

必要なライブラリをpipでインストールします。


```
pip install torch==1.8.0 torchvision
pip install opencv-python
pip install timm==0.4.5
```

インストールされたライブラリのバージョンは以下の通りです。

```
numpy==1.21.0
opencv-python==4.5.2.54
Pillow==8.3.0
timm==0.4.5
torch==1.8.0
torchvision==0.9.0
```

# とりあえず試してみる
## 準備
MiDaSのリポジトリをクローン

```
git clone https://github.com/intel-isl/MiDaS.git
```

READMEの[setup](https://github.com/intel-isl/MiDaS#setup)にあるリンクから学習済みモデルをダウロードする。モデルは以下の４種類がある。

- `dpt_large`: 最も高精度
- `dpt_hybrid`: 画質はやや劣るが、CPUや低速のGPUで速度が向上
- `midas_v21_small`: リソースに制約のあるデバイス、もしくはリアルタイムアプリケーション向け
- `midas_v21`: レガシー（convolutional model）


ダウンロードした学習済みモデルは`weights`フォルダーに入れる。

`input`に深度推定したい画像を入れる。今回使用した以下の画像のサイズは1280x720です。
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/6a1c8e90-0b38-051b-1a25-60cf1d725dda.jpeg' width=500 />

あとは`run.py`を実行するだけです。結果は`output`へ出力されます。

## 実行

```
python run.py --model_type dpt_large
```
推定所要時間：8.967秒
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/4f90f809-6e42-1c0d-8648-7184cdb7350c.png' width=500 />



```
python run.py --model_type dpt_hybrid 
```
推定所要時間：4.803秒
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/48da36cf-1650-da7d-c3e9-add28cfecc07.png' width=500 />



```
python run.py --model_type midas_v21_small
```
推定所要時間：0.172秒
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/25944d6a-62bd-5d41-8155-da318b10c44b.png' width=500 />



```
python run.py --model_type midas_v21
```
推定所要時間：2.597秒
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/2789f5e6-a01f-eedf-66e2-353d73d2dab9.png' width=500 />

## 感想
やはり推定時間と精度はトレードオフのようですね。結果を定量的には評価していませんが、見た目から明らかに精度に差が出ていることがわかります。`dpt_large`と`midas_v21_small`を比べると一目瞭然でしょう。`dpt_large`ははオブジェクトの輪郭がくっきりしているのに対して、`midas_v21_small`はぼやけています。しかし、`dpt_large`と`dpt_hybrid`を比べてみると微妙なところです。違いははっきり分かりますが、どちらの精度がいいかはわかりません。ちなみに各モデルの推定精度と推定時間をオープンデータセットで評価した結果は[README](https://github.com/intel-isl/MiDaS#accuracy)に記載してありました。

結論、リアルタイムで予測を行う際は`midas_v21_small`を使うのがよさそうです。今回の目的はWebcamで（そこそこのフレームレートの）リアルタイム深度推定することなので。


# Webcamでリアルタイム単眼深度推定
## モデルの読み込み

```python
model_type = "MiDaS_small"  # MiDaS v2.1 - Small   (lowest accuracy, highest inference speed)

midas = torch.hub.load("intel-isl/MiDaS", model_type)
```

## モデルをGPUへ転送
今回のデモはCPUのみで計算を行っています。

```python 
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
midas.to(device)
midas.eval()
```

## transformの読み込み
モデルの大きさに合わせて画像のサイズを変更したり、正規化するためのtransformを読み込む

```python
midas_transforms = torch.hub.load("intel-isl/MiDaS", "transforms")

if model_type == "DPT_Large" or model_type == "DPT_Hybrid":
    transform = midas_transforms.dpt_transform
else:
    transform = midas_transforms.small_transform
```

## Webcamで撮影したフレームを深度画像に変換
`cv2.VideoCapture(0)`でカメラ番号を指定します。
推定時は`with torch.no_grad():`と記述することで、勾配の計算を行わなくなります。それによって余計なメモリを使わなくて済みます。


```python
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    input_batch = transform(frame).to(device)
    with torch.no_grad():
        prediction = midas(input_batch)
        prediction = torch.nn.functional.interpolate(
            prediction.unsqueeze(1),
            size=frame.shape[:2],
            mode="bicubic",
            align_corners=False,
        ).squeeze()
    depth_frame = prediction.cpu().numpy()
    depth_frame = normalize_depth(depth_frame, bits=2)
    cv2.imshow('Depth Frame', depth_frame)

    if cv2.waitKey(1) == 27:
        break

cap.release()
cv2.destroyAllWindows()
```

## 深度画像の正規化
今回は画像の深度をわかりやすく可視化するために、推定した深度画像を正規化します（画像が8bitなら0~255、16bitなら0~65535）。

```python
def normalize_depth(depth, bits):
    depth_min = depth.min()
    depth_max = depth.max()
    max_val = (2**(8*bits))-1
    if depth_max - depth_min > np.finfo("float").eps:
        out = max_val * (depth - depth_min) / (depth_max - depth_min)
    else:
        out = np.zeros(depth.shape, dtype=depth.type)
    if bits == 1:
        return out.astype("uint8")
    elif bits == 2:
        return out.astype("uint16")
```

## 全体のコード

```midas_realtime_webcam.py
import cv2
import torch
import numpy as np


def normalize_depth(depth, bits):
    depth_min = depth.min()
    depth_max = depth.max()
    max_val = (2**(8*bits))-1
    if depth_max - depth_min > np.finfo("float").eps:
        out = max_val * (depth - depth_min) / (depth_max - depth_min)
    else:
        out = np.zeros(depth.shape, dtype=depth.type)
    if bits == 1:
        return out.astype("uint8")
    elif bits == 2:
        return out.astype("uint16")


def main():
    model_type = "MiDaS_small"  # MiDaS v2.1 - Small   (lowest accuracy, highest inference speed)

    midas = torch.hub.load("intel-isl/MiDaS", model_type)

    device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
    midas.to(device)
    midas.eval()

    midas_transforms = torch.hub.load("intel-isl/MiDaS", "transforms")

    if model_type == "DPT_Large" or model_type == "DPT_Hybrid":
        transform = midas_transforms.dpt_transform
    else:
        transform = midas_transforms.small_transform

    cap = cv2.VideoCapture(0)

    while True:
        ret, frame = cap.read()
        input_batch = transform(frame).to(device)
        with torch.no_grad():
            prediction = midas(input_batch)
            prediction = torch.nn.functional.interpolate(
                prediction.unsqueeze(1),
                size=frame.shape[:2],
                mode="bicubic",
                align_corners=False,
            ).squeeze()
        depth_frame = prediction.cpu().numpy()
        depth_frame = normalize_depth(depth_frame, bits=2)
        cv2.imshow('Depth Frame', depth_frame)

        if cv2.waitKey(1) == 27:
            break

    cap.release()
    cv2.destroyAllWindows()

    
if __name__ == '__main__':
    main()
```

## 実行

以下のコマンドを叩くだけです。

```
python midas_realtime_webcam.py
```

`ESC`キーを押すとループから抜け、終了します。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">MiDaSを用いてリアルタイム単眼深度推定をしてみました。ライトなモデルを使うとレスポンスもいい感じです。ROSやモバイルにも対応しているようなのでいろいろと遊べそう。<a href="https://twitter.com/hashtag/Python?src=hash&amp;ref_src=twsrc%5Etfw">#Python</a> <a href="https://twitter.com/hashtag/PyTorch?src=hash&amp;ref_src=twsrc%5Etfw">#PyTorch</a> <a href="https://twitter.com/hashtag/Midas?src=hash&amp;ref_src=twsrc%5Etfw">#Midas</a> <a href="https://t.co/0YQm2qgcQg">https://t.co/0YQm2qgcQg</a> <a href="https://t.co/ZDBG9GS8ZH">pic.twitter.com/ZDBG9GS8ZH</a></p>&mdash; yakiimo121 (@yakiimo121) <a href="https://twitter.com/yakiimo121/status/1412749622766800898?ref_src=twsrc%5Etfw">July 7, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 参考
- https://pytorch.org/hub/intelisl_midas_v2/
- https://github.com/intel-isl/MiDaS
- https://qiita.com/t2y/items/2a3eb58103e85d8064b6
- https://qiita.com/ozaki_physics/items/985188feb92570e5b82d
- https://ensekitt.hatenablog.com/entry/2017/12/19/200000