スクリーンショット_2019-01-07_19

Deep Learningによる一般物体認識の手法の把握と実利用のためのまとめ

目次

・一般物体認識とは
・モデルの性能を知るための評価指標
 ・IoUの閾値
    ・precision-recallグラフ
・一般物体認識を使う
    ・APIを利用する
    ・Keras実装を動かす(YOLOv3)
    ・darknetで学習済みモデルをOpenCVで動かす(YOLOv3)
・一般物体認識の最先端

次の記事で書こうと思っていること。
・YOLOv3の説明
・オリジナルのデータセットでYOLOv3を用いて一般物体認識の学習をする
・OpenCVを用いて学習した重みを用いてテストする

一般物体認識とは

画像引用: YOLO: Real-Time Object Detection

一般物体認識とは上記の画像のように、画像中の物体の位置を検出し、その物体の名前を予測するタスクになります。

一般物体認識は新しいモデルが次から次へと提案されている状況でモデルの歴史をみてみると、以下のようにたくさんのモデルが登場しています。(赤色のモデルは重要なモデルと思われます。)

図は下記レポジトリの内容から引用。各モデルの精度の比較もまとめられています。hoya012/deep_learning_object_detection

よく使われるモデルは、R-CNN系のモデル、SSD、YOLO系のモデルだと思います。R-CNNは歴史的に様々な変遷を遂げていて追ってみると面白いと思います。また、YOLOも現在version3まで出ている状況です。こちら分かりやすいので興味があれば一読をおすすめします。Deep Learningによる一般物体検出アルゴリズムの紹介

モデルの性能を知るための評価指標

様々なモデルが提案されている一般物体認識ですが、使い分けるためにモデルの性能を理解し、モデル間の差を理解することが必要となります。一般物体認識の性能を見るときには、「推論速度」と「認識精度」の2つに注目します。この2つは基本的にはトレードオフの関係にあるので、一般的には「推論速度」を上げるためには「認識精度」を下げることになり、逆も然りです。さて、「推論速度」は自明ですが、「認識精度」については詳しく説明します。

認識精度の評価としては、AP(Average Precision)がよく使われます。ここからAPを理解するための説明をしていきます。APを理解するためには、IoUprecision-recallグラフの理解が必要です。

IoUの閾値

detectionの精度はAP(Average Precision)を用いて行われます。このAPはIoUがベースとなっています。IoUとは下の図のように、正解の領域(Ground truth)と予測の領域(Prediction)の面積(Union)を2つの領域の共通部分の面積(Intersection)で割った値です。IoUの値が1に近づけばよく予測できていることになります。物体を検出できているかどうかは、このIoUに一定の閾値を設けてその値以上であれば認識できている(陽性/positive)と判定します。なお、物体を検出し損ねた場合は、(偽陰性/False Negative)となります。一般に使われるAPでは閾値を0.5から0.95までを0.05刻みでそれぞれ計算し、その平均を出すことで値を算出しています。

画像引用: Measuring Object Detection models - mAP - What is Mean Average Precision?

precision-recallグラフ

特定の閾値に対してAPを求めるために、precision-recallグラフを考えます。precisionは「物体があると予測して本当に物体があった割合」、recallは「正解の物体の総数の内、どれだけ予測できたかの割合」をそれぞれ表します。(もし、precision、recallがピンとこない方は調べてみて下さい。)先程説明したIoUを用いることによってprecisionとrecallが計算できるので、それを元にprecisionとrecallのグラフを作成します。recallが0、0.1、0.2、...、1.0になるようにそれぞれのconfidenceスコアを算出し、それに対応するprecisionを計算することによりグラフを作成できます。これを実際にグラフにすると、以下の図のオレンジのグラフのようになります。その後、recallが高くかつprecisionが高いものが存在すればそのprecisionの値を採用して、緑のグラフを作成します(図をみるとイメージが分かると思います。)。この緑のグラフの下の面積がAPの値となります。これが特定の閾値のIoU値のAPの値となります。評価値としてのAPは各IoUの閾値に対するAPを計算し、それの平均をとることで最終的なAPの値とします。

