Windowsで姿勢推定(tf pose estimation)

# はじめに
姿勢推定アルゴリズムOpenPoseのTensorflow版としてtf pose estimationが公開されています。
![inference_result2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487676/4baa9741-30a9-5c09-a835-1dfb62e93162.png)

これをWindows＋Anaconda仮想環境で動かしてみました。
Windowsではすんなり動かなかったため備忘録として残しておきます。
初心者のため説明などふわふわしてますがご了承ください。

# 環境
Windows 10 
conda 4.7.11
CUDA 10.1
cudnn 7.3.1
GeForce RTX 2080

本記事ではAnacondaやCUDA等のインストールには触れません。
Anacondaで仮想環境を作るとこから入ります。

# 環境を作る前に

### tf pose estimation のダウンロード
まず下記Githubからファイルをダウンロードして好きなところに解凍して置いておきます。
https://github.com/ildoonet/tf-pose-estimation

### Windows Subsystem for Linux をインストール
本記事の手順ではbashを使いますので、作業に入る前に使えるようにしときます。
手順は下記とかをご参考に。
https://qiita.com/Aruneko/items/c79810b0b015bebf30bb

# 環境構築
### 仮想環境の作成
Anaconda (powersell) promptで下記コマンドでpython3.6の仮想環境を作ります。
Anaconda Navigatorからも簡単に作れますが、たまに壊れるときがあるのでコマンドから作っています。

```
conda create -n myenv python=3.6 
```

python3.7でもデモを動かす分には大丈夫でした。
作った仮想環境をactvateして作業していきます。

```
conda activate myenv 
```

### ライブラリインストール
必要なライブラリを仮想環境にインストールします。
tf pose estimationが必要とするものはrequirements.txtに記載してあるのですが、その前に以下のライブラリをインストールしておくとスムーズです。
バージョンは動作確認したものを記載。
- cython 0.29.13
- numpy 1.16.4
- git 2.20.1
- swig 3.0.12
- opencv 3.3.1
- tensorflow-gpu 1.14.0　（GPU無い場合は tensorflow )

全部```conda install ```で入れました。

次に、ダウンロードしたtf-pose-estimation-masterに移動して、requirements.txtをインストールする、んですが。
その中のpycocotoolのインストールがうまくいきません。
ので、先に下記コマンドで別にpycocotoolを入れておくかrequirements.txtを書き換えておくかしないといけません。

```
pip install "git+https://github.com/philferriere/cocoapi.git#egg=pycocotools&subdirectory=PythonAPI"
```

できたらrequirements.txtに記載の必要なライブラリをまとめてインストールします。

```
pip install -r requirements.txt
```

必要なライブラリが揃いました。

あとは手順通りにやっていきます。
### C++ライブラリのビルド

```
cd tf_pose\pafprocess
swig -python -c++ pafprocess.i && python setup.py build_ext --inplace
```

### パッケージインストール

```
cd ..\..\
python setup.py install
```

### modelのダウンロード

```
cd models\graph\cmu
bash download.sh
```

環境構築はこれでOKのはず。

# Run
### tensorRTのコメントアウト
環境構築ができたので早速動かせるかと思いきや、run.pyしてみたらtensorRTが無いとerrorが出ました。
tensorRT は windows の python にはまだ対応していない為使えないみたいです。
なのでコード内で使っているところをコメントアウトします。
（最適化して速度を早くするものらしく、動かす分には無くても問題ないようです）

tf_pose/estimator.py
- 14行目　import文
- 315行目~328行目　if文

### 動かしてみる
とりあえず何も考えずカメラで試す。

```
python run_webcam.py 
```

Escで終了。

モデルは4種類用意されており、指定しない場合cmuモデルが使われます。
- cmu (trained in 656x368)
- mobilenet_thin (trained in 432x368)
- mobilenet_v2_large (trained in 432x368)
- mobilenet_v2_small (trained in 432x368)

速度はmobilenetの方が早く、精度はcmuが良い印象？
モデルを指定するときは、上記コマンドの後ろに```--model=モデル名 ```を付けて指定できます。

静止画

```
python run.py --model=mobilenet_thin --image=./images/p1.jpg
```

![Figure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487676/d825867a-8e48-0207-bb1a-5149c5bc3f4c.png)

静止画はデフォルトではこんな感じにpyplotで出力されます。
推定関節のヒートマップと、関節をつなぐために求められたクトルマップです。
Result画像を保存したい場合、run.py 54行目``` image ```がデータですので```cv2.imwrite(出力名.jpg, image)```で保存できます。


