[画像認識] 自動アノテーションした結果をVoTTで読み込む方法

# はじめに

機械学習で一番大事だけど面倒なのがアノテーション（教師ラベル作成）です。
画像認識だとVoTTなどのアノテーションツールが出ていますが、マウスでポチポチするのはやはり面倒。
ある程度ラベルを自動的に作成し、おかしいところだけ修正するようにできれば楽できそうです。

## Active Learning機能 (VoTT)

そういう要望を叶えてくれる機能が実は世の中にはあって、例えばVoTTのActive Learning機能などがそうです。
[VoTTのActiveLearning機能の紹介 - Qiita](https://qiita.com/wakame1367/items/a707d1d324f70d0dfe85)

ただし使用できる学習済みモデルは決まっていて、タスクによっては使えないこともあります。
私などは[マルフク看板をアノテーション](https://qiita.com/everylittle/items/909c5a5c69e28617d061)したかったりするのですが、**既存の物体検出用のモデルではさっぱりです。というか、だから自分でモデルを学習しようとしているのに。**

というわけで、自分で好きなロジックやモデルを使って作成したラベルを、アノテーションツールで読み込めるように変換する方法をご紹介します。
例えば、OpenCVで作った以下のようなラベルをアノテーションツールで読み込み、必要なところだけ修正して深層学習に使うという方法も可能になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/bee88671-f497-cf91-569f-994834bda94f.png)
[OpenCV shape detection - PyImageSearch](https://www.pyimagesearch.com/2016/02/08/opencv-shape-detection/)

# 検証環境

- Windows 10 Home (1903)
- Python 3.6.8
    - pillow==7.1.2 (pipからインストール)
- VoTT 1.7.2

記事を書いた2020年5月の時点で VoTT 2.1.0 がリリースされていますが、高機能になりすぎてプロジェクトの構造が複雑で、自分で作ったアノテーションを読み込ませるのが難しそうでした。
そのため、今回は古いバージョンである 1.7.2 で読み込みます。

# 手順

## VoTTのインストール

VoTT 1.7.2を使います。``vott-win.exe`` をダウンロードして、インストールしてください。
[Release 1.7.2 · microsoft/VoTT](https://github.com/microsoft/VoTT/releases/tag/v1.7.2)

## データの準備

適当な場所にフォルダを作成し、その直下にアノテーションしたい画像 (JPG or PNG) をまとめます。
ここではフォルダのフルパスを ``C:\foo\bar\myproj`` とします。
VoTT 1.7.2の場合、対応するプロジェクトファイルの名前は ``C:\foo\bar\myproj.json`` となります。

## 自動アノテーション結果の作成

```make_vott_project.py
import sys
import json
import hashlib
import urllib
from pathlib import Path
from PIL import Image # pillow 7.1.2

imgdir = Path(sys.argv[1]).resolve()
projfile = imgdir.with_suffix(".json")

if projfile.exists():
    # 既存のプロジェクトを読み込む
    f = open(projfile, "r+")
    data = json.load(f)
else:
    # プロジェクトを新規作成する
    f = open(projfile, "w")
    data = {
        "frames": {},
        "framerate": "1",
        "inputTags": "mrfk", # タグリスト（カンマ区切り）
        "suggestionType": "track",
        "scd": False,
        "visitedFrames": [],
        "tag_colors": ["#0cc7ff"] # 領域の色（任意）
    }

with f:
    for imgfile in imgdir.glob("*.*"):
        if imgfile.suffix.lower() in [".jpg", ".png"]:
            if imgfile.name in data["frames"]:
                # エントリがある場合はスキップ
                continue
            else:
                # エントリがない場合、新規作成
                frame = []
                data["frames"][imgfile.name] = frame

            # 画像のサイズを取得
            img = Image.open(imgfile)
            w, h = img.size
            img.close()

            # 自分で検出した領域を列挙する（仮）
            points = [ # 頂点を辺でつながっている順に列挙する
                {"x": 0.0, "y": 0.0},
                {"x": w,   "y": 0.0},
                {"x": w,   "y": h},
                {"x": 0.0, "y": h}
            ]
            box = {  # 外接長方形
                "x1": min(p["x"] for p in points),
                "y1": min(p["y"] for p in points),
                "x2": max(p["x"] for p in points),
                "y2": max(p["y"] for p in points)
            }
            region = box.copy()
            region.update({
                "width": w,
                "height": h,
                "box": box,
                "points": points,
                "type": "rect",
                "tags": ["mrfk"], # 領域に付与するタグリスト
            })
            frame.append(region)

    # プロジェクトを保存
    f.seek(0)
    json.dump(data, f)
```

コマンドプロンプトから以下のように実行すると、各画像の全領域に ``mrfk`` というタグが付与されます。

```
python make_vott_project.py C:\foo\bar\myproj
```

## VoTTでの編集方法

VoTT 1.7.2 を起動し、画像のアイコンをクリックし、``C:\foo\bar\myproj`` フォルダを選択します。
**プロジェクトのファイル名はフォルダ名から自動的に決まることにご注意ください。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/fdcf8562-b1c7-0261-9ffe-d270264fcda0.png)

以下の画面はそのまま Continue でOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/3b7409f8-8700-ca23-23d7-5e4fc4774737.png)

ラベルが画像全体に対してついています。左向き三角2個・右向き三角2個のボタンで画像を切り替えられます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/fd6e5453-332f-ac55-4592-5a76d83ac120.png)

