私的AI研究会 > TinyYolo1

TinyYolo v1 で物体検出

 TinyYolo v1 による物体検出のサンプルプログラムを解読する。

※ 最終更新:2021/04/01 

TinyYolo推論実行

 TinyYoloは「物体検出」が可能なモデルでありGoogLeNetやGenderNetなどの「画像認識」モデルは1画像に対して1つの認識しか出来ないが、「物体検出」は1画像に対して複数の認識が可能であり、認識した物体の位置や大きさも知ることができる。
 Neural Compute Application Zoo (NC App Zoo) 公開のサンプルから原理を検証する。

推論実行結果

認識できる物体の種類 (label.txt)

object(日本語)
aeroplane飛行機
bicycle自転車
bird
boatボート
bottleボトル
busバス
car
cat
chair椅子
cow
diningtableダイニングテーブル
dog
horse
motorbikeバイク
person
pottedplant植木鉢
sheep
sofaソファー
train列車
tvmonitorテレビ

Yoloアルゴリズム

 物体検出Yoloのアルゴリズムについて末尾の参考資料のサイトの記事を理解のためにまとめ直す。

・Yolo以前の物体検出は、物体領域を検出してから各領域に対して画像認識を行うというアルゴリズムが主流であったのに対し、YOLO(You Only Look Once)は物体領域検出と画像認識を同時に行うというアルゴリズムで、従来の物体検出より速く行うことが可能である。Tiny版は、小さなメモリで実行可能なモデルで、認識率よりも検出のスピードを重視している。
・物体領域検出はこのような感じで表現される。それぞれの矩形はバウンディングボックス(BB)と呼ばれていて、その領域検出の信用度が高いほど太い枠で表示されてる。

・Yolo以前のアルゴリズムでは、各BBについてCNN(Convolutional Neural Network 畳み込みニューラルネットワーク)による画像認識を行うという手法であった。CNNによる画像認識は処理時間がかかるので、できるだけ回数を減らした方が良い。
そこでYoloは、BBに関係なく画像を7×7のセルに分割し、各セル49箇所に対してのみCNN画像認識を行っている。
・BBは各セルに対して候補を2個だけに絞っている。

・各セルに対してのみCNN画像認識を行うイメージは左のようになる。

・各セルの2個のBBに対し、先ほどの画像認識の結果を融合すると左のようなイメージで成果物が得られる。
・ここまでディープラーニングを使って一気に処理される。

・このままだと非常に情報量の多い表示となる。そこで、その後プログラミングですっきりさせる処理を行う。
・閾値設定に基づき、信用度の高いところのみを抜き出し、重複箇所を消す。すると、左のような「すっきりした情報」になる。

TinyYolo推論コード解読

 ~/ncappzoo/networks/tiny_yolo_v1 にある tiny_yolo_v1.py を参考に「tiny_yolo_v1_a.py」とコードを書き直しながら理解を進めることにする。

全体のプログラム構成

  1. import関連
  2. コマンド・オプション・パラメータ定義
  3. filter_objects関数
  4. get_duplicate_box_mask関数
  5. boxes_to_pixel_units関数
  6. get_intersection_over_union関数
  7. display_objects_in_gui関数
  8. main関数

import関連

import sys
import numpy as np
import cv2
import argparse

コマンド・オプション・パラメータ定義

def parse_args():
    parser = argparse.ArgumentParser(description = 'Image classifier using \
                         Intel® Neural Compute Stick 2.' )
    parser.add_argument( '--ir', metavar = 'IR_File',
                        type=str, default = 'tiny-yolo-v1_53000.xml', 
                        help = 'Absolute path to the neural network IR xml file.')
    parser.add_argument( '-l', '--labels', metavar = 'LABEL_FILE', 
                        type=str, default = 'labels.txt',
                        help='Absolute path to labels file.')
    parser.add_argument( '-i', '--image', metavar = 'IMAGE_FILE', 
                        type=str, default = '../../data/images/nps_chair.png',
                        help = 'Absolute path to image file.')
    parser.add_argument( '--threshold', metavar = 'FLOAT', 
                        type=float, default = DETECTION_THRESHOLD,
                        help = 'Threshold for detection.')
    parser.add_argument( '--iou', metavar = 'FLOAT', 
                        type=float, default = IOU_THRESHOLD,
                        help = 'Intersection Over Union.')
    return parser
