この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
CX事業本部の平内(SIN)です。
Amazon SageMaker Ground Truth (以下、Ground Truth)では、作業を完了したデータをコンソール上で確認できます。
しかし、1ページに表示できる数が固定となっているため、データが大量にあると、全体を見渡すのが少し辛いかも知れません。
今回は、物体検出(Bounding box)用に作成されたデータセットを、ローカルで一覧するビューアーを作ってみました。
2 Ground Truthの出力
最初に、Ground Truthの出力である、画像とアノテーションデータ(output.manifest)をローカルにダウン ロードします。
output.manifestは、各行が各画像用のアノテーション情報であるJSONになっています。(以下、参考)
3 ビューア
ビューアは、動的に作成するindex.htmlで実現されています。下記の項目を設定して実行することで、index.htmlが生成されます。
- targetPath Ground Truthの出力をダウンロードした、上記のフォルダ
index.py
import json
# 定義
targetPath = '/Users/hirauchi.shinichi/Downloads/VoTT-DataSet/GroundTruth'
manifest = 'output.manifest'
def main():
# ベースとなるのHTMLを取得
with open("./base.html") as f:
html = f.read()
# アノテーション(output.manifest)を読み込む
with open("{}/{}".format(targetPath, manifest)) as f:
lines = f.read()
# ベースHTMLの置き換え(//$$$[TargetPath]$$$)
html = html.replace("//$$$[TargetPath]$$$", "\"" + targetPath + "\"" )
text = ''
for line in lines.split('\n'):
if(line!=''):
text += "outputManifest.push(JSON.parse('"+ line +"'))\n"
# ベースHTMLの置き換え(//$$$[Manifest]$$$)
html = html.replace("//$$$[Manifest]$$$",text)
# index.htmlの保存
with open("./index.html", 'w') as f:
f.write(html)
main()
上記のindex.pyは、ベースとなるhtmlファイルを読み込み、output.manifestの内容(//$$$[Manifest]$$$))とターゲットとなるフォルダ(//$$$[TargetPath]$$$))を上書きしています。
ベースとなっているhtmlファイルは、以下のとおりです。
base.html
<br /><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<center></center>
<h1>GroundTrurh Viewer</h1>
<input id="scale" max="1" min="0.1" step="0.1" type="range" value="0.5" />
<div id="contents"></div>
<script>
const outputManifest = [];
//$$$[Manifest]$$$
const targetPath = //$$$[TargetPath]$$$
class Data {
constructor(json) {
const tmp = json["source-ref"].split('/');
this.imgName = tmp[tmp.length-1];
let projectName = '';
Object.keys(json).forEach(key => {
const index = key.indexOf("-metadata");
if(index != -1){
projectName = key.substr(0,index)
}
})
this.size = json[projectName]["image_size"][0];
this.annotations = json[projectName]["annotations"];
this.class_map = json[projectName + "-metadata"]["class-map"];
}
}
function createRgba(color, alpha) {
return "rgba(" + [color[0], color[1], color[2], alpha] + ")";
}
function createColor(){
return [Math.random() * 256, Math.random() * 256, Math.random() * 256]
}
function drawText(ctx, x, y, fontsize, color, text){
ctx.font = `${fontsize}pt Arial`;
const area = ctx.measureText(text);
const th = area.actualBoundingBoxAscent * 1.5;
const tw = area.width * 1.2;
ctx.fillStyle = createRgba(color, 1);
ctx.fillRect(x, y - th, tw, th);
ctx.fillStyle = "white";
ctx.fillText(text, x + fontsize/3, y - fontsize/4)
}
function draw(canvas, targetPath, scale, colors, data){
const image = new Image();
image.addEventListener("load",() => {
const width = image.naturalWidth * scale;
const height = image.naturalHeight * scale
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight, 0, 0, width, height);
data.annotations.forEach( annotation =>{
const i = annotation.class_id;
const x = annotation.left * scale;
const y = annotation.top * scale;
const w = annotation.width * scale;
const h = annotation.height * scale;
const name = data.class_map[i];
// 塗りつぶし
ctx.fillStyle = createRgba(colors[i], 0.3);
ctx.fillRect(x, y, w, h);
// 枠線
ctx.strokeStyle = createRgba(colors[i], 1);
ctx.lineWidth = 2;
ctx.strokeRect(x, y, w, h);
// ラベル名
drawText(ctx, x, y, width/35, colors[i], name)
})
// ファイル名
const fontSize = width/35;
drawText(ctx, 0, fontSize * 1.5, fontSize, [0,0,0], data.imgName);
});
image.src = `${targetPath}/${data.imgName}`;
}
// 再表示
function refresh(dataList, scale, colors){
// 表示エリアのノード取得
const contents = $("#contents");
// 全て削除
contents.empty();
// 各データの描画
dataList.forEach( data => {
const div = document.createElement('span');
const canvas = document.createElement('canvas');
// const name = document.createTextNode(data.imgName);
// div.append(name)
div.append(canvas)
contents.append(div)
draw(canvas, targetPath, scale, colors, data);
})
}
function main(){
const colors = [];
Array.from(Array(100).keys()).forEach( ()=>{
colors.push(createColor())
})
// output.manifestからデータを読み込む
const dataList = [];
outputManifest.forEach(json => {
dataList.push(new Data(json))
})
// 各データの描画
refresh(dataList, 0.5, colors);
$('#scale').on('input change', () => {
refresh(dataList, $("#scale").val(), colors);
});
}
main();
</script>
4 動作確認
生成されたindex.htmlを開くと、アノテーションされた画像が確認できます。
タイトルの下のスライダーで、サイズを0.1倍〜1倍まで変更可能です。
ページ変更はなく、全てのデータが、一覧されます。
5 最後に
今回は、Ground Truthで作成したデータをローカルで一覧するビューアーを作成してみました。
実は、最近、Ground Truthを中心に、オブジェクト検出のためのデータセットの作成環境を整備しています。ビューアーは、この内の⑤になってます。
①VoTTで作成したデータをCustom Labelsで利用可能なAmazon SageMaker Ground Truth形式に変換してみました
②Amazon SageMaker Ground Truth で作成したデータをオブジェクト検出で利用可能なイメージ形式に変換してみました
③Amazon SageMaker Ground Truth で作成したデータを使用してオブジェクト検出でモデルを作成してみました