16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「がんばる人のための画像検査機 presented by shinmura0」をOpenVINOで異次元のスピードにパワーアップして異常検出 (CPUのみ 又は Intel HD Graphics 615) その1

Last updated at Posted at 2019-02-16

Keras-OneClassAnomalyDetection GitHub stars

はじめに

@shinmura0 さんの 「がんばる人のための画像検査機」 を30倍以上にスピードアップしました。
なお、 Githubへの引用 は、ご本人様の承諾を得たうえで行っております。
shinmura0 さんは理論と実装、 私はパフォーマンスチューニング、 と割りきっております。

かくして、スピード狂のオジサンは更なる推論スピードの向上を求めて旅立つのであった。。。

  • デバイス
    • 【前】 RaspberryPi3 (armv7l) ---> 【後】 LattePanda Alpha (Core m3)
  • 推論用フレームワーク
    • 【前】 Keras (バックエンド:Tensorflow) ---> 【後】 OpenVINO

環境

  • LattePanda Alpha (シングルボードコンピュータ) + Ubuntu 16.04
  • USB Camera (Playstationeye)
  • OpenVINO R5 2018.5.445
  • Numpy 1.15.4
  • scikit-learn 0.19.2+

いきなり結果

もはや、カメラの撮影スピードが推論スピードに負けてしまいます。
320x240 (QVGA) の解像度では、カメラの撮影スピードの上限が 180 FPS です。
640x480 (VGA) の解像度では、カメラの撮影スピードの上限が 60 FPS です。
1240x780 (HD) 60 FPS 対応のカメラも所有していますが、撮影が面倒だったため今回はテストをしていません。
ただ、高解像度でもかなり高速に推論できることでしょう。

200 FPS 近い数字が見えます。
なお、残念ながら、RaspberryPi3 + NCS2 の組み合わせでは、APIのバグで正常に動作しませんでした。

320x240_CPU_Core_m3
$ python3 main_openvino.py -cfps 150 -cwd 320 -cht 240

Youtube: https://youtu.be/8GhYysCBhq4
01.gif

640x480_CPU_Core_m3
$ python3 main_openvino.py -cfps 60 -cwd 640 -cht 480

Youtube: https://youtu.be/FplYoZfHx3A
02.gif

320x240_GPU_Intel_HD_Graphics_615
$ python3 main_openvino.py -cfps 150 -cwd 320 -cht 240 -d GPU

Youtube: https://youtu.be/d5KTGv7K7II
03.gif

640x480_GPU_Intel_HD_Graphics_615
$ python3 main_openvino.py -cfps 60 -cwd 640 -cht 480 -d GPU

Youtube: https://youtu.be/hg1kmPA3TcQ
04.gif

導入手順

Github - Keras-OneClassAnomalyDetection - 10-2. How to use をご覧ください。

異常検出モデルのコンバージョン

OpenVINOのモデルへ変換する手順を少し工夫しました。
ストレートに Keras (.h5) から OpenVINO (.bin) へ変換するのではなく、一度、 Tensorflow (.pb) へ変換してから OpenVINO へ変換しています。

モデル変換フロー
Keras → Tensorflow → OpenVINO

詳しくは、 Github - Keras-OneClassAnomalyDetection - 13. Model Convert をご覧ください。

ロジック

下記のように、たったのこれだけです。 素晴らしくシンプル。
shinmura0 さんに感謝。

main_openvino.py
import cv2
import time
import os
import sys
import numpy as np
import argparse
import platform
from sklearn.neighbors import LocalOutlierFactor
from sklearn.preprocessing import MinMaxScaler
from sklearn.externals import joblib
from keras.models import model_from_json
from keras import backend as K
from openvino.inference_engine import IENetwork, IEPlugin

