[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをVoTT形式のデータに変換してみました

2020.04.14

1 はじめに

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

Amazon SageMaker Ground Truth (以下、Ground Truth)では、作業を完了したデータセットで、「少し(一部)だけ、修正したい」というような要求に対する仕組みは用意されていません。

今回は、物体検出(Bounding box)用に作成されたデータセットを、VoTT用に変換して、一部のアノテーションの修正・変更などが行えるようにしてみました。

microsoft/VoTTは、マイクロソフトによって開発されているアプリケーションで、ローカルでアノテーションの定義が可能なツールです。

2 Ground Truthの出力

最初に、Ground Truthの出力である、画像とアノテーションデータ(output.manifest)をローカルにダウン ロードします。

output.manifestは、各行が各画像用のアノテーション情報であるJSONになっています。(以下、参考)

3 VoTTのプロジェクト作成

VoTT用に変換するためには、VoTT上での画像とアノテーション定義の関係を知る必要がありますが、この関係は、VoTTを起動しないと定義されないため、以下の手順で作業を進めます。

(1) Connection Settings

VoTTを起動し、Connection Settingsで、上でGround Truthの出力をダウンロードしたフォルダを定義します。

下図では、Ground Truthという名前で定義しています。Providerは、Local File Systemをし、先のフォルダが指定されています。

(2) プロジェクト作成

続いてNew Projectでプロジェクトを作成します。

プロジェクト名を指定(任意の名前)し、Source Connection及び、Target Coonnectionをともに、先に作成したGround Truthにします。

Save Projectでプロジェクトを作成すると、画像が読み込まれている事を確認できます(まだ、アノテーションは反映されていません)

サムネイルの画像を選択するごとに、オレンジ色のアイコンが追加されますので、全てのサムネイル画像を一回選択して下さい。このオレンジのアイコンが表示された時点で、画像とアドレーション設定の関連が生成されたことになります。

(3) ラベルの定義

画面右上のTAGSの+から、必要なラベルを定義します。(今回は、AHIRUとDOGに2つです)

(4) プロジェクトファイル

ここまでの作業を終えると、フォルダ内にVoTTのプロジェクトファイル(ここでは、GroundTruthData.vott)が増えている事を確認できます。

このプロジェクトファイルを開くと、以下のような構造となっており、assetsに、画像ファイル名ごとにidが振られていることがわかります。 このidから、アノテーション定義JSONのファイル名が決まります。

{id}-asset.json
{
"name": "GroundTruthData",
//・・・(略)
"sourceConnection": {
"name": "GroundTruth",
//・・・(略)
},
"targetConnection": {
"name": "GroundTruth",
//・・・(略)
},
"tags": [
{
"name": "AHIRU",
"color": "#5db300"
},
{
"name": "DOG",
"color": "#e81123"
}
],
//・・・(略)
"assets": {
"cdd6e852fddb2ca802ffc5f851c2fde2": {
"format": "jpg",
"id": "cdd6e852fddb2ca802ffc5f851c2fde2",
"name": "AHIRU-1586397259091391.jpg",
"path": "file:/Users/xxxxxxxxxxxxxxxxxxxxxxxxxxxx/GroundTruth/AHIRU-1586397259091391.jpg",
"size": {
"width": 800,
"height": 600
},
"state": 1,
"type": 1
},
"821128a3e272a0ae103d8b04b005f97a": {
"format": "jpg",
"id": "821128a3e272a0ae103d8b04b005f97a",
"name": "AHIRU-1586397378592848.jpg",
"path": "file:/Users/xxxxxxxxxxxxxxxxxxxxxxxxxxxx/GroundTruth/AHIRU-1586397378592848.jpg",
"size": {
"width": 800,
"height": 600
},
"state": 1,
"type": 1
},
//・・・(略)

4 変換

下記のプログラムを実行することで、変換が行われます。設定が必要なのは、以下の2つです。

  • targetPath 画像、output.minifest, VoTTのプロジェクトファイルが存在するフォルダ
  • projectFile VoTTのプロジェクトファイル(ここでは、GroundTruthData.vott)

プログラムが行っているのは、output.manifestの内容を該当するVoTTのJSONファイルに反映しているだけです。

import json
import glob
import os
import shutil
import random, string

# 定義
targetPath = '/Users/xxxxxxxxxx/GroundTruth'
projectFile = 'GroundTruthData.vott'
manifest = 'output.manifest'

# 1件のデータを表現するクラス
class Data():
def __init__(self, src):
# プロジェクト名の取得
for key in src.keys():
index = key.rfind("-metadata")
if(index!=-1):
projectName = key[0:index]

# メタデータの取得
metadata = src[projectName + '-metadata']
class_map = metadata["class-map"]

# 画像名の取得
self.imgFileName = os.path.basename(src["source-ref"])
self.baseName = self.imgFileName.split('.')[0]
# 画像サイズの取得
project = src[projectName]
image_size = project["image_size"]
self.img_width = image_size[0]["width"]
self.img_height = image_size[0]["height"]

self.annotations = []
# アノテーションの取得
for annotation in project["annotations"]:
class_id = annotation["class_id"]
top = annotation["top"]
left = annotation["left"]
width = annotation["width"]
height = annotation["height"]

self.annotations.append({
"label": class_map[str(class_id)],
"width": width,
"top": top,
"height": height,
"left": left
})

# 全てのJSONデータを読み込む
def getDataList(inputPath, manifest):
dataList = []
with open("{}/{}".format(inputPath, manifest), 'r') as f:
srcList = f.read().split('\n')
for src in srcList:
if(src != ''):
json_src = json.loads(src)
dataList.append(Data(json.loads(src)))
return dataList

# 画像名からデータを検索する
def getData(dataList, imgName):
for data in dataList:
if(data.imgFileName == imgName):
return data
return None

# ランダム文字列生成
def randomname(n):
return ''.join(random.choices(string.ascii_letters + string.digits, k=n))

def main():

# 全てのJSONデータを読み込む
dataList = getDataList(targetPath, manifest)
log = "全データ: {}件 ".format(len(dataList))

# VoTTのプロジェクトファイルを読み込む
with open("{}/{}".format(targetPath, projectFile), 'r') as f:
project = json.loads(f.read())

assets = project["assets"]
# プロジェクトからidの列挙
for id in assets.keys():
# 画像ファイル名の取得
imgName = assets[id]["name"]
# 画像名からデータを検索する
data = getData(dataList, imgName)
# アノテーション情報
vott_json = {}
vott_json["asset"] = {
"format": "jpg",
"id": id,
"name": imgName,
"path": "file:{}/{}".format(targetPath, imgName),
"size": {
"width": data.img_width,
"height": data.img_height
},
"state": 2,
"type": 1
}
vott_json["regions"] = []
for annotation in data.annotations:
height = int(annotation["height"])
width = int(annotation["width"])
top = int(annotation["top"])
left = int(annotation["left"])
label = annotation["label"]
vott_json["regions"].append({
"id": randomname(9),
"type": "RECTANGLE",
"tags": [
label
],
"boundingBox": {
"height": height,
"width": width,
"left": left,
"top": top
},
"points": [
{
"x": left,
"y": top
},
{
"x": left + width,
"y": top
},
{
"x": left + width,
"y": top+height
},
{
"x": left,
"y": top + height
}
]
})
vott_json["version"] = "2.1.0"

# jsonの保存
with open("{}/{}-asset.json".format(targetPath, id), mode='w') as f:
json.dump(vott_json, f)

main()

5 確認

変換が終わって、再びVoTTを開くと、アノテーションが反映されていることが確認できます。

統計情報で、ラベル数も確認できます。

6 最後に

今回は、Ground Truthで作成したデータセットをVoTTで利用可能なように変換してみました。

「オブジェクト検出」においては、なんだかんだ言っても、データセットを軽易に確認・修正出来る事が、結構大事だと感じています。ローカルで操作できるVoTTは、非常に強力だと思います。