また、特定のIoU値に対するAPも評価値として示されることもあり、AP50、AP75のようにIoUの閾値の値がつきます。

画像引用: mAP (mean Average Precision) for Object Detection

ここまで理解すると、一般物体認識のモデルの精度の差も理解できるようになっているはずです。ここで、YOLOv3のpaperに登場する比較のグラフをみてみましょう。

mAPが評価値として使われています。先程のAPと基本的には同じで「m」は「mean」の意味で物体の各クラスのAPの平均を出しています。さて、この図の横軸は「推論速度」を表しており、縦軸は「認識精度」を表しています。YOLOv3は認識精度はRetinaNetよりも低いが、推論速度は高いことが分かります。同じ名前のモデルでいくつもモデルがあるのはネットワークの大きさの問題でネットワークの深さで「推論速度」と「認識精度」のトレードオフを実現しています。

それでは、もうひとつグラフをみてみましょう。同じような比較ですが、YOLOv3の精度がRetinaNetと同程度になっています。どこが原因か分かりますか?

答えは評価指標が「mAP-50」になっていることです。これは先程も説明したように、IoUの閾値を0.5(50%)にしていることを表しています。一般のAPは閾値を0.5から0.95までを0.05刻みでそれぞれ計算した平均でした。YOLOv3のモデルは閾値が小さいときは認識精度は高いのですが、閾値が大きくなると認識精度が落ちてしまうのです(ざっくりいうと、物体を検出するだけなら強いが、領域まで特定するのは難しいというようなイメージです。)。このように、評価指標を理解したことで、モデルの特性を理解できるようになりました。ちなみに、YOLOv3はそこそこの認識精度で圧倒的な推論速度を持っているので実用的には優れたモデルだと思います。一般物体認識のコンペでも使われているのをみかけます。この記事でも、基本的にはこのYOLOv3を利用したいと思います。

一般物体認識を使う

一般物体認識はコードも調べればたくさん見つかりますので、選択肢は多いです。その中でも使いやすかったものを紹介します。以下の3つをご紹介します。特に3つ目の方法がおすすめです。具体的に記述したので長めですが、さらっと読み流してもらえると雰囲気は掴めると思います。
・APIを利用する
・Keras実装を動かす(YOLOv3)
・darknetで学習済みモデルをOpenCVで動かす(YOLOv3)

APIを利用する

一般物体認識もAPI化されてきており、ここではGoogleのAPIについて説明します。APIはお手軽に利用できるので、最も簡単に試すことができる手段のひとつです。GoogleのCloud Vision APIの中に、複数のオブジェクトを検出するAPIであるobject localizeが存在するのでそれを利用します。ただし、まだベータ版なので注意です(API バージョンv1p3beta1に含まれる)。まだベータ版かどうかはリリースノートをチェックすれば分かります。

1,000リクエストまでは無料のようなので簡単に試す分には無料でできますね。1万リクエストで$22.5というコスト感なので、サービスに適用する場合、画像であればなんとかなるとは思いますが、動画でやるとなると厳しそうですね(そもそも動画利用は考慮されていないとは思いますが)。

参考: Google Vision API

それでは、実際に利用してみましょう。利用するためにはアクセスキーが必要なので、GCPでアクセスキーを取得して下さい。Google Cloud Platform
認証のためのJSONのファイルを取得することができます。詳しくは、こちら。アクセスキーが用意できたら、以下のようにJSONファイルのパスを通します。

$ export GOOGLE\_APPLICATION\_CREDENTIALS=_<path\_to\_service\_account\_file>_

その後、必要モジュールを読み込みます。

$ pip install google-cloud-vision