def main(camera_FPS, camera_width, camera_height, inference_scale, threshold, device):

    path = "pictures/"
    if not os.path.exists(path):
        os.mkdir(path)

    model_path = "OneClassAnomalyDetection-RaspberryPi3/DOC/model/" 
    if os.path.exists(model_path):
        # LOF
        print("LOF model building...")
        x_train = np.loadtxt(model_path + "train.csv",delimiter=",")

        ms = MinMaxScaler()
        x_train = ms.fit_transform(x_train)

        # fit the LOF model
        clf = LocalOutlierFactor(n_neighbors=5)
        clf.fit(x_train)

        # DOC
        print("DOC Model loading...")
        if device == "MYRIAD":
            model_xml="irmodels/tensorflow/FP16/weights.xml"
            model_bin="irmodels/tensorflow/FP16/weights.bin"
        else:
            model_xml="irmodels/tensorflow/FP32/weights.xml"
            model_bin="irmodels/tensorflow/FP32/weights.bin"
        net = IENetwork(model=model_xml, weights=model_bin)
        plugin = IEPlugin(device=device)
        if device == "CPU":
            if platform.processor() == "x86_64":
                plugin.add_cpu_extension("lib/x86_64/libcpu_extension.so")
        exec_net = plugin.load(network=net)
        input_blob = next(iter(net.inputs))
        print("loading finish")
    else:
        print("Nothing model folder")
        sys.exit(0)

    base_range = min(camera_width, camera_height)
    stretch_ratio = inference_scale / base_range
    resize_image_width = int(camera_width * stretch_ratio)
    resize_image_height = int(camera_height * stretch_ratio)

    if base_range == camera_height:
        crop_start_x = (resize_image_width - inference_scale) // 2
        crop_start_y = 0
    else:
        crop_start_x = 0
        crop_start_y = (resize_image_height - inference_scale) // 2
    crop_end_x = crop_start_x + inference_scale
    crop_end_y = crop_start_y + inference_scale

    fps = ""
    message = "Push [p] to take a picture"
    result = "Push [s] to start anomaly detection"
    flag_score = False
    picture_num = 1
    elapsedTime = 0
    score = 0
    score_mean = np.zeros(10)
    mean_NO = 0

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

    time.sleep(1)

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

        ret, image = cap.read()

        if not ret:
            break

        image_copy = image.copy()

        # prediction
        if flag_score == True:
            prepimg = cv2.resize(image, (resize_image_width, resize_image_height))
            prepimg = prepimg[crop_start_y:crop_end_y, crop_start_x:crop_end_x]
            prepimg = np.array(prepimg).reshape((1, inference_scale, inference_scale, 3))
            prepimg = prepimg / 255
            prepimg = prepimg.transpose((0, 3, 1, 2))

            exec_net.start_async(request_id = 0, inputs={input_blob: prepimg})
            exec_net.requests[0].wait(-1)
            outputs = exec_net.requests[0].outputs["Reshape_"]
            outputs = outputs.reshape((len(outputs), -1))
            outputs = ms.transform(outputs)
            score = -clf._decision_function(outputs)

        # output score
        if flag_score == False:
            cv2.putText(image, result, (camera_width - 350, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
        else:
            score_mean[mean_NO] = score[0]
            mean_NO += 1
            if mean_NO == len(score_mean):
                mean_NO = 0
                
            if np.mean(score_mean) > threshold: #red if score is big
                cv2.putText(image, "{:.1f} Score".format(np.mean(score_mean)),(camera_width - 230, 100), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA)
            else: # blue if score is small
                cv2.putText(image, "{:.1f} Score".format(np.mean(score_mean)),(camera_width - 230, 100), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1, cv2.LINE_AA)
              
        # message
        cv2.putText(image, message, (camera_width - 285, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA)
        cv2.putText(image, fps, (camera_width - 164, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0 ,0), 1, cv2.LINE_AA)

        cv2.imshow("Result", image)
            
        # FPS
        elapsedTime = time.time() - t1
        fps = "{:.0f} FPS".format(1/elapsedTime)

        # quit or calculate score or take a picture
        key = cv2.waitKey(1)&0xFF
        if key == ord("q"):
            break
        if key == ord("p"):
            cv2.imwrite(path + str(picture_num) + ".jpg", image_copy)
            picture_num += 1
        if key == ord("s"):
            flag_score = True

    cv2.destroyAllWindows()
    
if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-cfps","--camera_FPS",dest="camera_FPS",type=int,default=30,help="USB Camera FPS. (Default=30)")
    parser.add_argument("-cwd","--camera_width",dest="camera_width",type=int,default=320,help="USB Camera Width. (Default=320)")
    parser.add_argument("-cht","--camera_height",dest="camera_height",type=int,default=240,help="USB Camera Height. (Default=240)")
    parser.add_argument("-sc","--inference_scale",dest="inference_scale",type=int,default=96,help="Inference scale. (Default=96)")
    parser.add_argument("-th","--threshold",dest="threshold",type=int,default=2.0,help="Threshold. (Default=2.0)")
    parser.add_argument("-d","--device",dest="device",default="CPU",help="Device. CPU/GPU (Default=CPU) GPU=Intel HD Graphics.")
    args = parser.parse_args()
    camera_FPS = args.camera_FPS
    camera_width = args.camera_width
    camera_height = args.camera_height
    inference_scale = args.inference_scale
    threshold = args.threshold
    device = args.device

    main(camera_FPS, camera_width, camera_height, inference_scale, threshold, device)

おわりに

とてつもなく速いタクトの業種でも活用できそうなほど、CPUのみで無意味に速く異常検出できるようになりました。

16
8
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?