Intel NCStick用TinyYOLOのソース読んでみた(その1)

Intel NCStick用TinyYOLOのソース読んでみた(その1)

Intel NCStick用TinyYOLOの解説記事を見かけた。
ソースを読んでみたが、 結構難解で(特にnumpy回り)、自分の鶏頭でも思い出せるように調べた結果をメモしてみた。
NCStick持ってないから実際に動かしてないけど。。。

リポジトリは https://github.com/movidius/ncappzoo だが、このソースはmasterブランチには存在しない。必ずncsdk2ブランチを選択すること。
git clone する場合は要注意。

どっか行っちゃうといけないので、ソースのコピーをここにも置いておく。

モジュールのインポート

特に難しいことはしてない。mvncがNCStickのドライバ。

from mvnc import mvncapi as mvnc
import sys
import numpy as np
import cv2

ファイル名定義

13行目
input_image_file : ここに書かれたファイルを読み込んで認識する。
tiny_yolo_graph_file : ニューラルネットのネットリスト(?) ニューロンの接続情報と重みが入っていると思われる。

input_image_file= '../../data/images/nps_chair.png'
tiny_yolo_graph_file= './graph'

認識用の画像サイズ定義

17行目
ニューラルネットに入力する画像サイズ。任意のサイズの画像をこのサイズにリサイズしてから入力する。
このサイズはニューラルネット構築の際に決定された値。Graphファイルに紐づいた値と考えられる。

Grid分割数が7×7で、1Grid当たりの画像サイズが64pixelなので、7×64 = 448 でおのずと決まる。

NETWORK_IMAGE_WIDTH = 448
NETWORK_IMAGE_HEIGHT = 448

NCStickの出力を整理する処理ルーチン(filter_objects)

35行目
別ページ

重なったボックス情報を削除するためのマスク情報配列を取得(get_duplicate_box_mask)

109行目
別ページ

バウンティングボックス情報の単位変換(boxes_to_pixel_units)

129行目
別ページ

2つのBOXの重なり比率を計算(get_intersection_over_union)

163行目
別ページ

認識結果の表示ルーチン(display_objects_in_gui)

203行目
別ページ

mainルーチン

関数の先頭とオープニングメッセージ

255行目

def main():
    print('Running NCS Caffe TinyYolo example')

NCStickドライバのオプション設定

258行目

    # Set logging level to only log errors
    mvnc.global_set_option(mvnc.GlobalOption.RW_LOG_LEVEL, 3)

NCStickの検出とオープン

260行目
なかったらエラー終了。
複数見つかった場合は最初のものをオープンする。

    devices = mvnc.enumerate_devices()
    if len(devices) == 0:
        print('No devices found')
        return 1
    device = mvnc.Device(devices[0])
    device.open()

Graphファイルの読み込み

267行目
14行目で設定したGraphファイルを読み込んで、NCStickドライバに設定する。

    #Load graph from disk and allocate graph via API
    with open(tiny_yolo_graph_file, mode='rb') as f:
        graph_from_disk = f.read()
    graph = mvnc.Graph("Tiny Yolo Graph")
    fifo_in, fifo_out = graph.allocate_with_fifos(device, graph_from_disk)

入力画像の読み込みと前処理

276行目

    input_image = cv2.imread(input_image_file)
    display_image = input_image
    input_image = cv2.resize(input_image, (NETWORK_IMAGE_WIDTH, NETWORK_IMAGE_HEIGHT), cv2.INTER_LINEAR)
    input_image = input_image.astype(np.float32)
    input_image = np.divide(input_image, 255.0)
    input_image = input_image[:, :, ::-1]  # convert to RGB

NCStick による処理

284行目
NCStickに前処理した画像を入力し、計算結果を得る。
このとき、input_imageの各要素はfloat32型に変換して入力する。(既に変換済みな気もするが…)
ちなみに、input_imageそのものの型は numpy.ndarray
ニューラルネットの処理本体の処理は実質この2行だけ。

    # Load tensor and get result.  This executes the inference on the NCS
    graph.queue_inference_with_fifo_elem(fifo_in, fifo_out, input_image.astype(np.float32), None)
    output, userobj = fifo_out.read_elem()

NCStickの出力を整理する

288行目
filter_objects(35行目)で NCStickの出力を整理する。
別ページを参照。
パラメータは

得られるデータは

filtered_objs は 最終認識結果数 × 6 の 2次元配列(list)
データ構成はこんな感じ。

    filtered_objs = filter_objects(output.astype(np.float32), input_image.shape[1], input_image.shape[0])

認識結果の表示

290行目
display_objects_in_gui (203行目)で 表示用イメージと整理された認識結果を表示。
別ページを参照。
パラメータは
display_image : 表示用画像 filtered_objs : 整理された認識結果

    print('Displaying image with objects detected in GUI')
    print('Click in the GUI window and hit any key to exit')
    #display the filtered objects/boxes in a GUI window
    display_objects_in_gui(display_image, filtered_objs)

後片付け

295行目
各クローズ処理。

    fifo_in.destroy()
    fifo_out.destroy()
    graph.destroy()
    device.close()
    device.destroy()
    print('Finished')

mainルーチン呼び出し

お約束の処理

if __name__ == "__main__":
    sys.exit(main())