[Amazon SageMaker] イメージ分類で画面の一部に写っているものを検出してみました

2020.05.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

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

最近、棚に陳列された商品を確認する作業を進めています。

ここまで、商品を検出するためのモデル作成には、Amazon SageMaker(以下、SageMaker)のビルトインアルゴリズムである物体検出(object-detection)を利用してきました。

マーカーなどで、商品の写っている部分を特定することで、ある程度の精度を上げることは可能なのですが、要求に耐えるモデルを作成するためには、一定量の精度の高いデータセットが必要であり、アノテーション作業を伴うデータセット作成のコストを削減することは、なかなか難しいと感じていました。

今回は、アルゴリズムを同じくSageMakerのビルトインであるイメージ分類(image-classification)に変更することで、「データセットの作成コストを大きく下げることができないか」という検討の記録です。

2 image-classification

イメージ分類(image-classification)は、画像自体がどのようなシーンであるかを分類するもので、画像とラベルだけでデータセットが、作成可能です。

ここまで使用してきた物体検出(object-detection)では、ターゲットを囲ったBounding boxが必要であり、これが必要なくなることでデータセット作成のコストは格段に下がります。


※ Ground TruthのJob作成画面より

image-classificationは、本来、画面自体に何が写っているかを分類するもですが、既に、商品の位置を特定し、部分画像として切り出しているので、この切り出した部分を、「全画面」として扱い推論することで、object-detectionの代替とします。

下記は、検出対象を中央の水色枠に固定し、推論を行っている様子です。

3 データセット

準備したデータセットは、白地の上に商品を置いて、800*600で撮影した画像です。 各ラベルごと80枚用意しましたが、フォルダ名をラベル名にして、その下に画像を置いて、プログラムで処理すれば、あっという間に作成できます。

4 学習

5種類のラベル各80枚(計400枚)の画像を8:2(320:80)に分けて学習用と検証用に使用しました。

設定したパラメータは、以下のようになっています。

ml.p2.xlargeでepoch=5で回して143秒でした。

以下は、ログの抜粋ですが、Validation-accuracyが、あっという間に1.0になっているのに驚きます。

Epoch[0] Train-accuracy=0.521875
Epoch[0] Train-top_k_accuracy_5=1.000000
Epoch[0] Time cost=6.138
Epoch[0] Validation-accuracy=0.962500
Epoch[1] Train-accuracy=0.984375
Epoch[1] Train-top_k_accuracy_5=1.000000
Epoch[1] Time cost=2.210
Epoch[1] Validation-accuracy=0.912500
Epoch[2] Train-accuracy=0.993750
Epoch[2] Train-top_k_accuracy_5=1.000000
Epoch[2] Time cost=2.168
Epoch[2] Validation-accuracy=1.000000
Epoch[3] Train-accuracy=1.000000
Epoch[3] Train-top_k_accuracy_5=1.000000
Epoch[3] Time cost=2.167
Epoch[3] Validation-accuracy=1.000000
Epoch[4] Train-accuracy=1.000000
Epoch[4] Train-top_k_accuracy_5=1.000000
Epoch[4] Time cost=2.166
Epoch[4] Validation-accuracy=1.000000

5 動画での利用

以下は、Webカメラの画像を、推論しているコードです。

from boto3.session import Session
import json
import cv2

profile = 'developer'
endPoint = 'sampleEndPoint'
classes = ['ASPARA','CRATZ','OREO','PRETZEL','PRIME']

deviceId = 3 # Webカメラ
height = 600
width = 800

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

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

sageMake = SageMaker(profile, endPoint)

cap = cv2.VideoCapture(deviceId)
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))

# 切り出す部分の定義
x1 = int(width/2-80)
x2 = int(width/2+80)
y1 = int(height/2-120)
y2 = int(height/2+120)

while True:

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

    # 部分画像の切り出し
    img = frame[y1: y2, x1: x2] 

    # 推論
    _, 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)
    (name, probability) = probabilitys[0]
    str = "{} {}".format(name, probability)
    cv2.putText(frame,str,(20, int(height)-20), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255), 2, cv2.LINE_AA)

    # 水色枠の描画
    frame = cv2.rectangle(frame,(x1, y1), (x2, y2), (255,255,0),3)

    cv2.imshow('frame', frame) # カメラ画像
    cv2.imshow('img', img) # 切り出し画像
    cv2.waitKey(1)

cap.release()
cv2.destroyAllWindows()

6 最後に

イメージ分類(image-classification)は、物体検出(object-detection)に比較して、非常に軽量です。そして、データセットの作成コストは、比較にならないぐらい低いと思います。

運用場面をよく考察し、うまく適用していければと考えています。