ラベルの修正をしたい場合は、左上の Regions Manipulation を選択してから、領域の四隅をドラッグ＆ドロップします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/e4eb439e-f670-85f5-f221-223af02dbe81.png)

メニューの File → Save で上書き保存できます。
更新されたjsonファイルを読み込んで、適当に学習データを作ればよいですね。

# カスタマイズ

## OpenCVなど外部で作ったアノテーションを読み込ませる

```python
# 自分で検出した領域を列挙する（仮）
```

の部分で、``points`` に検出結果を入れてください。
領域の各頂点の座標を、辺でつながっている順番に指定していきます。

例えば以下のチュートリアルのコードを流用するなら
[OpenCV shape detection - PyImageSearch](https://www.pyimagesearch.com/2016/02/08/opencv-shape-detection/)

```python
frame = []
# loop over the contours
for c in cnts:
    (略)
    points = [{"x": p[0], "y": p[1]} for p in c]
    (略)
    frame.append(region)
```

のようにすれば作れます。

## 多角形の領域を指定する

長方形ではない複雑な領域を指定したい場合は

```python
            region.update({
                "width": w,
                "height": h,
                "box": box,
                "points": points,
                "type": "rect",
                "tags": ["mrfk"], # 領域に付与するタグリスト
            })
```

この部分を

```python
            region.update({
                "width": w,
                "height": h,
                "box": box,
                "points": points,
                "type": "polygon", # ここを変更
                "tags": ["mrfk"], # 領域に付与するタグリスト
            })
```

に変更するだけです。
こうすると、1つの頂点を動かしたときに他の頂点が動かなくなります。以下のように、長方形ではない形状を作れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/c5d2a1a6-40fc-793a-f505-47b21e4adc54.png)

## タグを複数作る

まず、以下の ``inputTags`` のところに名前をカンマ区切り文字列で指定して

```python
    data = {
        "frames": {},
        "framerate": "1",
        "inputTags": "mrfk,chst,wide", # タグリスト（カンマ区切り）
        "suggestionType": "track",
        "scd": False,
        "visitedFrames": [],
        "tag_colors": ["#0cc7ff"] # 領域の色（任意）
    }
```

各領域の ``tags`` にはタグのリスト（カンマ区切り文字列ではなく、Pythonのリスト）を指定します。

```python
            region.update({
                "width": w,
                "height": h,
                "box": box,
                "points": points,
                "type": "rect",
                "tags": ["mrfk", "chst"], # 領域に付与するタグリスト
            })
```

ご覧のように、ウィンドウ左下と画像のツールチップに反映されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/415e95ef-1828-355c-fe23-a0db3fb4d597.png)

既存の物体検出結果のクラスをさらに細分化したいといった場合には、``inputTags`` だけを増やしておいて、各領域のクラス（タグ）はVoTTで編集するという使い方も可能です。

## タグの色を変える

領域の色や左下のタグリストの文字色を変えたい場合は、以下の ``tag_colors`` に色のリストを指定すればOKです。

```python
    data = {
        "frames": {},
        "framerate": "1",
        "inputTags": "mrfk,chst,wide", # タグリスト（カンマ区切り）
        "suggestionType": "track",
        "scd": False,
        "visitedFrames": [],
        "tag_colors": ["#ff4040", "#ffff40", "#008000"] # 領域の色（任意）
    }
```

以下のように好きな色で出せるようになります。（画像は自分でアノテーションを編集した後のものです）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204084/e720c09c-5788-d005-851e-c7c824581969.png)