複数のオブジェクトを検出する | Cloud Vision API ドキュメント | Google Cloudに画像ファイルパスを指定するパターンと画像のURLを指定するパターンの2パターンのコードが用意されているのでこれを使うと、簡単にAPIを利用できます。

以下の椅子の画像で試してみました。コードはドキュメントに記載のあるものを使用しています。

from google.cloud import vision_v1p3beta1 as vision
from PIL import Image
from PIL import ImageDraw

def localize_objects_uri(uri):
    """Localize objects in the image on Google Cloud Storage

    Args:
    uri: The path to the file in Google Cloud Storage (gs://...)
    """
    from google.cloud import vision_v1p3beta1 as vision
    client = vision.ImageAnnotatorClient()

    image = vision.types.Image()
    image.source.image_uri = uri

    objects = client.object_localization(
        image=image).localized_object_annotations

    print('Number of objects found: {}'.format(len(objects)))
    for object_ in objects:
        print('\n{} (confidence: {})'.format(object_.name, object_.score))
        print('Normalized bounding polygon vertices: ')
        for vertex in object_.bounding_poly.normalized_vertices:
            print(' - ({}, {})'.format(vertex.x, vertex.y))

uri="https://kinarino.k-img.com/system/press_images/000/992/178/bd7f6252d71bc8b16065ee96e51aebcecb7a1055.jpg"
localize_objects_uri(uri)

以下のような値が返ってきました。

Number of objects found: 4
Chair (confidence: 0.7590481638908386)
Normalized bounding polygon vertices:
- (0.05593398958444595, 0.27786797285079956)
- (0.2897130846977234, 0.27786797285079956)
- (0.2897130846977234, 0.7553085088729858)
- (0.05593398958444595, 0.7553085088729858)
Chair (confidence: 0.6897056698799133)
Normalized bounding polygon vertices:
- (0.7032281756401062, 0.27833855152130127)
- (0.9238307476043701, 0.27833855152130127)
- (0.9238307476043701, 0.7529555559158325)
- (0.7032281756401062, 0.7529555559158325)Chair (confidence: 0.6897056698799133)
Normalized bounding polygon vertices:
- (0.2851104736328125, 0.2797503173351288)
- (0.49300721287727356, 0.2797503173351288)
- (0.49300721287727356, 0.7520144581794739)
- (0.2851104736328125, 0.7520144581794739)Chair (confidence: 0.6278436183929443)
Normalized bounding polygon vertices:
- (0.508404552936554, 0.27786797285079956)
- (0.7200660705566406, 0.27786797285079956)
- (0.7200660705566406, 0.7458967566490173)
- (0.508404552936554, 0.7458967566490173)

しっかりと椅子を4つ検知できているようです。

Keras実装を動かす(YOLOv3)

次に、OpenCV + YOLOv3で物体検出を行う | from umentu import stupidを参考にして実行まで行ってみました。いくつか詰まる所があったので詳しめに説明をします。自前でコードを動かせるのでAPIのように料金を気にする必要もありません(サーバーで動かすのであればサーバー代がかかりますが)。また、pythonの環境としてpyenvがある前提で話を進めます。Tensorflowのバージョンが3.7以上に対応されたものがなかったので、Pythonの3.6系をインストールしています。

$ pyenv install 3.6.7

今回はTensorflowのCPUバージョンをインストールしました。(pythonの3.4、3.5、3.6に対応しているバージョンになります)
また、kerasは現在最新バージョンの2.2系ではエラーが出たので2.1系を使用しています。以下が必要モジュールのインストールです。

$ python -m pip install --upgrade https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.12.0-py3-none-any.whl
$ pip install keras==2.1.2
$ pip install opencv-python
$ pip install pillow
# これ以下は可視化に関するモジュール
$ brew install graphviz
$ pip install pydotplus
$ pip install graphviz

可視化のモジュールのインストールは下記を参考にしています。
[Keras] モデルの可視化をしよう!! - MATHGRAM
kerasのplot_modelでハマった話 - Qiita

