keras-deeplab-v3-plusで人だけとってみる(ソース有り)

この投稿は自分のブログの転載です。
[keras-deeplab-v3-plusで人だけとってみる - 機械音痴な情報系](http://saneeeatsu.hatenablog.com/entry/keras-deeplab-v3-plus)

Semantic Segmentationで人をとってきたいのでkeras-deeplab-v3-plusを使ってみました。
勿論本来は人以外も色々なものをとってこれます。
[keras-deeplab-v3-plus - Github](https://github.com/bonlime/keras-deeplab-v3-plus)

<img width="680" alt="Screen Shot 2018-09-07 at 14.37.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/130886/8f3cd8d3-9617-af51-6ca0-400162fb0ffd.png">


## 準備
```
# 仮想環境の準備
$ conda create -n keras-deeplab-v3-plus
$ source activate keras-deeplab-v3-plus

# モジュールインストール
$ conda install tqdm
$ conda install numpy
$ conda install keras

# 重みダウンロード
$ python extract_weights.py 
$ python load_weights.py 
```

## Jupyterを使って画像を出力
ここからは自分用にカスタマイズしていく。
多クラス分類だが、人だけとれれば良い。

今回は以下の画像を使用します(自分)。
![frame.jpg](https://qiita-image-store.s3.amazonaws.com/0/130886/09a6aaa2-f047-2ed7-140a-34ff603c4491.jpeg)


```python
import model
import cv2

img = cv2.imread('./test.jpg')
img = cv2.resize(img, (512, 512)) # imgは0〜255の値をとる

model_dlv3 = model.Deeplabv3()

predicted = model_dlv3.predict(img[np.newaxis, ...])
print(predicted.shape) 
# (1, 512, 512, 21)
```

21クラス分類だが、どれが人なのか。
閾値がどうなっているのか知りたい。

が、プログラム内に無さげ。
「semantic segmentation 21 class names」でぐぐる。

一番上にぽいのが出てくる。
[pascal-voc-classes.txt(NVIDIA/DIGITS) - Gtihub](https://github.com/NVIDIA/DIGITS/blob/master/examples/semantic-segmentation/pascal-voc-classes.txt)
0が背景で、15が人と仮定する。

★<font color="Red">**追記**</font>
というか```models.py```に```def Deeplabv3(weights='pascal_voc', ... ```という行もあるのでこれで合っていると思われる。

```python
person_score = predicted[0, :, : ,15]
back_score = predicted[0, :, :, 0]

mask = (person_score > back_score).astype("uint8") * 255
cv2.imwrite("test.jpg", mask)
```

出力してみると真っ黒。
考えられる原因は…

1. OpenCVはBGRだがこれはRGBを使っている？
2. 0〜255ではなく-1〜1などで処理している？

★<font color="Red">**追記**</font>
入力画像の大きさが小さすぎた&人が横になっていたのが原因でした。
横になっていた際の出力例、RGBに変換した画像は下の方にあります。


## 入力の際の前処理
ところで、これはモデルとしてMobileNetv2を使用していた。
画像の前処理はどうやってるんだろう？
Kerasがmobilenetv2を提供していて、ResNetなどもpreprocess_input的な関数は提供している（らしい）。
ということで調べてみる。

こういう当たりが付けられるの強い。
といってもここらへん全部友人がやってくれた。圧倒的人任せ。

「mobilenetv2 preprocess keras」でぐぐると以下のサイトが見つかる。
[mobilenet_v2.py(keras-team/keras) - Github](https://github.com/keras-team/keras/blob/master/keras/applications/mobilenet_v2.py)
「preprocess_input」というのがあるのでそれを使ってみよう。


```python
import cv2
import numpy as np

import model

from keras.applications.mobilenetv2 import preprocess_input

img = preprocess_input(cv2.imread('./test.jpg')) ## ここでエラー
img = cv2.resize(img, (512, 512))
```

エラー発生

```
TypeError: ufunc 'true_divide' output (typecode 'd') could not be coerced to provided output parameter (typecode 'B') according to the casting rule ''same_kind''
```

以下のサイトを参考にして修正。

[TypeError: ufunc 'true_divide' output (typecode 'd') could not be coerced to provided output parameter (typecode 'B') according to the casting rule ''same_kind'' #8635](https://github.com/keras-team/keras/issues/8635)


## ソースコード
最終的に以下のようなコードに。

```python
import cv2
import numpy as np

import model

from keras.applications.mobilenetv2 import preprocess_input

img = preprocess_input(cv2.imread('./test.jpg').astype("float"))

img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (512, 512))

model_dlv3 = model.Deeplabv3()
predicted = model_dlv3.predict(img[np.newaxis, ...])

person_score = predicted[0, :, :, 15]
back_score = predicted[0, :, :, 0]

mask = (person_score > back_score).astype("uint8") * 255

cv2.imwrite("./person_musk.jpg", mask)
```


## 出力結果
背景はデフォルトで黒、あとは人だけ取り出してみた。
上手いこととれた！
これで画像を重ねて背景変えるなり色々出来そう。
![person_musk.jpg](https://qiita-image-store.s3.amazonaws.com/0/130886/b72400c8-3f81-2660-0897-8a436eb0b29a.jpeg)

他の画像では以下のようになりました。
<img width="510" alt="20180820183446.png" src="https://qiita-image-store.s3.amazonaws.com/0/130886/f2c7e8a6-10dd-8347-e42c-1eda62896767.png">

因みにこの人を横にすると人だと認識しない。
<img width="510" alt="20180820183428.png" src="https://qiita-image-store.s3.amazonaws.com/0/130886/6633ec3d-6227-edb0-29f4-03f81afffd6d.png">

## BGR→RGBにしてみる
OpenCVではデフォルトでBGRで扱うが、もしこのアーキテクチャではRGBで扱っていて、更に人を肌の色などで認識している場合、精度が異なってくる。
ってことで試す。

と言っても1行追加するだけだけど。

```python
img = preprocess_input(cv2.imread('./imgs/R0_L0_ono_001360.jpg').astype("float"))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 追加行
img = cv2.resize(img, (512, 512))
```

元画像を載せていないのでよくわからないと思うが、本来白色になってほしいところ(スカートの白色の部分)が白色にきちんとなっているしRGBの方が良さげではある。
<img width="510" alt="20180820184540.png" src="https://qiita-image-store.s3.amazonaws.com/0/130886/43659673-5c31-0c39-2390-c572367a7e10.png">