Python
DeepLearning
Movidius
SemanticSegmentation
OpenVINO

CPU単体で無理やり RealTime Semantic Segmentaion 【その2】 [4-5 FPS / CPU only] DeeplabV3+MobilenetV2 (Core i7なら11-12 FPS)

OpenVINO-DeeplabV3 GitHub stars3
I wrote an English translation, here

◆ 前回記事

CPU単体で無理やり RealTime Semantic Segmentaion その1 [1 FPS / CPU only]

◆ はじめに

GithubとForumでフィードバックをガンガン頂けるので助かる。
世間に必要とされているか、必要とされていないか、がよく分かる。
Commitした直後に何者かが既に :star: Star を付けてくれている。。。
なんか、動きを監視されてるみたいでオジサン怖い。。。

下記は Core i7 単体でのスピード
ezgif.com-optimize (3).gif

さておき、今回も1分読了可能な記事を目指す。
今回はカップラーメン1個で十分。
昨日投稿の実装から、 CPU単体でのセマンティック・セグメンテーション を4〜5倍高速化した。
今回は、DeeplabV3 + MobilenetV2 のモデルを使用。
DeeplabV3に関しては、 mine820さん が DeepLab v3+(意訳) - mine820 - Qiita に英語論文の日本語訳をまとめてくださっている。
OpenVINOの概要や各種キッティングの手順、性能評価の結果(国外エンジニアとの協力調査) は 前々回記事 に記載しているため、そちらを参照願う。

この記事の実装を行うと、シングルボードコンピュータのCPU単体 で、下図のようなスピードでリアルタイムにセグメンテーションできる。
Intel Neural Compute Stick も GPU のどちらも使用せずに CPU単体 でセグメンテーションする 男気実装(改)
今回は、動画再生とUSBカメラ撮影の2種類を試行した。

十分速い!?
もうね、GPUいらなくね?

すいません、、、いくらなんでも言い過ぎました。
ただ、GPUとか外付けVPUとか小細工しなくても、シングルボードコンピュータ単体で、セマンティック・セグメンテーションが使い物になりそうな雰囲気ではあります。

↓ 画像をクリックすると Youtube で再生できる。

東南アジア系? ちょこまかと動きがせわしないので微妙な感じ。
ezgif.com-optimize.gif
本人初登場です。 綺麗にセグメントされている。
ezgif.com-optimize (1).gif
車、ちゃんと拾えてるね。 バスに対する努力の色が見えるw
ezgif.com-optimize (2).gif

◆ 環境

  • LattePanda Alpha (Intel 7th Core m3-7y30)
  • Ubuntu 16.04 x86_64
  • OpenVINO toolkit 2018 R4 (2018.4.420)
  • Python 3.5
  • OpenCV 3.4.3
  • PIL
  • DeeplabV3 + MobliletV2 (Pascal VOC 2012)

◆ 実装

従前のとおり、推論デバイスは CPU GPU NCS1/NCS2 から選択可能。
Github - OpenVINO-DeeplabV3 - PINTO0309 で、OpenVINO本体以外のリソース一式を調達可能な状態にしておいた。
CPU用カーネル拡張ライブラリを取り込まないと正常に動作しない、が、 ".so"ファイル の取り込み動作そのものはロジック中に実装してあるため、気にする必要は無い。

CPU単体のリアルタイムセグメンテーションの実装
#!/usr/bin/env python

import sys
import os
from argparse import ArgumentParser
import numpy as np
import cv2
import time
from PIL import Image
import tensorflow as tf
from tensorflow.python.platform import gfile
from openvino.inference_engine import IENetwork, IEPlugin

class _model_preprocess():
    def __init__(self):
        graph = tf.Graph()
        f_handle = gfile.FastGFile("pbmodels/frozen_inference_graph.pb", "rb")
        graph_def = tf.GraphDef.FromString(f_handle.read())
        with graph.as_default():
            tf.import_graph_def(graph_def, name='')
        self. sess = tf.Session(graph=graph)

    def _pre_process(self, image):
        seg_map = self.sess.run("sub_7:0", feed_dict={"ImageTensor:0": [image]})
        return seg_map


class _model_postprocess():
    def __init__(self):
        graph = tf.Graph()
        f_handle = gfile.FastGFile("pbmodels/frozen_inference_graph.pb", "rb")
        graph_def = tf.GraphDef.FromString(f_handle.read())
        with graph.as_default():
            new_input = tf.placeholder(tf.int64, shape=(1, 513, 513), name="new_input")
            tf.import_graph_def(graph_def, input_map={"ArgMax:0": new_input}, name='')
        self.sess = tf.Session(graph=graph)

    def _post_process(self, image_ir, image):
        seg_map = self.sess.run("SemanticPredictions:0", feed_dict={"ImageTensor:0": [image], "new_input:0": np.int64(image_ir)})
        return seg_map


_pre = _model_preprocess()
_post = _model_postprocess()


