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


この投稿は自分のブログの転載です。
keras-deeplab-v3-plusで人だけとってみる - 機械音痴な情報系

Semantic Segmentationで人をとってきたいのでkeras-deeplab-v3-plusを使ってみました。
勿論本来は人以外も色々なものをとってこれます。
keras-deeplab-v3-plus - Github

準備

# 仮想環境の準備
$ 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を使って画像を出力

ここからは自分用にカスタマイズしていく。
多クラス分類だが、人だけとれれば良い。

今回は以下の画像を使用します(自分)。

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
0が背景で、15が人と仮定する。

追記
というかmodels.pydef Deeplabv3(weights='pascal_voc', ...という行もあるのでこれで合っていると思われる。

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などで処理している?

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

入力の際の前処理

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

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

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

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

ソースコード

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

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)

出力結果

背景はデフォルトで黒、あとは人だけ取り出してみた。
上手いこととれた!
これで画像を重ねて背景変えるなり色々出来そう。

他の画像では以下のようになりました。

因みにこの人を横にすると人だと認識しない。

BGR→RGBにしてみる

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

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

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の方が良さげではある。