コマンドオプションデフォールト設定意味
--irtiny-yolo-v1_53000.xml学習済みIRファイル
-l, --labellabels.txtラベルファイル
-i, --image../../data/images/nps_chair.jpg入力画像ファイルパス
--threshold0.1表示する閾値
--iou0.25重なりを許す閾値 ※

※ コマンドで設定できるようにソース変更

main関数

さらに各セルに対して、20個のクラス確率情報がある。(図の白い部分)
20種類の画像に対するそれぞれの確率で、ほかの推論モデルと同じ。

filter_objects関数

boxes_to_pixel_units関数

# Converts the boxes in box list to pixel units
# assumes box_list is the output from the box output from
# the tiny yolo network and is [grid_size x grid_size x 2 x 4].
def boxes_to_pixel_units(box_list, image_width, image_height, grid_size):

    # number of boxes per grid cell
    boxes_per_cell = 2

    # setup some offset values to map boxes to pixels
    # box_offset will be [[ [0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 6]] ...repeated for 7 ]
    box_offset = np.transpose(np.reshape(np.array([np.arange(grid_size)]*(grid_size*2)),(boxes_per_cell,grid_size, grid_size)),(1,2,0))

    # adjust the box center
    box_list[:,:,:,0] += box_offset
    box_list[:,:,:,1] += np.transpose(box_offset,(1, 0, 2))
    box_list[:,:,:,0:2] = box_list[:,:,:,0:2] / (grid_size * 1.0)

    # adjust the lengths and widths
    box_list[:,:,:,2] = np.multiply(box_list[:,:,:,2], box_list[:,:,:,2])
    box_list[:,:,:,3] = np.multiply(box_list[:,:,:,3], box_list[:,:,:,3])

    #scale the boxes to the image size in pixels
    box_list[:,:,:,0] *= image_width
    box_list[:,:,:,1] *= image_height
    box_list[:,:,:,2] *= image_width
    box_list[:,:,:,3] *= image_height

get_duplicate_box_mask関数

# creates a mask to remove duplicate objects (boxes) and their related probabilities and classifications
# that should be considered the same object.  This is determined by how similar the boxes are
# based on the intersection-over-union metric.
# box_list is as list of boxes (4 floats for centerX, centerY and Length and Width)
def get_duplicate_box_mask(box_list):
    # The intersection-over-union threshold to use when determining duplicates.
    # objects/boxes found that are over this threshold will be
    # considered the same object
    max_iou = IOU_THRESHOLD

    box_mask = np.ones(len(box_list))

    for i in range(len(box_list)):
        if box_mask[i] == 0: continue
        for j in range(i + 1, len(box_list)):
            if get_intersection_over_union(box_list[i], box_list[j]) > max_iou:
                box_mask[j] = 0.0

    filter_iou_mask = np.array(box_mask > 0.0, dtype='bool')
    return filter_iou_mask

get_intersection_over_union関数

