46
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

VoTTを使って超お手軽に画像のタグ付け~ChainerCVのデータセット変換まで

Last updated at Posted at 2018-05-25

VoTTを使って超お手軽に画像のタグ付け~ChainerCVのデータセットへの変換

はじめに

今回は画像向けの機械学習を用いるときに大変なタグ付けを楽にすることができるツールを紹介します。
また、最終的にComputer Vision向けに特化したChainerCVのR-CNN系で使用できるデータセット形式に変換していきます。
枚数が多く前処理アプリなどを独自で作ることも検討している方は必見です。

VoTTとは?

Microsoftが主導で進めているタグ付け(Tagging)のためのツールで、Visual Object Tagging Toolの略です。
GUIで簡単にタグ付けができるため、非常に便利なツールです。
Electronで作られているため、Windows, Mac, Linux問わずクロスプラットフォームで動く優れものです。
また、Github(こちら)上でコードが公開されているため、独自に改良することも可能です。

画像向けと動画向けのタグ付けができ、動画ではトラッキングの機能も備えています。
百聞は一見にしかず、さっそく使い方を見ていきましょう。

VoTTのインストール

VoTTはこちらからインストールを行うことができます。
ビルド済みの release packageで配布されているため、こちらをダウンロードしましょう。
Windows版もMac版もあるため、OSに合わせてものをダウンロードしましょう。
※ 最新版のv1.0.9は動作が安定していないため、v1.0.8をお勧めします。

01.png

ダウンロード後は解凍して、デスクトップなどわかりやすい場所に移しておきましょう。

VoTT.exeのような実行ファイルを選択し、VoTTを起動しましょう。

03.png

手動でタグ付けを行おう

ツールを使って便利になるとはいえ、人手で主導のタグ付けが必要になります。
今回は犬と猫を分けるようなタグ付けを行っていくこととします。

まずは、画像のタグ付けを選択しましょう。

04.png

タグ付けをしたい画像の入っているフォルダ選択の画面になるため、フォルダを選択しましょう。

タグ付けのジョブ設定ですが、Region Typeは特別なことがない限りは長方形のRectangleとしておきましょう。
Labelsのところは、タグ付けを行いたいラベルの名前をカンマで区切って入力しましょう。今回は、catdogとしました。

05.png

ジョブ設定が終わると、画像が表示されるため、ドラッグドロップであとはとにかく下記の2ステップに基づいて、ラベル付けを行っていきます。

  • ステップ1:ドラッグドロップで領域を選択
  • ステップ2:選択した領域に当てはまるラベルを選択

1.png

タグ付けを行っていくと、フォルダと同じ階層に cat_dog.json のようなタグ付けを行った結果が格納されたJSONファイルが作成されます。

TensorFlowやCNTKであればそのまま使用できるデータセット形式でExportできるのですが、Chainer向けの形式は残念ながらないため、Python側で簡単に加工していきましょう。

タグ付けした結果をChainerCVのデータセット形式へ変換

出力されたJSONファイルの確認

cat_dog.jsonをうまくパースして、データセットに変換していきましょう。
Pathはお手元の状況に合わせてうまく調整してください。

import json
import pandas as pd
with open('images/cat_dog.json') as f:
    result = json.load(f)

ファイルの中を確認していきましょう。

result
{'framerate': '1',
 'frames': {'0': [{'height': 397,
    'id': 0,
    'name': 1,
    'tags': ['cat'],
    'type': 'Rectangle',
    'width': 452,
    'x1': 65,
    'x2': 209,
    'y1': 106,
    'y2': 370},
   {'height': 397,
    'id': 1,
    'name': 2,
    'tags': ['dog'],
    'type': 'Rectangle',
    'width': 452,
    'x1': 195,
    'x2': 407,
    'y1': 20,
    'y2': 370}]},
 'inputTags': 'cat,dog',
 'scd': False,
 'suggestiontype': 'track',
 'visitedFrames': [0]}

変数の中を確認していくと、framesの中に各ラベルのデータが格納されていることがわかると思います。

result['frames']['0']
[{'height': 397,
  'id': 0,
  'name': 1,
  'tags': ['cat'],
  'type': 'Rectangle',
  'width': 452,
  'x1': 65,
  'x2': 209,
  'y1': 106,
  'y2': 370},
 {'height': 397,
  'id': 1,
  'name': 2,
  'tags': ['dog'],
  'type': 'Rectangle',
  'width': 452,
  'x1': 195,
  'x2': 407,
  'y1': 20,
  'y2': 370}]

VoTTの結果として、少し困ったこととは、どの画像に対するタグ付けした結果が格納されていないことです。

基本的には、画像名の昇順に対する結果であるため、filepathを取得するときは昇順にソートしておくことを忘れないようにしましょう。

タグ付けされた位置の確認

たとえば、一番最初のサンプルに対して、タグ付けの結果を確認しましょう。

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from glob import glob
from PIL import Image
filepaths = sorted(glob('images/cat_dog/*.jpg'))

len(filepaths)
-> 269
# 一番最初のデータ
filepath = filepaths[0]
img = Image.open(filepath)
plt.imshow(img)

output_14_1.png

この画像に対するタグ付けされた領域を切り出していきましょう。

