AR x AIで使えそうなMask R-CNNというOSSを教えてもらったので動かしてみました。
Mask R-CNNでできること
画像の中の物体(人とか車とか)を検知(Object Detection)して、検知した物体を背景と分割(Object Segmentation)する形でマスクしてくれます。
これとよく似た画像って、どこかで見たことがあるような?
「Niantic Real World Platform」の記事で、現実と溶け込んだARの要素技術として物体検知が紹介されていました。
リアルタイムに物体検知を行いつつ、物体との距離も推定してマスクを行うことができれば、リアルなオクルージョンが実現できそうです。
AR以外の用途だと、写真投稿サイトで写り込んだ人物をぼかしたり、自動運転やナビゲーションで人を避けたり、とか。
環境構築
GithubのREADMEをざっくり読むと、Jupyter Notebookを使ってステップごとの結果が可視化できるらしい。
また、物体検知用に提供されているMS COCOというデータセットを使うらしい。MSはMicrosoftの略。
COCO - Common Objects in Context
Jupyter Notebookのインストール
$ pip install jupyter
それでは早速入れてみ・・・エラーがいっぱい出ました!
メッセージを眺めるとライブラリのバージョンがどうのこうの言っているので、Pythonのバージョンを確認したところ2系でした。pyenv使っててglobalは古いままだった・・・。
$ python --version Python 2.7.10
Mask_R_CNNはPython3で動作と明記されているので、3系に切り替えます。
pyenv versions
でインストール済みのバージョンを確認。3.6.6があったので、localのバージョンを切り替えて再度実行。
$ pyenv local 3.6.6 $ pip install jupyter
今度はインストールできたので、以下のコマンドで実行。
$ jupyter notebook
何か表示されました。
で、ここからどうすれば?
必要ライブラリのインストール
Getting Startedを見ると、とりあえずdemo.ipynbを動かしてみるのがよいらしい。
拡張子が.ipynb
になっているファイルが、Jupyter Notebookの実行ファイルのようです。JSONにPythonのコードを突っ込んだようなフォーマットになってます。
Jupyter Notebookを起動して"samples > demo.ipynb"をクリックすると、以下のような画面が表示されました。
Runボタンをクリックしていくと次の処理に進みますが、途中でエラーメッセージが出ます。
ModuleNotFoundError: No module named 'numpy'
どうやらライブラリが足りない様子です。
これらのインストール方法はREADMEの下の方に書いてありました(もうちょっと上の方に書いておいて欲しさ)
$ pip install -r requirements.txt $ python setup.py install
requirements.txtにパッケージ名が列記されていて、上記コマンドを実行すれば必要なライブラリがインストールされます。
COCO APIのインストール
前述の手順でインストールしただけだと、以下のエラーが解消されません。
ModuleNotFoundError: No module named 'pycocotools'
上記はCOCO APIのインストールで入るようです。
Githubからcloneして、とりあえずプロジェクトフォルダ直下に配置。Python APIをインストールします。
$ git clone https://github.com/cocodataset/cocoapi.git $ cd coco/PythonAPI $ python setup.py install
これで環境は構築できたはず。
Runコマンドをポチポチしていくと、最終的に以下のような画像が表示されます。(画像は毎回変わる)
コードを読んでみる
サンプルスクリプトが何をやっているのか、ざっと見てみてみます。
In[1]
最初のブロックでは、ライブラリのインポートと定数を定義しているだけ。
import os import sys import random import math import numpy as np import skimage.io import matplotlib import matplotlib.pyplot as plt # Root directory of the project ROOT_DIR = os.path.abspath("../") # Import Mask RCNN sys.path.append(ROOT_DIR) # To find local version of the library from mrcnn import utils import mrcnn.model as modellib from mrcnn import visualize # Import COCO config sys.path.append(os.path.join(ROOT_DIR, "samples/coco/")) # To find local version import coco %matplotlib inline # Directory to save logs and trained model MODEL_DIR = os.path.join(ROOT_DIR, "logs") # Local path to trained weights file COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5") # Download COCO trained weights from Releases if needed if not os.path.exists(COCO_MODEL_PATH): utils.download_trained_weights(COCO_MODEL_PATH) # Directory of images to run detection on IMAGE_DIR = os.path.join(ROOT_DIR, "images")
mask_rcnn_coco.h5
が学習済みモデルで、ローカルになければダウンロードしてくるようになっている。
IMAGE_DIR
が物体検知を行う画像フォルダのパス。
In[2]: Configurations
CocoConfigクラスを継承して、InferenceConfigクラスで一部プロパティを上書きしている。
class InferenceConfig(coco.CocoConfig): # Set batch size to 1 since we'll be running inference on # one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU GPU_COUNT = 1 IMAGES_PER_GPU = 1 config = InferenceConfig() config.display()
In[3]: Create Model and Load Trained Weights
モデルの生成部分です。先程作成したconfig
を渡してます。
# Create model object in inference mode. model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=config) # Load weights trained on MS-COCO model.load_weights(COCO_MODEL_PATH, by_name=True)
コードの最初の方に、
import mrcnn.model as modellib
とあるので、'mrcnn/model.py'が本体のようです。
In[4]: Class Names
検知した物体のクラス名の定義。ラベルを付けるときに使う。
# COCO Class names # Index of the class in the list is its ID. For example, to get ID of # the teddy bear class, use: class_names.index('teddy bear') class_names = ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']
データセットが参照できれば以下のコードで置き換えられるらしい(試してないです)
# Load COCO dataset dataset = coco.CocoDataset() dataset.load_coco(COCO_DIR, "train") dataset.prepare() # Print class names print(dataset.class_names)
In[5]: Run Object Detection
ここがメインの処理ですね。
# Load a random image from the images folder file_names = next(os.walk(IMAGE_DIR))[2] image = skimage.io.imread(os.path.join(IMAGE_DIR, random.choice(file_names))) # Run detection results = model.detect([image], verbose=1) # Visualize results r = results[0] visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'], class_names, r['scores'])
画像データをファイルから取得してmodel.detect()
を行うと、resultsに結果のデータが入ってくるので、それをvisualize.display_instances()
という処理に渡しています。
visualize
はコードの最初の方でimportしてあるライブラリで、mrcnn/visualize.py
に実際の処理が書いてあります。
from mrcnn import visualize
visualize.py
内の処理はコード書きながら理解することにします。
使ってみる
サンプルと同じことをやるだけですが、自分でコードを書いてみます。
.ipynb
形式はコーディングしづらいので、test.py
を作成して、In[1]〜[5]のコードをコピペしていきます。
ファイルができたら、とりあえず以下の3ヶ所だけ変更しておきます。
#%matplotlib inline #よくわかんないけどエラーになるのでコメントアウト
ROOT_DIR = os.path.abspath("../") #実行ファイルからルートへの相対パスに直す
# 画像を加工&表示する部分は自作するのでコメントアウト #result = visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'], # class_names, r['scores'])
コマンドラインで実行します。
$ python test.py
バックエンドがMacOSだとなんちゃらみたいなmatplotlibのエラーが出た場合、以下の記事で解決しました。
結果の画像を表示する
skimage.ioとmatplotlib.pyplotがインポートされているので、これを使います。
result_image = image.copy() skimage.io.imshow(result_image) plt.show()
こんな感じで表示されるはず。
ウィンドウを閉じると、コマンドラインで実行したプログラムも終了します。
検知結果の中身を確認する
resultsに何が入っているか見たいので、printしてみます。
print(r['rois']) print(r['masks']) print(r['class_ids']) print(r['scores'])
rois
[[ 38 265 493 533] [163 482 229 530] [250 31 587 782]]
検出した物体を囲う矩形(Bounding box)の頂点座標が入っています。配列の要素数が検知した物体の数になります。
masks
[[[False False False] [False False False] [False False False] ... [False False False] [False False False] [False False False]] [[False False False] [False False False] [False False False] ... (長いので省略) [False False False] [False False False] [False False False]]]
rois内でマスクをかける範囲が配列で入っている。詳しいところはよくわかってない。
class_ids
[ 1 47 61]
検知した物体のクラスに対応するIDが入っています。
class_namesを使うことでラベル文字列を取得できます。
for id in r['class_ids']: print(class_names[id])
person banana dining table
scores
[0.99927574 0.9297102 0.91985023]
検知の精度がスコアとして入っています。数値が1に近いほど、検出の正確性が高いはず。
コードを書く
resultsの中身がわかったので、resultsを使って結果を描画していきます。
前提として、
for roi in r['rois']: # 矩形の描画 for mask in r['masks']: # マスクの描画
のようなコーディングだと、ループがたくさんになるので、
N = r['rois'].shape[0] #配列の大きさ(要素数) for i in range(N): # r['rois'][i]を描画 # r['masks'][i]を描画
とした方がループが1回で速い(たぶん)
矩形の描画
visualize.draw_box(image, box, color)
でimageにboxを重ねることができます。
result_image = visualize.draw_box(result_image, r['rois'][i], rgb)
もしくはOpenCVを使って、
import cv2 result_image = cv2.rectangle(result_image, (r['rois'][i][1], r['rois'][i][0]), ( r['rois'][i][3], r['rois'][i][2]), rgb)
みたいな実装でもできます。
ラベルとスコアの描画
OpenCVを使いました。位置は対応する矩形(r['rois'][i])の左上の座標を使っています。
font = cv2.FONT_HERSHEY_SIMPLEX text = class_names[r['class_ids'][i]] + ':' + str(r['scores'][i]) result_image = cv2.putText(result_image, text, (r['rois'][i][1],r['rois'][i][0]), font, 0.8, rgb, 2, cv2.LINE_AA)
マスクの描画
ここはちょっと難しいので、visualize.apply_mask(image, mask, color)
を使いました。
mask = r['masks'][:, :, i] result_image = visualize.apply_mask(result_image, mask, color)
実行結果
精巧なフィギュアやイラストは、人として認識されるんですね。
他にも、忍ちゃんの髪がバナナと判別されたり、パドックのOの字がフリスビーと判別されたりしていますが、ARで使う場合は、物体が何であるかの検知よりも物体がそこにあることを検知してくれた方が都合良さそうです。
検知(判別はともかく)の精度という意味では、パドックの写真で後ろ向きの人物をすべて検出していてすごい。
- マスクあり版
大体わかった(わかってない) pic.twitter.com/uzl0MRtwFw
— jyuko (@jyuko49) 2018年11月12日
- マスクなし版
ここまでできた。あとマスク処理。 pic.twitter.com/aZQwhg3RF6
— jyuko (@jyuko49) 2018年11月12日
マスクなし版はGoogleのVision APIをローカルで実行している感じですね。
複数のオブジェクトを検出する | Cloud Vision API ドキュメント | Google Cloud
完成版のコード
Gistにアップしました。
Mask_RCNN直下のファイルにコピペして、test_imageフォルダを作って画像を置けば動くはず。
今後
大体の動作はわかったので、AR開発で使ってみたいです。
というか、これからのARはAIありきになっていきそう。
リアルタイムの動画でテストする
各フレームに対して同様の処理を行えばよいので、実装自体は難しくないと思う。
フレームレートがどれくらい落ちるかの方が懸念。
Unityで使えるか調べる
ARアプリで使うには、学習済みモデルをUnityで動かしたい。
mask_rcnn_coco.h5
はKeras用のファイルのようなので、TensorFlowで使えるようProtocol Bufferで保存して、TensorFlowSharpでごにょごにょする感じでしょうか。
オクルージョンに使う
マスクしている箇所をオクルージョンするシェーダを書けばいいはず。
書ければ。
他の学習モデルも試す
物体検知のモデルとしては、R-CNN、YOLO、SSD(Solid State Driveではない)が有名らしい。
以下の記事を見ると、R-CNNよりもYOLOの方がFPSが出てるので、同様のマスク処理ができるなら両方試した方がいいかもしれない。
追加学習させる
今回は学習済みモデルをそのまま使いましたが、自分で学習データを追加して用途に応じてチューニングできた方が便利です。が、教師データの準備とか大変そうなので優先度は低め。
3D対応を試す
READMEにて、3Dに対応したデータセットとしてMatterport3Dが紹介されています。
お問い合わせしないとダウンロードできないので試せませんでしたが、3Dでの物体検知は興味があります。
また、別のモデルで単眼カメラの画像からデプスを推定するvid2depthというのもあったので、これと物体検知を組み合わせて使うのも良さそう。
models/research/vid2depth at master · tensorflow/models · GitHub
↓続きです
jyuko49.hatenablog.com