じゅころぐAR

AR/VRメインのブログ。時々ドローン。

DeepLabをMask R-CNNと比べてみた

前回の続きで、物体検知&セグメンテーションのライブラリ調査です。
最終的にはモバイルARで使う想定で評価しています。

↓前回
jyuko49.hatenablog.com

はじめに

Mask R-CNNをカメラでキャプチャしたリアルタイムの動画に適用したところ、すごくカクカクで思うように速度が出ませんでした。
GPU性能を活かせば速くなるらしいのですが、モバイルARで使うとなると、マシンスペックに頼るのは難しいです。

そこで代替のライブラリを探したところ、以下の動画を見つけました。

youtu.be

右下のDeepLabは、Mask R-CNNと同様に検知した物体のセグメンテーションができていて、リアルタイム動画にも追従できそうな雰囲気です。
人や車だけでなく、建物などの背景もマスクしている点も気になったので、試してみることにしました。

DeepLabの特徴

TensorFlowで実装・学習されており、オープンソースとしてGithubで公開されています。
また、Googleの紹介記事があります。

developers-jp.googleblog.com

記事内では、Semantic image segmentation(セマンティック・イメージ・セグメンテーション)と書かれており、画像を"ピクセル単位"で意味的に分割するアルゴリズムのようです。
Mask R-CNNは、画像内の物体を検知して、検知した物体をマスクしていたので、その点で若干の違いがありそうです。ここは、実際に結果を見てみるとイメージしやすいです。

DeepLabの環境設定

まず、Tensorflowのモデルが提供されているGithubからリポジトリごとcloneして、DeepLabのディレクトリに移動します。

github.com

$ git clone https://github.com/tensorflow/models.git
$ cd models/research/deeplab/

ライブラリのインストール

前回と同じで、Python3、Jupyter Notebookを使います。今回はMask R-CNNの実行時に構築した環境をそのまま使いました。

また、tensorflow、matplotlib、numpyなどのライブラリも必要になります。requirement.txtが見当たらないので、不足分はpip install [パッケージ名]で追加します。Mask R-CNNの環境を先に作ってあれば、大体インストールされていると思います。

サンプル実行

Mask R-CNNのときと同様に、Jupyter Notebookで可視化されたサンプルを動かします。

$ jupyter notebook

deeplab_demo.ipynbを選択して、"Run"をクリックしていくと処理の流れが見られます。
結果のサンプルとしては、以下のような画像が表示されます。

f:id:jyuko49:20181116083658p:plain

コードを読んでみる

重要な部分だけ抜き出して見ていきます。

コードの最後に書かれているrun_visualization()がメイン処理です。

def run_visualization(url):
  """Inferences DeepLab model and visualizes result."""
  try:
    f = urllib.request.urlopen(url)
    jpeg_str = f.read()
    original_im = Image.open(BytesIO(jpeg_str))
  except IOError:
    print('Cannot retrieve image. Please check url: ' + url)
    return

  print('running deeplab on image %s...' % url)
  resized_im, seg_map = MODEL.run(original_im)

  vis_segmentation(resized_im, seg_map)


image_url = IMAGE_URL or _SAMPLE_URL % SAMPLE_IMAGE
run_visualization(image_url)

image_urlから画像を読み込んでoriginal_imにセットし、MODEL.run()を実行すると、リサイズされた元画像resized_imとセグメンテーション結果seg_mapが得られます。
その結果をvis_segmentation()に渡して、画面に表示しています。

MODELはコードの前半で定義されているDeepLabModelクラスのインスタンスで、http://download.tensorflow.org/models/からダウンロードしたモデルを使ってTensorflowのSessionを実行しています。

MODEL = DeepLabModel(download_path)

seg_mapからマスク画像を生成する処理は、vis_segmentation()内に記述されています。

seg_image = label_to_color_image(seg_map).astype(np.uint8)
plt.imshow(seg_image)

具体的な処理はlabel_to_color_image()create_pascal_label_colormap()に書かれていますが、長くなりそうなのでここでは割愛。

テストスクリプトの作成

例によって、deeplab_demo.ipynbのコードを参考にして作りました。

静止画(ローカルファイル)

改変した箇所は以下です。

元画像を固定のURLから取得するようになっているので、ファイルパスに変更します。

#image_url = IMAGE_URL or _SAMPLE_URL % SAMPLE_IMAGE
image_path = '[ローカルのファイルパス]'
run_visualization(image_path)

Mask R-CNNのサンプルに倣って、ディレクトリ内の画像をランダムに表示する場合は以下のようにします。

import random

