[Amazon Rekognition] VoTTで作成したデータをCustom Labelsで利用可能なAmazon SageMaker Ground Truth形式に変換してみました
1 はじめに
CX事業本部の平内(SIN)です。
Amazon Recognition のCustom Labelsでオブジェクト検出のモデルを作成する場合、データセットの準備が必要です。 画像に対するアノテーションは、Custom LabelsのUIでも提供さていますが、今回は、これを、microsoft/VoTTで作成してみました。
microsoft/VoTTは、マイクロソフトによって開発されているアプリケーションで、ローカルでアノテーションの定義が可能なツールです。
Custom Labelsでは、Ground Truth の出力からデータセットが生成できますので、VoTT形式のデータをGround Truth 形式に変換する作業になります。
2 VoTT
動作確認のためにVoTTで簡単なアノテーションを定義しました。
この状態で、作業フォルダを確認すると、プロジェクトファイル(tmp0.vott)と各画像に対応したアノテーション定義ファイル(.json)が生成されています。なお、Source CorrectionとTarget Correctonは、同じになっています。
3 VoTT形式
VoTTが生成する、アノテーション情報の入ったJSONファイルは、以下のようになっています。 ここで、変換に必要なのは、対応する画像ファイル名(/tmp0/IMG_9281.jpg)や、アノテーションの座標情報(boundingBox)、ラベル情報(tags)です。
{ "asset": { "format": "jpg", "id": "7eb927d9136e5357a2a2e5e3bb745f7f", "name": "IMG_9281.jpg", "path": "file:/tmp0/IMG_9281.jpg", "size": { "width": 600, "height": 800 }, "state": 2, "type": 1 }, "regions": [ { "id": "SBoWJxOx1", "type": "RECTANGLE", "tags": [ "AHIRU" ], "boundingBox": { "height": 516.9595926412616, "width": 336.0018060420315, "left": 166.73872591943956, "top": 62.86957950065703 }, "points": [ { "x": 166.73872591943956, "y": 62.86957950065703 }, { "x": 502.7405319614711, "y": 62.86957950065703 }, { "x": 502.7405319614711, "y": 579.8291721419185 }, { "x": 166.73872591943956, "y": 579.8291721419185 } ] } ], "version": "2.1.0" }
4 Ground Truth 形式
一方、変換したいGround Truthのファイル形式は、以下のようになっています。変換は、元データから取得したファイル名等を、該当する箇所に埋め込むだけです。
下記の例は、JSONが見やすいように、コロンの後ろに空白や改行や空白が入っていますが、実際に生成するファイルでは、空白や改行があるとエラーとなって読み込めませんので、注意が必要です。
{ "source-ref": "s3://data-set-creater/TMP/IMG_9281.jpg", "boxlabel": { "image_size": [ { "width": 600, "height": 800, "depth": 3 } ], "annotations": [ { "class_id": 0, "width": 336.0018060420315, "top": 62.86957950065703, "height": 516.9595926412616, "left": 166.73872591943956 } ] }, "boxlabel-metadata": { "job-name": "7eb927d9136e5357a2a2e5e3bb745f7f", "class-map": { "0": "AHIRU" }, "human-annotated": "yes", "objects": { "confidence": 1 }, "creation-date": "2020-04-08T14:32:32.252239", "type": "groundtruth/object-detection" } }
変換で生成されたデータは、以下のように出力先フォルダに出力されます。 なお、manifest(output.manifest)ファイルは、各jsonファイルを1行として集めたテキストファイルです。Custon Labelsからは、このmanifest(output.manifest)ファイルが読みこまれます。
5 S3
出力フォルダのファイルのうち、画像ファイルとmanifest(output.manifest)を、S3にアップロードします。各jsonファイルの内容は、先のとおり、manifest(output.manifest)に集められていますので、ここでは必要ありません。
6 Custom Labels
S3に置いたmanifest(output.manifest)を指定してデータセットを作成します。注意書きにもありますように、画像等を置いたS3バケットには、Rekognitionからアクセスするためのパーミッションの追加が必要です。
データセットが作成された様子です。VoTTで 設定したアノテーションが、反映されていることを確認できます。
7 プログラム
変換のためのプログラムは、以下のとおりです。下記のパスを設定して利用できます。
- inputPath 入力フォルダ(VoTTのSource Correction/Target Correcton)
- outputPath 出力フォルダ(変換したファイルの出力先)
- s3Path 画像が置かれるS3のバケット及び、プレフィックス(Rekognitionからデータセットを作成する場合に使用される)
import json import glob import os import shutil import datetime # 定義 inputPath = '/tmp/input' outputPath = '/tmp/output' manifest = 'output.manifest' s3Path = 's3://my-dataset/TMP' # 出力先フォルダ生成 os.makedirs(outputPath, exist_ok=True) # マニュフェスト出力用 outputText = '' srcFiles = glob.glob("{}/*.json".format(inputPath)) # class_idが、全データで共通となるラベルの一覧を管理する labels = [] for srcFile in srcFiles: basename = os.path.basename(srcFile) dst_json = {} with open(srcFile, 'r') as f: src_json = json.load(f) asset = src_json["asset"] path = asset["path"] id = asset["id"] name = asset["name"] size_width = asset["size"]["width"] size_height = asset["size"]["height"] dst_json["source-ref"] = "{}/{}".format(s3Path,name) dst_json["boxlabel"] = {} dst_json["boxlabel"]["image_size"] = [] dst_json["boxlabel"]["image_size"].append({ "width": size_width, "height": size_height, "depth": 3 }) dst_json["boxlabel"]["annotations"]=[] regions = src_json["regions"] for i,region in enumerate(regions): tag = region["tags"][0] if(labels.count(tag)==0): labels.append(tag) cls_id = len(labels)-1 else: cls_id = labels.index(tag) boundingBox = region["boundingBox"] left = boundingBox["left"] top = boundingBox["top"] width = boundingBox["width"] height = boundingBox["height"] dst_json["boxlabel"]["annotations"].append({ "class_id": cls_id, "width": int(width), "top": int(top), "height": int(height), "left": int(left) }) dst_json["boxlabel-metadata"] = {} dst_json["boxlabel-metadata"]["job-name"] = id dst_json["boxlabel-metadata"]["class-map"] = {} for i,label in enumerate(labels): dst_json["boxlabel-metadata"]["class-map"][i]= label dst_json["boxlabel-metadata"]["human-annotated"] = "yes" dst_json["boxlabel-metadata"]["objects"] = [{ "confidence": 1 }] dt = datetime.datetime.now() dst_json["boxlabel-metadata"]["creation-date"] = dt.isoformat(timespec='microseconds') dst_json["boxlabel-metadata"]["type"] = "groundtruth/object-detection" outFile = "{}/{}.json".format(outputPath, name.split('.')[0]) with open(outFile, 'w') as f: json.dump(dst_json, f) #outputText += str(dst_json) + '\n' outputText += json.dumps(dst_json) + '\n' outFile = "{}/{}".format(outputPath, name) shutil.copyfile(path.replace('file:',''), outFile) with open("{}/{}".format(outputPath, manifest), mode='w') as f: f.write(outputText)
8 最後に
アノテーション作業は、大量になると結構大変な作業ですが、ローカルで作業できるVoTTは、非常に快適です。 VoTTでCustom Labelsのデータセットが作成できるのは、個人的には非常に嬉しいです。