[Amazon SageMaker] 回転台を使って撮影した動画で、Amazon SageMaker Ground Truth形式のデータセット(Image Classification)を作ってみました

2020.05.13

1 はじめに

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

Amazon SageMaker(以下、SageMaker)のビルトインアルゴリズムであるイメージ分類では、データセットにアノテーション情報が必要ないため、比較的簡単にデータセットが作成できます。

今回は、動画を撮影して、Amazon SageMaker Ground Truth(以下、Ground Truth)形式のデータを生成する仕組みを試してみました。

最初に、作業している様子です。商品の動画を継ぎ足しで撮影し、プログラムでデータ変換しています。

2 動画撮影

動画を撮影しているコードです。

mp4は、OpenCVで生成しています。

モニター上に表示されているシャッターボタンを押すと、撮影開始となり、もう一度押すと撮影終了です。'q'キーを押すまでは、追加で動画が撮影されます。

index.py

import cv2
import time
import shoot as sh
import os

# 設定
outputFileName = "/tmp/Movie/PRIME.mp4"

def main():
    # 出力先ディレクトリ作成
    path = os.path.dirname(outputFileName)
    os.makedirs(path, exist_ok=True)

    # カメラ初期化
    deviceId = 3 # Webカメラ
    height = 600
    width = 800
    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 = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    print("FPS:{} WIDTH:{} HEIGHT:{}".format(fps, width, height))

    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    out = cv2.VideoWriter(outputFileName, fourcc, fps, (width, height))

    # 撮影クラス初期化    
    shoot = sh.Shoot(width, height)

    while True:

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

        # 録画
        if(shoot.recording):
            out.write(frame)

        # モニター表示
        shoot.disp(frame.copy()) 

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    out.release()
    cv2.destroyAllWindows()

main()

shoot.py

import cv2

# 撮影クラス
class Shoot():
    def __init__(self, width, height):
        self.__windowName = 'Monitor'
        self.__W = int(width/30)
        self.__M = int(self.__W/4)
        self.__X = int(width/2)
        self.__Y = int(height - self.__W - self.__M * 5)
        self.__shutterColor =  (50, 50, 255)
        self.__recording = False 

    @property
    def recording(self):
        return self.__recording

    # シャッターボタンが押された時のイベント処理
    def __onMouseEvent(self, event, x, y, flags, param):
        if(event == cv2.EVENT_LBUTTONDOWN):
            print("event:{} x:{} y:{}".format(event, x, y))
            if((self.__X - self.__W) < x and x < (self.__X + self.__W)):
                if((self.__Y - self.__W) < y and y < (self.__Y + self.__W)):
                    self.__recording = not bool(self.__recording)

    # シャッターボタン
    def __dispShutter(self, frame):
        if(self.__recording):
            frame = cv2.rectangle(frame, (self.__X - 10, self.__Y - 10), (self.__X + 10, self.__Y + 10), self.__shutterColor, thickness=-1)
        else:
            frame = cv2.circle(frame, (self.__X, self.__Y), self.__W, self.__shutterColor, thickness=-1)
        frame = cv2.circle(frame, (self.__X, self.__Y), self.__W + self.__M, self.__shutterColor, thickness=2, lineType=cv2.LINE_AA)
        return frame

    # モニター画面
    def disp(self, frame):

        # シャッターボタン描画
        frame = self.__dispShutter(frame)
        cv2.imshow(self.__windowName, frame)
        cv2.setMouseCallback(self.__windowName, self.__onMouseEvent) # マウスイベント取得

outputFileNameに指定した名前でmp4が生成されます。ファイル名は、次の変換でラベル名となります。

3 データ変換

撮影した動画(mp4)から、Ground Truth形式のデータを生成しているコードです。

inputPathに指定したフォルダに複数の動画がある場合、ラベルを連番でインデックスして変換します。 すべてのフレームからmaxで指定した数のフレームを均等に抽出し、画像データとしています。

import cv2
import os
import glob
import datetime
import json

# 設定
inputPath = "/tmp/Movie/"
outputPath = "/tmp/Movie/DataSet"
s3Path = 's3://sagemaker-working-bucket/my-project'
max = 200
manifest = 'output.manifest'
projectName = 'my-project'

# 出力先ディレクトリ作成
os.makedirs(outputPath, exist_ok=True)

files = glob.glob("{}/*.mp4".format(inputPath))

# output.manifest
outputText = ''

dt = datetime.datetime.now()
dateStr = dt.isoformat(timespec='microseconds')

for index, file in enumerate(files):

    movie = cv2.VideoCapture(file) 
    frameCount = int(movie.get(cv2.CAP_PROP_FRAME_COUNT))
    interval = int(frameCount/max)

    fileName = os.path.basename(file)
    baseName = os.path.splitext(fileName)[0]
    counter = 0
    for i in range(frameCount - 1):
        _, frame = movie.read()

        if(i % interval != 0):
            continue

        if(max <= counter):
            break

        c = str(i)
        imageName = "{}_{}.jpg".format(baseName, str(counter).zfill(5))
        outputFileName = "{}/{}".format(outputPath, imageName)


        dst_json = {
                "source-ref": "s3://sagemaker-working-bucket/my-project/{}".format(imageName),
                projectName: index,
                projectName + "-metadata": {
                    "confidence": 1.0,
                    "job-name": "labeling-job/project",
                    "class-name": baseName,
                    "human-annotated": "yes",
                    "creation-date": dateStr,
                    "type": "groundtruth/image-classification"
            }
        }
        outputText += json.dumps(dst_json) + '\n'

        counter += 1

        cv2.imwrite(outputFileName, frame)
        print(outputFileName) 

with open("{}/{}".format(outputPath, manifest), mode='w') as f:
    f.write(outputText)

出力されたデータです。

output.manifestには、全画像のラベル情報が生成されています。

4 最後に

回転台を回すことで、比較的簡単に動画が生成できます。24fpsぐらいで撮影して、4回転ほど撮影すれば、1000件ぐらいのデータセットの生成が可能です。

今回は、正面から撮影して商品を検出する場面が想定だったので、商品の正面の回転画像のみですが、要件によっては、商品を立てたり斜めにしたりする事も検討が必要になってくると思います。