vals = result['frames']['0']
vals
[{'height': 397,
  'id': 0,
  'name': 1,
  'tags': ['cat'],
  'type': 'Rectangle',
  'width': 452,
  'x1': 65,
  'x2': 209,
  'y1': 106,
  'y2': 370},
 {'height': 397,
  'id': 1,
  'name': 2,
  'tags': ['dog'],
  'type': 'Rectangle',
  'width': 452,
  'x1': 195,
  'x2': 407,
  'y1': 20,
  'y2': 370}]
len(vals)
-> 2
val = vals[0]
val
{'height': 397,
 'id': 0,
 'name': 1,
 'tags': ['cat'],
 'type': 'Rectangle',
 'width': 452,
 'x1': 65,
 'x2': 209,
 'y1': 106,
 'y2': 370}

配列の切り出しを行う場合は numpy の形式に変換しておくと便利です。

img = np.array(img)
img_part = img[val['y1']:val['y2'], val['x1']:val['x2']]

それでは切り出した領域をプロットしてみましょう。

plt.imshow(img_part)
<matplotlib.image.AxesImage at 0x1da07d9d048>

output_22_1.png

こちらのように全く的外れな部分のタグ付けとなっているのですが、実はheightwidthの部分がヒントで、その大きさにresizeしてあげた後の座標の値を指しています。

val = vals[0]
val
{'height': 397,
 'id': 0,
 'name': 1,
 'tags': ['cat'],
 'type': 'Rectangle',
 'width': 452,
 'x1': 65,
 'x2': 209,
 'y1': 106,
 'y2': 370}

画像のリサイズをかけてあげましょう。

img = Image.open(filepath).resize((val['width'], val['height']))
plt.imshow(img)
<matplotlib.image.AxesImage at 0x1da07c373c8>

output_27_1.png

img = np.array(img)
img_part = img[val['y1']:val['y2'], val['x1']:val['x2']]
plt.imshow(img_part)
<matplotlib.image.AxesImage at 0x1da07d89fd0>

2.png

このようにタグ付けした部分を取得できました。

全体に適用

少し難しいですが、下記のようにforでループを回して、すべての画像に対して選択した領域などを抽出し、imgs, bboxes, labelsを切り出せるように設定しています。

注意として、画像をPillowやOpenCVで読み込んだ際は、(Height, Width, Channels)の順で格納されていますが、ChainerCV含め、多くのディープラーニングフレームワークでは(Channels, Height, Width)のようにChannelsを先頭にもってくる必要があるため、この時点でnp.transposeを使って変更しています。

imgs, bboxes, labels = [], [], []

for (key, vals) in result['frames'].items():
    
    # ラベル付きの画像のみ
    if len(vals) > 0:
        
        # Part1 画像の読み込み
        filepath = filepaths[int(key)]
        val = vals[0]
        img = Image.open(filepath).resize((val['width'], val['height']))  # 読み込みの際にリサイズ
        img = np.transpose(img, (2, 0, 1))  # (H, W, C) -> (C, H, W)
        
        # Part2 特定領域の読み込み
        _bboxes, _labels = [], []
        for val in vals:
            x1, x2 = val['x1'], val['x2']
            y1, y2 = val['y1'], val['y2']
            bbox = [y1, x1, y2, x2]
            _bboxes.append(bbox)
            _labels.append(val['name'] - 1)  # 始まりが1のため
            
        # Part3 numpyに変換
        img = np.array(img, 'f')
        _bboxes = np.array(_bboxes, 'f')
        _labels = np.array(_labels, 'i')
        
        # Part4 リストに追加
        imgs.append(img)
        bboxes.append(_bboxes)
        labels.append(_labels)

切り出した後のデータは極力中を確認するようにしましょう。

bboxes
-> 
[array([[106.,  65., 370., 209.],
        [ 20., 195., 370., 407.]], dtype=float32)]
labels
-> [array([0, 1], dtype=int32)]

ChainerのDataset形式に変換してファイルに保存

最後に、後々の学習にしようできるようにPickleでファイルとして保存しておきましょう。
Chainerでは、(画像, バウンディングボックス, ラベル)の順にTuppleDatasetへ渡してあげればDatasetとして使用できます。
特に画像など容量の大きいものは dataset = list(zip(imgs, bboxes, labels)) のようにdatasetの形式へと変換すると、メモリに乗り切らず、学習時になぜか遅いといった問題に遭遇するため注意してください(ChainerのTuppleDatasetは一旦ファイルシステムに置き、必要な分を読み出すとのことです)。

import chainer

dataset = chainer.datasets.TupleDataset(imgs, bboxes, labels)
import pickle
with open('dataset_cad_dog.pickle', mode='wb') as f:
    pickle.dump(dataset, f)

これでファイルに保存できていれば、またpickleで読み込めば完了です。

おわりに

画像のラベル付けなどあまり参考書ではフォーカスが当たらない部分ですが、実務に入る際には必要となり、結構時間がかかるところを今回は紹介しました。

こういった実務寄りの話はなかなか参考書としてまとまっていることは少ないのですが、私自身良い記事を見つけたらツイートするようにしているため、良ければTwitterのフォローをお待ちしています。
Twitter: @yoshizaki_kkgk

それでは、楽しいディープラーニングライフを送りましょう!

著者

株式会社キカガク 代表取締役社長
吉崎 亮介

46
50
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?