概要
- 手元のセマンティックセグメンテーションタスクのデータセットを SageMaker Ground Truth に取り込み、アノテーションを調整したい
- 目的は達成できたが、0 コストの労力で実現はできなかった
- 社内の協力者がいたためなんとかなった
- 画像枚数が 2,000 枚以下なのでなんとかなった
背景
- 数年前、とある案件で AWS アカウントを作成
- このアカウント上で SageMaker GT を使いアノテーション作業を実施
- クラウドからアノテーション結果のダウンロードを行い、加工した後に gdrive で保管
- 案件終了と共に AWS アカウントを削除
- 再度アノテーションデータの追加・調整の要望
上記の経緯があり、再度手元のデータセットを SageMaker GT にインポートする必要を迫られた。この要件が満たせるか不安だったが無事目的を達成できたため共有したい。
手順
おおまかな手順は下記を踏んだ。それぞれのステップに関して後述する。
画像(サンプル)の枚数に対して協力者が少ない場合は別の手段を講じる必要がある。
- 手元のデータセットのラベル情報の確認をする
- 画像セットを s3 bucket にアップロードする
- ↑をデータソースとして新規のラベリングジョブを作成する
- アノテーション無しで一度ジョブを完了させる
- S3 上のカラーラベル画像を上書きする
- 調整用のラベリングジョブを作成する
手元のデータセットのラベル情報の確認をする
SageMaker GT のラベリングジョブ作成時にラベル情報を登録する必要があるため、始めに手元のデータセットのアノテーションデータ(カラーラベル画像)を合わせる必要がある。
私のセマンティックセグメンテーション向けのデータセットは以下のようなファイルレイアウトとなっており、学習時には images
, labels
以下の画像ファイルを使っている。images
下は入力画像で、labels
をラベル画像と呼称している。ラベル画像は 1ch で画素にセマンティッククラスの ID が 0-255 の値で格納されている。(0 が背景、1 が対象物A、2 が対象物B といった具合)。このため、ラベル画像は単に画像ビュアーで開いてもアノテーション結果が目視で確認できない。このため、3ch のカラーラベル画像 (color_labels
) を用意している。
また、これらサンプルとして対応関係を保つためにファイル名は規則を設けている。 例として、0001
をサンプルの名称とする時、それぞれimages/0001.jpg
, labels/0001.png
として 0001
の部分で一意に特定できるようにしている 。
dataset_root/
├── color_labels // *.png
├── images // *.jpg
├── labels // *.png
ラベル画像は手元にあるが、カラーラベル画像が存在しない場合は用意する必要がある。後述する output.manifest に関係する部分として、ラベリングジョブ作成時に特別な指定無しに進めている場合、下記の internal-color-map
のようなラベルが SageMaker GT で作られて個々のアノテーションタスクで付与されている。カラーラベル画像を作成する際は、このカラーコード( hex-color
)に従い RGB 値を求めて作成する。hex-color
の変換は下記の python コードの断片記載のように求めることができる。
def code2rgb(code):
code = code.lstrip('#')
return tuple(int(code[i:i+2], 16) for i in (0, 2, 4))
"internal-color-map": {
"0": {
"class-name": "BACKGROUND",
"hex-color": "#ffffff",
"confidence": 0
},
"1": {
"class-name": "A",
"hex-color": "#2ca02c",
"confidence": 0
},
"2": {
"class-name": "B",
"hex-color": "#1f77b4",
"confidence": 0
},
"3": {
"class-name": "C",
"hex-color": "#ff7f0e",
"confidence": 0
},
"4": {
"class-name": "D",
"hex-color": "#d62728",
"confidence": 0
},
"5": {
"class-name": "E",
"hex-color": "#9467bd",
"confidence": 0
},
"6": {
"class-name": "F",
"hex-color": "#8c564b",
"confidence": 0
}
画像セットを S3 バケットにアップロードする
- データセットの
images/*.jpg
を任意のバケットにアップロードする
新規のラベリングジョブを作成する
- 先ほどアップロード先の S3 バケットを指定して作成する
- 出力先は別のバケットを用意するとよい
- ラベルの作成は id とカラーコードを示す色に注意しつつ設定する
一度ラベリングジョブを完了させる
- アノテーション画面で「Submit」 ボタンの横に「Nothing to label」 のボタンがあるため、ひたすら押下を行いジョブを1度完了させる
S3 上のカラーラベル画像を上書きする
- 出力先の S3 バケット内の
output.manifest
を確認する - 下記のようにラベリングタスクのメタデータが確認できる
-
experimet1
がジョブ名 -
source-ref
が入力画像の位置 -
experiment1-ref
がジョブexperiment1
でアノテーションされたカラーラベル画像の位置
-
{
"source-ref": "s3://img-source/105_4_00012.jpg",
"experiment1-ref": "s3://anno-output/experiment1/annotations/consolidated-annotation/output/2_2022-10-03T04:39:30.089215.png",
"experiment1-ref-metadata": {
"internal-color-map": "略",
"type": "groundtruth/semantic-segmentation",
"human-annotated": "yes",
"creation-date": "2022-10-03T04:39:30.271567",
"job-name": "labeling-job/experiment1"
}
}
- 上記のメタデータに従い、下記のようなスクリプトを用意してカラーラベル画像を上書きする
import argparse
import json
import boto3
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description="指定されたラベリングジョブの consolidated annotation output のファイルを上書きする")
parser.add_argument('--bucket', type=str, required=True, help='bucket where a manifest file exists')
parser.add_argument('--job-name', type=str, required=True, help='labeling job name which create the manifest file')
parser.add_argument('--data-root', type=str, required=True, help='path to local dataset root')
args = parser.parse_args()
job_name = args.job_name
data_root = args.data_root
s3 = boto3.client('s3')
key = f'{job_name}/manifests/output/output.manifest'
s3.download_file(args.bucket, key, 'output.manifest')
with open('output.manifest', 'r') as f:
for line in f.readlines():
meta = json.loads(line)
img_filename = meta['source-ref'].split('/')[-1]
ano_filename = img_filename.replace('.jpg', '.png')
ano_filepath = f'{data_root}/color_labels/{ano_filename}'
with open(ano_filepath, 'rb') as f2:
ano_data = f2.read()
output_path = meta[f'{job_name}-ref']
path_components = output_path.replace('s3://', '').split('/')
bucket = path_components[0]
key = '/'.join(path_components[1:])
s3.put_object(Bucket=bucket, Key=key, Body=ano_data)
- ラベリングジョブの概要からアノテーション結果が更新されていれば良い
調整用のラベリングジョブを作成する
-
「ラベリングジョブ作成」から行うこと
* 「ジョブのチェーン」、「ジョブのクローン」は使わない
ラベリングジョブの作成画面では 「入力データのセットアップ」から「手動によるデータのセットアップ」を選び、先ほどのoutput.manifest
を指定する。 「次へ」から「既存ラベルの表示」の中の「このジョブのデータセットから既存のラベルを表示したいです」から、 前述のラベリングジョブを指定してラベル情報 を読み込む。
まとめ
- ダウンロードの逆をやればよいが、中間ファイルを上書きすれば本当に良いのか不明な点であり不安があった
- 2022/12/20 時点では、記載の内容は有効だった
- SageMaker の出力する output.manifest はパッと見て、数年前から変わっていないようなので、しばらくは再現性があるのではないか
余談
- ラベリングジョブのタスクの有効期限が過ぎたなどで、アノテーションタスクに下記のようなエラーが格納されている場合があるので、ダウンロード時は注意が必要だった
{
"retry-count": 1,
"failure-reason": "ClientError: Task failed to render: [ InvalidParameters: '\\\"grant_read_access\\\" input is not a valid S3 URI: \\\"\\\".' ].",
"human-annotated": "true"
}