静止画とカメラは問題なく動きますが、動画用のファイルにはバグがあるみたいで推定してくれません。
下記コードで動きました。
~~（拝借したコードなのですが、どこから持ってきたかわからなくなったのでソースを見つけたら貼り付けます。）~~
[こちら](https://github.com/ildoonet/tf-pose-estimation/issues/186)で解決してくれたものです。

ついでに推定骨格を描画した動画を保存するコード入れています。
保存する場合は4行あるコメントアウトを全て外してください。

```python:run_video.py
import argparse
import logging
import time

import cv2
import numpy as np

from tf_pose.estimator import TfPoseEstimator
from tf_pose.networks import get_graph_path, model_wh

logger = logging.getLogger('TfPoseEstimator-Video')
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)

fps_time = 0


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='tf-pose-estimation Video')
    parser.add_argument('--video', type=str, default='')
    parser.add_argument('--resize', type=str, default='0x0',
                        help='if provided, resize images before they are processed. default=0x0, Recommends : 432x368 or 656x368 or 1312x736 ')
    parser.add_argument('--resize-out-ratio', type=float, default=4.0,
                        help='if provided, resize heatmaps before they are post-processed. default=1.0')
    parser.add_argument('--model', type=str, default='mobilenet_thin', help='cmu / mobilenet_thin')
    parser.add_argument('--show-process', type=bool, default=False,
                        help='for debug purpose, if enabled, speed for inference is dropped.')
    parser.add_argument('--showBG', type=bool, default=True, help='False to show skeleton only.')
    args = parser.parse_args()

    logger.debug('initialization %s : %s' % (args.model, get_graph_path(args.model)))
    
    w, h = model_wh(args.resize)
    if w > 0 and h > 0:
        e = TfPoseEstimator(get_graph_path(args.model), target_size=(w, h))
    else:
        e = TfPoseEstimator(get_graph_path(args.model), target_size=(432, 368))
            
    cap = cv2.VideoCapture(args.video)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fps = cap.get(cv2.CAP_PROP_FPS)
	
    if cap.isOpened() is False:
        print("Error opening video stream or file")
	
    # fourcc = cv2.VideoWriter_fourcc(*'DIVX')
    # writer = cv2.VideoWriter('./video/output.mp4', fourcc, fps, (width, height))
	
    while cap.isOpened():
        ret_val, image = cap.read()
        
        logger.debug('image process+')
        humans = e.inference(image, resize_to_default=(w > 0 and h > 0), upsample_size=args.resize_out_ratio)
        if not args.showBG:
            image = np.zeros(image.shape)
        
        logger.debug('postprocess+')
        image = TfPoseEstimator.draw_humans(image, humans, imgcopy=False)

        logger.debug('show+')
        cv2.putText(image, "FPS: %f" % (1.0 / (time.time() - fps_time)), (10, 10),  cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
        cv2.imshow('tf-pose-estimation result', image)
        fps_time = time.time()
        # writer.write(image)
        if cv2.waitKey(1) == 27:
            break

    cv2.destroyAllWindows()

    # writer.release()
    cap.release()

logger.debug('finished+')
```

動かすときは同様に

```
python run_video.py --video=./video.mp4
```

相変わらずエラー文出ますね、、時間があれば見直します。

# 結果
- さっきのやつ
![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487676/6b4c623d-c519-c201-79f3-2fe65d4874eb.png)

- 借りてきたふちこさんズ
![0828_144201.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487676/d1b5575d-9ec3-0740-ea84-bd9046f45bc0.png)
逆さまは以外はぱっと見認識できてるように見えますが…。
よく見ると関節の位置がちらほらずれてます。変な体勢は難しいみたいです。
これはウェブカメラのキャプチャーですが、FPSは画像のとおりcmuモデルでFPS24、mobilenet_thinモデルだとFPS30くらい出ていました。CPUだとその1/10くらい遅くなります。
ちなみに13体のふちこさんを貸していただいたのですが、全員載せられませんでした。ごめんなさい。


- サイゼにいる天使
![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487676/887995bb-3475-252e-2656-56265374a9d4.png)

カメラでも試しましたが、上半身だけはOKで肩以上がフレームアウトしていると認識してくれない感じでした。
たまに背景に居ないはずの人間を認識します。これ見ると肩が入っていることが重要なんでしょうか。

微妙な結果ばかり載せてすみません。
助けてくださった先輩方、ありがとうございます。

以上！