IMAGE_DIR = './test_image'
file_names = next(os.walk(IMAGE_DIR))[2]
image_path = os.path.join(IMAGE_DIR, random.choice(file_names))
run_visualization(image_path)

image_urlで使っていた定数は要らないので、コメントアウトします。

'''
SAMPLE_IMAGE = 'image1'  # @param ['image1', 'image2', 'image3']
IMAGE_URL = ''  #@param {type:"string"}

_SAMPLE_URL = ('https://github.com/tensorflow/models/blob/master/research/'
               'deeplab/g3doc/img/%s.jpg?raw=true')
'''

最後に、画像の読み込み部分を変更します。
ローカルファイルを読み込む場合、 Image.open()にパスをそのまま渡せばOKです。引数名はurlじゃなくてpathだけど直してません。

def run_visualization(url):
  """Inferences DeepLab model and visualizes result."""
  try:
    # f = urllib.request.urlopen(url)
    # jpeg_str = f.read()
    # original_im = Image.open(BytesIO(jpeg_str))
    original_im = Image.open(url)
  except IOError:
    print('Cannot retrieve image. Please check url: ' + url)
    return

完成版のコードサンプルです。

gist.github.com

リアルタイム動画

OpenCVVideoCapture()を使って、取得したフレーム画像にマスクをかける処理をループさせれば実現できます。

import cv2
import time

capure = cv2.VideoCapture(0)

def run_visualization():
  while(True):
    ret, frame = capure.read()
    original_im = cv2.resize(frame,(480,320))

    start_time = time.time()

    resized_im, seg_map = MODEL.run(original_im)
    vis_segmentation(resized_im, seg_map)

    elapsed_time = time.time() - start_time
    print(elapsed_time)

    if cv2.waitKey(1) == 27:
      break
  capure.release()
  cv2.destroyAllWindows()

run_visualization()

画像の表示部分もOpenCVに変更しています。かなりシンプルに書けます。

def vis_segmentation(image, seg_map):
  seg_image = label_to_color_image(seg_map).astype(np.uint8)

  result = cv2.add(image, seg_image)
  cv2.imshow("camera window", result)

動かすとこんな感じ。

完成版のサンプルコードはこちら。

gist.github.com

性能比較

実行環境

処理結果は4パターンの画像をそれぞれ比較。 処理時間の方はMacbookのカメラでキャプチャしたリアルタイムの映像を480 x 320にリサイズして連続処理した際の時間を計測。

Mask R-CNN

処理結果

f:id:jyuko49:20181116230323p:plain f:id:jyuko49:20181112154021p:plain f:id:jyuko49:20181112153856p:plain f:id:jyuko49:20181112154027p:plain

検知した物体をかなり正確にセグメンテーションできています。

処理時間(sec)

2.493619203567505
2.4120068550109863
2.4120633602142334
2.4788520336151123
2.5836117267608643
...

1回の検知に2.4-2.5 secかかっています。
ということは、フレームレートは0.4fps。1fpsを切っているため、リアルタイム動画ではラグが相当大きいです。

DeepLab

処理結果

f:id:jyuko49:20181116233707p:plain f:id:jyuko49:20181116234933p:plain f:id:jyuko49:20181116235210p:plain f:id:jyuko49:20181116235221p:plain

正面や横を向いている物体はキレイに検出できていますが、後ろ向きや座っている人の検出がイマイチです。この点は追加学習させれば改善する部分なのかもしれません。
明らかな差異としては物体の形状が途切れやすい点で、画像のピクセル単位でセグメンテーションを行なっている結果のようです。

処理時間(sec)

0.42249274253845215
0.45106005668640137
0.44458889961242676
0.42955899238586426
0.4366567134857178
...

0.40-0.45 sec前後なので、Mask R-CNNの1/5程度の処理時間です。決して速くはないですが、2.5fpsくらいは出ます。
冒頭で紹介したYoutubeの動画内でも平均2.5fpsと表示されているので、妥当な結果と思われます。

結果まとめ

  • リアルタイム性を重視するならDeepLab
  • リアルタイム性が重要でなく、精度重視ならMask R-CNN

という使い分けになりそうです。

今後

DeepLabの処理速度であれば、モバイルARでもなんとか使えそうという感触です。
実際に同様の研究はされていて、モバイル相当のスペックでもリアルタイム動画に追従はできる様子。

ai.googleblog.com

静止画のRGBに加えて前フレームで生成したマスクを使って、高速に処理しつつ精度を上げてるんですね。なるほど。

一旦は学習済みモデルをそのまま使うことにして、TensorFlowSharp in Unityを先に試そうかなと思っています。