# Evaluate the intersection-over-union for two boxes
# The intersection-over-union metric determines how close
# two boxes are to being the same box.  The closer the boxes
# are to being the same, the closer the metric will be to 1.0
# box_1 and box_2 are arrays of 4 numbers which are the (x, y)
# points that define the center of the box and the length and width of
# the box.
# Returns the intersection-over-union (between 0.0 and 1.0)
# for the two boxes specified.
def get_intersection_over_union(box_1, box_2):

    # one diminsion of the intersecting box
    intersection_dim_1 = min(box_1[0]+0.5*box_1[2],box_2[0]+0.5*box_2[2])-\
                         max(box_1[0]-0.5*box_1[2],box_2[0]-0.5*box_2[2])

    # the other dimension of the intersecting box
    intersection_dim_2 = min(box_1[1]+0.5*box_1[3],box_2[1]+0.5*box_2[3])-\
                         max(box_1[1]-0.5*box_1[3],box_2[1]-0.5*box_2[3])

    if intersection_dim_1 < 0 or intersection_dim_2 < 0 :
        # no intersection area
        intersection_area = 0
    else :
        # intersection area is product of intersection dimensions
        intersection_area =  intersection_dim_1*intersection_dim_2

    # calculate the union area which is the area of each box added
    # and then we need to subtract out the intersection area since
    # it is counted twice (by definition it is in each box)
    union_area = box_1[2]*box_1[3] + box_2[2]*box_2[3] - intersection_area;

    # now we can return the intersection over union
    iou = intersection_area / union_area

    return iou

display_objects_in_gui関数

カメラ・動画ファイルによる物体検出アプリケーション

 前節の結果、何となくソースコードが解りかけてきたのでカメラと動画ファイルに対応してみる。

プログラムの実行 (静止画)「tiny_yolo_v1_a.py」

pi@raspberrypi:~/ncappzoo/networks/tiny_yolo_v1 $ python3 tiny_yolo_v1_a.py

--- ** tiny_Yolo_v1_a ** Object identification ---
4.5.1-openvino
OpenVINO inference_engine: 2.1.2021.2.0-1877-176bdf51370-releases/2021/2

Running NCS Caffe TinyYolo example...
tiny_yolo_v1_a.py:366: DeprecationWarning: 'inputs' property of IENetwork class is deprecated. To access DataPtrs user need to use 'input_data' property of InputInfoPtr objects which can be accessed by 'input_info' property.
  input_blob = next(iter(net.inputs))

Tiny Yolo v1: Starting application...
   - Plugin:       Myriad
   - IR File:      tiny-yolo-v1_53000.xml
   - Input Shape:  [1, 3, 448, 448]
   - Output Shape: [1, 1470]
   - Labels File:  labels_jp.txt
   - Image File:    ../../data/images/nps_chair.png
   - Threshold:    0.1
   - Intersection Over Union:    0.25

 Displaying image with objects detected in GUI...
 Click in the GUI window and hit any key to exit.

 Found this many objects in the image: 1
 - object: chair is at left: 221, top: 176, right: 601, bottom: 688

 Finished.

プログラムの実行 (カメラ・動画ファイル)「tiny_yolo_v1_b.py」

pi@raspberrypi-mas:~/ncappzoo/networks/tiny_yolo_v1 $ python3 tiny_yolo_v1_b.py

--- ** tiny_Yolo_v1_b ** Object identification ---
4.5.1-openvino
OpenVINO inference_engine: 2.1.2021.2.0-1877-176bdf51370-releases/2021/2

Running NCS Caffe TinyYolo example...
tiny_yolo_v1_b.py:368: DeprecationWarning: 'inputs' property of IENetwork class is deprecated. To access DataPtrs user need to use 'input_data' property of InputInfoPtr objects which can be accessed by 'input_info' property.
  input_blob = next(iter(net.inputs))

Tiny Yolo v1: Starting application...
   - Plugin:       Myriad
   - IR File:      tiny-yolo-v1_53000.xml
   - Input Shape:  [1, 3, 448, 448]
   - Output Shape: [1, 1470]
   - Labels File:  labels_jp.txt
   - Input File:    0
   - Threshold:    0.1
   - Intersection Over Union:    0.25

 Finished.

ソースコード

▼「tiny_yolo_v1_b.py」

更新履歴

 

参考資料

 

Last-modified: 2021-04-01 (木) 09:55:10