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


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

## はじめに

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

## VoTTとは？

![](https://github.com/Microsoft/VoTT/raw/master/media/detectioninabox.jpg)

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

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

## VoTTのインストール

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

<img width="590" alt="01.png" src="https://qiita-image-store.s3.amazonaws.com/0/130181/39d7908b-094c-1671-7b2c-5dcd83e26ba1.png">

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

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

<img width="610" alt="03.png" src="https://qiita-image-store.s3.amazonaws.com/0/130181/f027ac43-10ff-26ef-0fc8-410000e969c7.png">


## 手動でタグ付けを行おう

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

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

<img width="610" alt="04.png" src="https://qiita-image-store.s3.amazonaws.com/0/130181/1f2e6f87-1b38-3faf-8e59-9678a3f296fa.png">

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

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

<img width="605" alt="05.png" src="https://qiita-image-store.s3.amazonaws.com/0/130181/dc81e198-5592-4c76-95b4-8c54fd1cb2a7.png">


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

- ステップ１：ドラッグドロップで領域を選択
- ステップ２：選択した領域に当てはまるラベルを選択

![1.png](https://qiita-image-store.s3.amazonaws.com/0/130181/6d519ae8-a99f-2983-55ca-0874f6c10bec.png)



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

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

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

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

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


```python
import json
import pandas as pd
```


```python
with open('images/cat_dog.json') as f:
    result = json.load(f)
```

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


```python
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`の中に各ラベルのデータが格納されていることがわかると思います。


```python
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を取得するときは昇順にソートしておくことを忘れないようにしましょう。

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


```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from glob import glob
from PIL import Image
```


```python
filepaths = sorted(glob('images/cat_dog/*.jpg'))

len(filepaths)
-> 269
```


```python
# 一番最初のデータ
filepath = filepaths[0]
img = Image.open(filepath)
plt.imshow(img)
```

![output_14_1.png](https://qiita-image-store.s3.amazonaws.com/0/130181/bf67d2ce-42a2-0401-a83c-ea8a9fe68eda.png)



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


```python
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}]




```python
len(vals)
-> 2
```

```python
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 の形式に変換しておくと便利です。


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

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


```python
plt.imshow(img_part)
```




    <matplotlib.image.AxesImage at 0x1da07d9d048>




![output_22_1.png](https://qiita-image-store.s3.amazonaws.com/0/130181/e09f2e46-ea10-4a96-fcd0-7f9e99d3e4cf.png)



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


```python
val = vals[0]
val
```




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



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


```python
img = Image.open(filepath).resize((val['width'], val['height']))
```


```python
plt.imshow(img)
```




    <matplotlib.image.AxesImage at 0x1da07c373c8>




![output_27_1.png](https://qiita-image-store.s3.amazonaws.com/0/130181/6ab75e12-3900-e5d7-154e-cd6c50005184.png)




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


```python
plt.imshow(img_part)
```




    <matplotlib.image.AxesImage at 0x1da07d89fd0>



![2.png](https://qiita-image-store.s3.amazonaws.com/0/130181/c308aebd-4719-19c2-340e-f088ec68df5f.png)





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

### 全体に適用

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

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

```python
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)
```

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


```python
bboxes
-> 
[array([[106.,  65., 370., 209.],
        [ 20., 195., 370., 407.]], dtype=float32)]
```

```python
labels
-> [array([0, 1], dtype=int32)]
```



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

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

```python
import chainer

dataset = chainer.datasets.TupleDataset(imgs, bboxes, labels)
```


```python
import pickle
with open('dataset_cad_dog.pickle', mode='wb') as f:
    pickle.dump(dataset, f)
```

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

## おわりに

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

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

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

### 著者

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