OpenCVとMatplotlibでAR(拡張現実)する

## OpenCVとMatplotlibでAR(拡張現実)できるか挑戦してみた

去年のアドベントカレンダーでは[100行で作るAR](https://qiita.com/suo-takefumi/items/231a6f97cd880fe629a5)と題して、できるだけシンプルにARを実装する挑戦をしました。
今年は、MatplotlibでARを実装する挑戦をしました。
OpenCVで3Dデータを用いたARにはOpenGLを使うことがよくあると思いますが、OpenGLを使用せずMatplotlibの3DグラフでARができないか挑戦しました。

## 完成デモ

完成したものはこちらです。ARで描画しているのはMatplotlibの3Dグラフです。ARマーカーの傾きに応じグラフの見え方も変わっているのがわかるでしょうか。

<iframe width="560" height="315" src="https://www.youtube.com/embed/QnYwcrHeKbI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

---

ここから作り方について紹介します。

## 環境

Windows11(カメラ付き)でPython3.10を使用しました。

```console
PS C:\> python --version
Python 3.10.8
```

## コードはgithubにあります

コード類はgithubに格納しました。

https://github.com/zgw426/AugmentedReality_with_OpenCV_Matplotlib


## 環境の準備
### Windows環境の準備

PowerShellで以下のコマンドを実行しPythonの必要モジュールをインストールします。

最初にvenv環境を作り、作成したvenv環境`OpenCVMatplotlibAR`を起動します。

```console
mkdir .venv
cd .venv
python -m venv OpenCVMatplotlibAR
PS C:\Users\hoge> .venv\OpenCVMatplotlibAR\Scripts\Activate.ps1
(OpenCVMatplotlibAR) PS C:\Users\hoge> 
```

venv環境 `OpenCVMatplotlibAR`に必要モジュールをインストールします。
モジュールのインストールは本コードをまとめたgithubのrequirementsファイルを使用します。

```console
(OpenCVMatplotlibAR) PS C:\Users\hoge> cd repo
(OpenCVMatplotlibAR) PS C:\Users\hoge\repo> git clone https://github.com/zgw426/AugmentedReality_with_OpenCV_Matplotlib.git
(OpenCVMatplotlibAR) PS C:\Users\hoge\repo> cd AugmentedReality_with_OpenCV_Matplotlib
(OpenCVMatplotlibAR) PS C:\Users\hoge\repo\AugmentedReality_with_OpenCV_Matplotlib> pip install -r requirements_fixed-version.txt
```

これでWindows環境の準備は完了です。

### ARマーカーの準備

ARマーカーの作成は、[こちらの記事](https://qiita.com/suo-takefumi/items/30a225cef8ea42366f8e#ar%E3%83%9E%E3%83%BC%E3%82%AB%E3%83%BC%E7%94%BB%E5%83%8F%E3%81%AE%E4%BD%9C%E6%88%90)を参考に`0`番と`1`番の画像を作成します。

https://qiita.com/suo-takefumi/items/30a225cef8ea42366f8e#ar%E3%83%9E%E3%83%BC%E3%82%AB%E3%83%BC%E7%94%BB%E5%83%8F%E3%81%AE%E4%BD%9C%E6%88%90

これでARマーカーの準備は完了です。

## 実行してみる

実際にコードを実行してみます。

## Step01.2DのMatplotlibグラフでARする

スクリプトが動作するかの確認です。元画像`sample.png`に対しARを実行し、その結果を`out-sample.png`として出力します。

```console
(OpenCVMatplotlibAR) PS C:\Users\hoge\repo\AugmentedReality_with_OpenCV_Matplotlib> python 01_matplot2d_not-camera.py
DONE : 
        inputfile is sample.png
        outputfile is out-sample.png
(OpenCVMatplotlibAR) PS C:\Users\hoge\repo\AugmentedReality_with_OpenCV_Matplotlib>
```

以下は、元画像`sample.png`と出力画像`out-sample.png`です。
このように、ARマーカーにグラフが描画されれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/b9ab8eac-d578-ac5b-4fd4-4ba33c8a7bca.png)

## Step02.カメラ映像と2DのMatplotlibグラフでARする

Windows搭載のカメラからの映像にMatplotlibグラフをARで描画します。以下を実行するとウィンドウが開きカメラ映像が表示されます。ARマーカーを映すと2Dグラフが描画されます。

```console
(OpenCVMatplotlibAR) PS C:\Users\hoge\repo\AugmentedReality_with_OpenCV_Matplotlib> python 02_matplot2d_no-transparency.py
```

この動画のように2Dグラフが表示されれば成功です。
開いたウィンドうは `q` キーで終了します。

<iframe width="560" height="315" src="https://www.youtube.com/embed/t4DyeLGA0gk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## Step03.3DのMatplotlibグラフでARする

次は3DグラフでARします。

```console
(OpenCVMatplotlibAR) PS C:\Users\hoge\repo\AugmentedReality_with_OpenCV_Matplotlib> python 03_matplot3d_no-transparency.py
```

この動画のように3Dグラフが表示されれば成功です。
開いたウィンドうは `q` キーで終了します。

<iframe width="560" height="315" src="https://www.youtube.com/embed/vzQA8_DQ8tw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## Step04.3Dの透過処理を追加してMatplotlibグラフでARする

Step03では白い背景色のため立体物を描画してる感じがしないので背景を透過します。またグラフの軸や目盛も不要なので消しました。

```console
(OpenCVMatplotlibAR) PS C:\Users\hoge\repo\AugmentedReality_with_OpenCV_Matplotlib> python 04_matplot3d_transparency.py
```

この動画のように3Dグラフが表示されれば成功です。
開いたウィンドうは `q` キーで終了します。

<iframe width="560" height="315" src="https://www.youtube.com/embed/QnYwcrHeKbI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

※Step04は冒頭で紹介したデモと同じです。

## 補足

3DグラフでAR(Step03, Step04)の仕組みを簡単に説明します。
要はARマーカーの傾きにあわせて3Dグラフも傾けています。

- Step03の処理フロー概要
    - (1) ARマーカーを検出した際にARマーカーの傾きを算出
    - (2) 算出した傾きをもとに3Dグラフの傾ける
    - (3) 3Dグラフの画像を生成
    - (4) 生成した画像をARマーカーにマッピング

3Dグラフの作成と画像生成は以下の関数で実施しています。引数の`roll_elev`,`pitch_azim`がARマーカーの傾きから算出した値です。

```python:ARマーカーに傾きに応じた3Dグラフの生成
def graph_3d(roll_elev, pitch_azim):
    t = np.linspace(0, np.pi * 4, 50)
    x = np.cos(t)
    y = np.sin(t)
    z = np.linspace(0,1,50)

    fig = plt.figure()
    ax = fig.add_subplot(projection='3d')
    ax.view_init(elev=roll_elev, azim=pitch_azim)

    plt.cla()
    ax.plot(x, y, z, marker='o', markersize=5, color='blue')

    fig.canvas.draw()
    img_array = np.array(fig.canvas.renderer.buffer_rgba())
    img_return = cv2.cvtColor(img_array, cv2.COLOR_RGBA2BGR)

    # メモリ解放
    plt.clf()
    plt.close()

    return img_return
```

Step04は、Step03の処理フロー(4)にて透過処理を追加で行います。
透過処理の関数はこちらです。

```python:透過処理の関数
def putSprite_npwhere(back, front4, pos):
    """透過処理"""
    x, y = pos
    fh, fw = front4.shape[:2]
    bh, bw = back.shape[:2]
    x1, y1 = max(x, 0), max(y, 0)
    x2, y2 = min(x+fw, bw), min(y+fh, bh)
    if not ((-fw < x < bw) and (-fh < y < bh)) :
        return back
    front3 = front4[:, :, :3]
    front_roi = front3[y1-y:y2-y, x1-x:x2-x]
    roi = back[y1:y2, x1:x2]
    tmp = np.where((front_roi==(255,255,255))|(front_roi==(0,0,0)), roi, front_roi)  # 255x3:黒, 0x3:白
    back[y1:y2, x1:x2] = tmp

    return back
```

---

今回は以上です。
起動したvenv環境`OpenCVMatplotlibAR`は`deactivate`コマンドで終了します。

```console
(OpenCVMatplotlibAR) PS C:\Users\hoge\repo\AugmentedReality_with_OpenCV_Matplotlib> deactivate
PS C:\Users\hoge\repo\AugmentedReality_with_OpenCV_Matplotlib> 
```

## 今後もOpenCVでARを挑戦していきます

今回はMatplotlibでしたが、他の画像生成系のモジュールでもに同様のことができそうに思いました。他のモジュールでも挑戦しようかと思います。

## 自己紹介

OpenCVで壁に穴をあけたりしています。こちらのデモは2枚の壁に穴をあけています。いつか高層ビルの1階から屋上まで穴をあけたいと思っています。

<iframe width="560" height="315" src="https://www.youtube.com/embed/G7gJNwqv2Cw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>