GitHub - xiaochus/YOLOv3: Keras implementation of yolo v3 object detection.のコードを利用します。READMEにある重みファイルをダウンロードしてプロジェクトフォルダ直下に置きます。

$ git clone https://github.com/xiaochus/YOLOv3
$ cd YOLOv3
$ python yad2k.py cfg/yolo.cfg yolov3.weights data/yolo.h5 # 重みファイルの変換

ここまでできればデモを実行することができます。(下記が問題なくうごくはずです)

$ python demo.py

次にデスクトップカメラでデモを行います。まず、出力ファイルのためにディレクトリを用意します。

$ mkdir -p images/output

プロジェクトフォルダ直下に以下のファイルを用意して実行すると、用意したアウトプットディレクトリにPCで撮影して検出の処理がされた画像が逐次出力されます。(最終行の--classes-pathのところは必要があれば変更して下さい。)

"""Demo for use yolo v3
"""
import os
import time
import argparse
import datetime
import cv2
import numpy as np
from PIL import Image
from keras.models import load_model
from model.yolo_model import YOLO
IMAGE_DIR = os.path.dirname(os.path.abspath(__file__)) + "/images/output/"
def process_image(img):
   """Resize, reduce and expand image.
   # Argument:
       img: original image.
   # Returns
       image: ndarray(64, 64, 3), processed image.
   """
   image = cv2.resize(img, (416, 416),
                      interpolation=cv2.INTER_CUBIC)
   image = np.array(image, dtype='float32')
   image /= 255.
   image = np.expand_dims(image, axis=0)
   return image
def get_classes(file):
   """Get classes name.
   # Argument:
       file: classes name for database.
   # Returns
       class_names: List, classes name.
   """
   with open(file) as f:
       class_names = f.readlines()
   class_names = [c.strip() for c in class_names]
   return class_names
def draw(image, boxes, scores, classes, class_names):
   """画像から検出されたオブジェクトを枠で囲う
   # Argument:
       image: original image.
       boxes: ndarray, boxes of objects.
       classes: ndarray, classes of objects.
       scores: ndarray, scores of objects.
       all_classes: all classes name.
   """
   for box, score, cl in zip(boxes, scores, classes):
       x, y, w, h = box
       top = max(0, np.floor(x + 0.5).astype(int))
       left = max(0, np.floor(y + 0.5).astype(int))
       right = min(image.shape[1], np.floor(x + w + 0.5).astype(int))
       bottom = min(image.shape[0], np.floor(y + h + 0.5).astype(int))
       cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
       cv2.putText(image, '{0} {1:.2f}'.format(class_names[cl], score),
                   (top, left - 6),
                   cv2.FONT_HERSHEY_SIMPLEX,
                   0.6, (0, 0, 255), 1,
                   cv2.LINE_AA)
       print('class: {0}, score: {1:.2f}'.format(class_names[cl], score))
       print('box coordinate x,y,w,h: {0}'.format(box))
   print()
def _main(args):
   classes_path = os.path.expanduser(args.classes_path)
   print(classes_path)
   with open(classes_path) as f:
       class_names = f.readlines()
   class_names = [c.strip() for c in class_names]
   yolo = YOLO(0.6, 0.5)
   cap = cv2.VideoCapture(0)
   while True:
       _, image = cap.read()
       # 画像を変換
       pimage = process_image(image)
       # 物体検出
       boxes, classes, scores = yolo.predict(pimage, image.shape)
       # 物体があったら枠で囲う
       if boxes is not None:
           draw(image, boxes, scores, classes, class_names)
       # 画像を保存
       image = Image.fromarray(image)
       file_name = datetime.datetime.now().strftime("%Y%m%d%H%M%s") + ".jpg"
       image.save(IMAGE_DIR + file_name)
if __name__ == '__main__':
   parser = argparse.ArgumentParser(description='Sample code for python.')
   parser.add_argument('--classes-path', default="~/python/detection/YOLOv3/data/coco_classes.txt")
   _main(parser.parse_args())

