[Amazon SageMaker] イメージ分類(Image Classification)における対象物の写り具合による検出状況について

2020.05.18

1 はじめに

CX事業本部の平内(SIN)です。

Amazon SageMakerのビルトインアルゴリズムのイメージ分類は、画像を分類するものですが、シングルラベルで作成されたモデルで、ターゲットする対象物が複数写っていたり、一部だけ写っていたりする場合の検出具合はどうなんだろう?という事で、色々試してみました。

使用したモデルは、下記のように回転台に載せて正面から撮ったデータセットから作成されたものです。
参考:[Amazon SageMaker] 回転台を使って撮影した動画で、Amazon SageMaker Ground Truth形式のデータセット(Image Classification)を作ってみました

試してみた対象物(商品)は、OREOCHEDDAR_CHEESEです。この2つは、今回作成したモデルの中でも、非常に制度が高かったものです。今回の確認結果は、精度の低いモデルでは、もっと違った結果となる可能性があることを、予めご了承下さい。

なお、画像はWebカメラで撮影し、表示されている水色の枠内だけを、推論にかけ、結果(上位3つ)を文字列で表示しています。

2 各種パターンでの検出状況

(1) 1つが正面の場合

正面から1つの商品だけ写っている場合は、99%の確率で検出できています。

(2) 2つが半々に映る場合

2つの商品が半分半分に写っている場合は、概ね検出上位の2つが、当該商品になっていますが、その割合は、映り込み具合(割合)とは、同じとは限らないようです。

(3) 2つが映る場合

2つの商品の全体が写っている場合も、概ね検出上位の2つが、当該商品になっています。こちらも、その割合は、映り込み具合(割合)に比例するわけでは無いようです。

(4) 一部が映る場合

商品の一部が映り込む場合、当該商品の特徴ある部分であれば、検出できそうです。

(5) 上の方から角度をつけて映り込む場合

少々角度がついた場合も、当該商品の特徴が見られれば、検出できるようです。

(6) 検出に失敗している例

一方、検出に失敗している例です。商品の一部しか写っていない場合や、特徴がうまく出ない角度(角度があり過ぎの場合など)では、失敗しているようです。

3 コード

確認のために使用したコードを参考に紹介させて下さい。マウスのクリックでスナップショットを取れるようにしています。

"""
[イメージ分類] 対象物が写っている状態によって、どのような結果が出るかを確認してみる
"""
import json
import datetime
import cv2
from boto3.session import Session

PROFILE = 'developer'
END_POINT = 'sampleEndPoint'
CLASSES = ['PORIPPY(GREEN)', 'OREO', 'CUNTRY_MAM', 'PORIPPY(RED)', 'BANANA'
           , 'CHEDDER_CHEESE', 'PRETZEL(YELLOW)', 'FURUGURA(BROWN)', 'NOIR'
           , 'PRIME', 'CRATZ(RED)', 'CRATZ(GREEN)', 'PRETZEL(BLACK)', 'CRATZ(ORANGE)'
           , 'ASPARA', 'FURUGURA(RED)', 'PRETZEL(GREEN)']
DEVICE_ID = 1 # Webカメラ
HEIGHT = 600
WIDTH = 800

class SageMaker():
    def __init__(self, profile, endPoint):
        self.__end_point = endPoint
        self.__client = Session(profile_name=profile).client('sagemaker-runtime')

    def invoke(self, image):
        data = self.__client.invoke_endpoint(
            EndpointName=self.__end_point,
            Body=image,
            ContentType='image/jpeg'
        )
        return json.loads(data['Body'].read())

def putText(frame, x, y, text):
    font_size = 1.2
    font_color = (255, 255, 255)
    cv2.putText(frame, text, (x, y), cv2.FONT_HERSHEY_SIMPLEX, font_size, font_color, 2, cv2.LINE_AA)

def onClick(event, x, y, flags, frame):
    if event == cv2.EVENT_LBUTTONUP:
        now = datetime.datetime.now()
        cv2.imwrite(str(now) + '.jpg', frame)
        print("Saved.")

def createArea(width, height, w, h, bias):
    x_1 = int(width/2-w/2)
    x_2 = int(width/2+w/2)
    y_1 = int(height/2-h/2) - bias 
    y_2 = int(height/2+h/2) - bias
    return [x_1, y_1, x_2, y_2]

def main():

    cap = cv2.VideoCapture(DEVICE_ID)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)

    fps = cap.get(cv2.CAP_PROP_FPS)
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    print("FPS:{} WIDTH:{} HEIGHT:{}".format(fps, width, height))

    # 推論の対象となるエリア
    area = createArea(width, height, 220, 300, 100)

    sageMake = SageMaker(PROFILE, END_POINT)

    while True:

        # カメラ画像取得
        _, frame = cap.read()
        if(frame is None):
            continue

        img = frame[area[1]: area[3], area[0]: area[2]]

        # 推論
        _, jpg = cv2.imencode('.jpg', img)
        results = sageMake.invoke(jpg.tostring())

        # 結果の整形
        probabilitys = {}
        for i, result in enumerate(results):
            probabilitys[CLASSES[i]] = result

        probabilitys = sorted(probabilitys.items(), key = lambda x:x[1], reverse=True)

        # 結果の表示
        for i in range(3):
            (name, probability) = probabilitys[i]
            text = "{} {}".format(name, probability)
            putText(frame, 20, int(height) - 120 + i*50, text)

        # 対象範囲の枠表示
        frame = cv2.rectangle(frame, (area[0], area[1]), (area[2], area[3]), (255, 255, 0), 3)

        # フレーム表示
        cv2.imshow('frame', frame)

        # マウスクリックでスナップ撮影
        cv2.setMouseCallback('frame', onClick, frame) 

        cv2.waitKey(1)

    cap.release()
    cv2.destroyAllWindows()

main()

4 最後に

正面からの撮影だけで作成したデータセットですが、角度を付けた画像などでも、比較的検出できていると思いました。

元々、検出精度の高かった商品なので、全てがこのように上手くいくとは限りませんが、とりあえずは、正面撮影だけのデータセットで始めるのも、あながち間違っていないような気がしています。

現時点の肌感覚で恐縮ですが、一旦、正面撮影のデータセットを軽易に作成し、角度や条件によって検出できない商品は、弱かった角度からの特徴を追加で学習させるような手順が、効率いいのでは? と感じでいます。