この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
CX事業本部の平内(SIN)です。
Amazon SageMakerのビルトインアルゴリズムのイメージ分類は、画像を分類するものですが、シングルラベルで作成されたモデルで、ターゲットする対象物が複数写っていたり、一部だけ写っていたりする場合の検出具合はどうなんだろう?という事で、色々試してみました。
使用したモデルは、下記のように回転台に載せて正面から撮ったデータセットから作成されたものです。
試してみた対象物(商品)は、OREOとCHEDDAR_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 最後に
正面からの撮影だけで作成したデータセットですが、角度を付けた画像などでも、比較的検出できていると思いました。
元々、検出精度の高かった商品なので、全てがこのように上手くいくとは限りませんが、とりあえずは、正面撮影だけのデータセットで始めるのも、あながち間違っていないような気がしています。
現時点の肌感覚で恐縮ですが、一旦、正面撮影のデータセットを軽易に作成し、角度や条件によって検出できない商品は、弱かった角度からの特徴を追加で学習させるような手順が、効率いいのでは? と感じでいます。