出力した画像ファイル群を動画にしてみたい場合は、以下のコードを実行することで可能になります。参考: Pythonで連番画像から動画を作成する - QiitaOpenCVのVideoWriterを使って画像から動画を作る。 - 可変ブログ

import cv2
import glob
import os
images_list = glob.glob("images/output/*")
images_list = list(map(lambda x: int(os.path.splitext(x.split("/")[-1])[0]), images_list))
images_list.sort()
fourcc = cv2.VideoWriter_fourcc('m','p','4','v')
video = cv2.VideoWriter('images/output/video.mp4', fourcc, 1, (1280, 720))
for i in images_list:
   img = cv2.imread('images/output/{}.jpg'.format(i))
   img = cv2.resize(img, (1280,720))
   video.write(img)
video.release()

以上で、Keras実装を使ったデモは終わりになります。学習済みモデルを使うことでお手軽に一般物体認識を実装できました。

darknetで学習済みモデルをOpenCVで動かす(YOLOv3)

darknetとは、Cベースのニューラルネットワークのライブラリです。YOLOv3の著者実装がこれで書かれているため、YOLOの文脈ではよく見かけます。OpenCVの中には実はこのdarknetで学習済みモデルの重みを使って一般物体認識を行えるモジュールが用意されています。そのため、他のニューラルネットワークライブラリの環境構築より楽に使用することができるメリットがあります。また、darknetは著者実装で学習済みモデルが豊富に用意されており、使い勝手がよいです。darknetで自分で用意したオリジナルデータでの再学習も簡単に行えたので、YOLOv3を使うのであればこれが一番使い勝手がいいと個人的には思いました。オリジナルデータでの再学習はまた次の記事で説明しようと思います。

darknetで学習済みモデルを動かすには、 YOLO Object Detection with OpenCV and PythonYOLO object detection with OpenCV - PyImageSearchに分かりやすい解説があるので説明は譲ります(需要がありそうなら別途記事にします)。特に後者はまとまったコードをメールしたらもらえるので利用を考えるときはコードをもらって参考にするとよいと思います。動画へ対応の実装してあって使いやすいコードです。

実際に、椅子の画像でテストしてみました。

出力は以下のような感じで椅子が認識されています。(黄緑で薄いです、、)

一般物体認識の最先端

以下は、M2Det: A Single-Shot Object Detector based on Multi-Level Feature Pyramid NetworkというAAAI2019の論文によるモデル比較の図です。注目するのはSNIPER: Efficient Multi-Scale TrainingM2Det: A Single-Shot Object Detector based on Multi-Level Feature Pyramid Networkです。

特に、M2DetはYOLOv3の比較をした以下の図をみると分かるのですが、推論速度、認識精度ともに凌駕しています。

一般物体認識のコンペの上位入賞者でもYOLOv3やRetinaNetを使っているようなので(少なくても少し前までは)、M2Detはかなり革新的な気がしています。なお、qijiezhao/M2Detでコードを公開するとのことです。現在、リファクタ中とのことで楽しみに待ちたいと思います。

最後に

私が所属している株式会社ACESでは、Deep Learningを用いた画像認識技術を中心に、APIによるアルゴリズムパッケージの提供や、共同研究開発を行なっています。特に、ヒトの認識・解析に強みを持って研究開発を行っておりますので、ご興味のある方は、ぜひお問い合わせください!
【詳細・お問い合わせはこちら】 acesinc.co.jp sharon.jp

◆画像認識アルゴリズム「SHARON」について
ヒトの行動や感情の認識、モノの検知などを実現する画像認識アルゴリズムを開発しています。スポーツにおけるパフォーマンス分析やマーケティングにおけるヒトの心の動きの可視化、ストレスなどの可視化による健康状態の管理を始めとするAIアルゴリズムを提供しています。

この記事が気に入ったらサポートをしてみませんか?