def build_argparser():
    parser = ArgumentParser()
    parser.add_argument("-pp", "--plugin_dir", help="Path to a plugin folder", type=str, default=None)
    parser.add_argument("-d", "--device", help="Specify the target device to infer on; CPU, GPU, FPGA or MYRIAD is acceptable. Sample will look for a suitable plugin for device specified (CPU by default)", default="CPU", type=str)
    parser.add_argument("-nt", "--number_top", help="Number of top results", default=10, type=int)
    parser.add_argument("-pc", "--performance", help="Enables per-layer performance report", action='store_true')

    return parser


def main_IE_infer():
    camera_width = 320
    camera_height = 240
    m_input_size=513
    fps = ""
    framepos = 0
    frame_count = 0
    vidfps = 0
    skip_frame = 0
    elapsedTime = 0

    args = build_argparser().parse_args()
    model_xml = "lrmodels/FP32/frozen_inference_graph.xml"
    model_bin = os.path.splitext(model_xml)[0] + ".bin"

    seg_image = Image.open("data/input/009649.png")
    palette = seg_image.getpalette() # Get a color palette

    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FPS, 10)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, camera_width)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_height)

    #cap = cv2.VideoCapture("data/input/testvideo2.mp4")
    #camera_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    #camera_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    #frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    #vidfps = int(cap.get(cv2.CAP_PROP_FPS))
    #print("videosFrameCount =", str(frame_count))
    #print("videosFPS =", str(vidfps))

    time.sleep(1)

    plugin = IEPlugin(device=args.device, plugin_dirs=args.plugin_dir)
    if "CPU" in args.device:
        plugin.add_cpu_extension("lib/libcpu_extension.so")
    if args.performance:
        plugin.set_config({"PERF_COUNT": "YES"})
    # Read IR
    net = IENetwork.from_ir(model=model_xml, weights=model_bin)
    input_blob = next(iter(net.inputs))
    exec_net = plugin.load(network=net)

    while cap.isOpened():
        t1 = time.time()

        #cap.set(cv2.CAP_PROP_POS_FRAMES, framepos)     # Uncomment only when playing video files

        ret, image = cap.read()
        if not ret:
            break

        ratio = 1.0 * m_input_size / max(image.shape[0], image.shape[1])
        shrink_size = (int(ratio * image.shape[1]), int(ratio * image.shape[0]))
        image = cv2.resize(image, shrink_size, interpolation=cv2.INTER_CUBIC)

        prepimg = _pre._pre_process(image)
        prepimg = prepimg.transpose((0, 3, 1, 2))  #NHWC to NCHW
        res = exec_net.infer(inputs={input_blob: prepimg})
        result = _post._post_process(res["ArgMax/Squeeze"], image)[0]

        outputimg = Image.fromarray(np.uint8(result), mode="P")
        outputimg.putpalette(palette)
        outputimg = outputimg.convert("RGB")
        outputimg = np.asarray(outputimg)
        outputimg = cv2.cvtColor(outputimg, cv2.COLOR_RGB2BGR)
        outputimg = cv2.addWeighted(image, 1.0, outputimg, 0.9, 0)
        outputimg = cv2.resize(outputimg, (camera_width, camera_height))

        cv2.putText(outputimg, fps, (camera_width-180,15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (38,0,255), 1, cv2.LINE_AA)
        cv2.imshow("Result", outputimg)

        if cv2.waitKey(1)&0xFF == ord('q'):
            break
        elapsedTime = time.time() - t1
        fps = "(Playback) {:.1f} FPS".format(1/elapsedTime)

        # frame skip, video file only
        #skip_frame = int((vidfps - int(1/elapsedTime)) / int(1/elapsedTime))
        #framepos += skip_frame

    cv2.destroyAllWindows()
    del net
    del exec_net
    del plugin

if __name__ == '__main__':
    sys.exit(main_IE_infer() or 0)

たったのこれだけ。
本当は、更にコレの5倍の性能が欲しい。
ただ、現時点では Core i7 とか第8世代以降の高火力CPUを使用するのは禁じ手。
パフォーマンスを確保したうえで、消費電力も抑えなければ意味が無い。
あくまで、モバイルやエッジコンピュータなどの非力な環境で、 スタンドアロン かつ 高速 に動かすことが目的なので。
Celeron などの超旧世代を使用する場合は、Neural Compute Stick 2 を使用することによるメリットがあることが、検証により明らかになってきた。
次は、シカゴのBobさんが $80 の Atom機 で検証してくれることになっている。
公式フォーラムでの議論はコチラ

今回は、OpenVINO非対応のレイヤー部をロジック内で明示的に Tensorflow 側へオフロードした。
この手法を使用すれば、セグメンテーションに限らず、既存のほとんどのモデルを高速化できると考える。
まぁ、実装上美しくはないかもしれないけど。
GithubのREADMEは少しずつ厚みを増す予定。

:new: 2018.12.03 一応追記。 Core i7 CPU単独 なら 11 - 12 FPS のパフォーマンスが出る。
https://youtu.be/TjiH2dMltl4