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

2020.05.18

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

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 最後に

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

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

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