Image Analysis 4.0でカスタム画像分析モデルをCOCOファイルから作成する

# 概要
車両、ナンバープレートの物体検出の検証を行うため、学習用のデータセットを取得しImage Analysis 4.0に読み込ませモデル学習を行いました

# 前提条件
以下のリソースが作成済みであることとします

* Cognitive services マルチサービス アカウント
Japan {East, West}等***カスタムモデルの学習が出来ないリージョン***があるので注意してください
ここではUs Eastを使用しました

* ストレージ アカウント
上記Cognitive servicesからの***READ権限を付与してください***(SAS, Managed Id, パブリックアクセス)
ここではパブリックアクセスを使用しました

:::note info
[Computer Vision に Azure データ BLOB へのアクセス権を付与する](https://learn.microsoft.com/ja-jp/azure/cognitive-services/computer-vision/how-to/model-customization?tabs=python#grant-computer-vision-access-to-your-azure-data-blob)
:::

# 学習モデルを取得する
学習用のデータセットを自前で一から作成するのは大変なので、無料で公開されているデータセットを使用します

1. COCO - Common Objects in Context (https://cocodataset.org/)
学習用の画像、アノテーションの数共に膨大(18Gb)で今回の検証には大きすぎるため見送りました

2. roboflow (https://universe.roboflow.com/)
検索画面で「car plate」等のキーワードで検索すると手頃(11Mb程度)なデータセットがあったのでこれを使用します
License Plates Dataset (https://public.roboflow.com/object-detection/license-plates-us-eu)

:::note warn
各データセットには異なるラインセンスが採用されていますので使用する際には注意してください
:::

# 学習モデルの構成
ダウンロードしたファイルを展開すると以下のような構成になっています
```
$ mkdir dataset
$  (mkdir dataset && cd dataset && unzip ../License\ Plates.v3-original-license-plates.coco.zip)
$ tree dataset
dataset
├── README.dataset.txt
├── README.roboflow.txt
├── test
│   ├── _annotations.coco.json     # テスト用のデータセットのCOCOファイル
│   └── <テスト用の画像データが沢山>
├── train
│   ├── _annotations.coco.json　　　# 学習用のデータセットのCOCOファイル
│   └── <学習用の画像データが沢山>
└── valid
    ├── _annotations.coco.json　　　# 検証用のデータセットのCOCOファイル
    └── <検証用の画像データが沢山>

3 directories, 356 files
$
```

# COCOファイルの修正

ダウンロードしたCOCOファイルをそのまま使用すると学習の際に異常終了してしまうので修正を行います

:::note info
COCO ファイル フィールドリファレンス
https://learn.microsoft.com/ja-jp/azure/cognitive-services/computer-vision/how-to/model-customization?source=recommendations&tabs=rest#coco-file-field-reference
:::

問題点は3つ

1. カテゴリーIDが0始まりである(1始まりでないとエラー)
2. supercategoryは不要
3. imagesにcoco_url(absolute_url)要素がない

手作業で修正するのは大変なので簡単な変換スクリプトを作成します
```python:fix.py
import json, sys

with open(sys.argv[1]) as fp:
    root = json.load(fp)

    del root["categories"][0]
    for category in root["categories"]:
        del category["supercategory"]

    for image in root["images"]:
        file_name = image["file_name"]
        image["id"] += 1
        image["absolute_url"] = f"https://<ストレージアカウント名>.blob.core.windows.net/<コンテナ名>/custom-model/dataset/train/{file_name}"

    for annotation in root["annotations"]:
        annotation["id"] += 1
        annotation["image_id"] += 1
    
    print(json.dumps(dict(root), indent=4))
```

作成したプログラムで修正を行います
```
$ py fix.py dataset/train/_annotations.coco.json > dataset/custom.coco.json
$
```



# COCOファイルのBlob Storageへのアップロード

ストレージ上にコンテナを作成し学習モデルをアップロードします

```shell-session
$ az storage container create         \  # Containerの作成
-n <コンテナ名>                        \
--public-access blob                  \  # パブリックアクセスをBlobに設定
--account-name <ストレージアカウント名>    
$ az storage blob directory upload    \  # 学習モデルのアップロード
-c <コンテナ名>                        \
--account-name <ストレージアカウント名> \
-s dataset                            \
-d custom-model                       \
--recursive
$
```

# Computer Visionにトレーニングセットを作成する

[ここ](https://learn.microsoft.com/ja-jp/azure/cognitive-services/computer-vision/how-to/model-customization?tabs=python#register-the-dataset)からソースコードを取得して実行します

```python:regist.py
# Resource and key
import logging
logging.getLogger().setLevel(logging.INFO)
from cognitive_service_vision_model_customization_python_samples import ResourceType

resource_type = ResourceType.MULTI_SERVICE_RESOURCE

resource_name = None
multi_service_endpoint = None

if resource_type == ResourceType.SINGLE_SERVICE_RESOURCE:
    resource_name = '{specify_your_resource_name}'
    assert resource_name
else:
    multi_service_endpoint = 'https://<コグニティブサービス名>.cognitiveservices.azure.com/'
    assert multi_service_endpoint

resource_key = '<コグニティブサービスのキー>'

from cognitive_service_vision_model_customization_python_samples import DatasetClient, Dataset, AnnotationKind, AuthenticationKind, Authentication

dataset_name = '<作成するデータセット名>'
auth_kind = AuthenticationKind.MI

dataset_client = DatasetClient(resource_type, resource_name, multi_service_endpoint, resource_key)
annotation_file_uris = ['https://<ストレージアカウント名>.blob.core.windows.net/<コンテナ名>/custom-model/dataset/custom.coco.json']
# register dataset
if auth_kind == AuthenticationKind.SAS:
   # option 1: sas
   sas_auth = Authentication(AuthenticationKind.SAS, '{your_sas_token}') # note the token/query string is needed, not the full url
   dataset = Dataset(name=dataset_name,
                     annotation_kind=AnnotationKind.MULTICLASS_CLASSIFICATION,  # checkout AnnotationKind for all annotation kinds
                     annotation_file_uris=annotation_file_uris,
                     authentication=sas_auth)
else:
   # option 2: managed identity or public accessible. make sure your storage is accessible via the managed identiy, if it is not public accessible
   dataset = Dataset(name=dataset_name,
                     annotation_kind=AnnotationKind.OBJECT_DETECTION,  # checkout AnnotationKind for all annotation kinds
                     annotation_file_uris=annotation_file_uris)

reg_dataset = dataset_client.register_dataset(dataset)
logging.info(f'Register dataset: {reg_dataset.__dict__}')

# specify your evaluation dataset here, you can follow the same registeration process as the training dataset
eval_dataset = None
if eval_dataset:
   reg_eval_dataset = dataset_client.register_dataset(eval_dataset)
   logging.info(f'Register eval dataset: {reg_eval_dataset.__dict__}')
```

```shell-session
$ py -m venv venv
$ source venv/Scripts/activate
(venv)
$ pip install cognitive-service-vision-model-customization-python-samples
(venv)
$ py regist.py
(venv)
$
```

# 実際に学習を行う

[ここ](https://learn.microsoft.com/ja-jp/azure/cognitive-services/computer-vision/how-to/model-customization?tabs=python#train-a-model)からソースコードを取得し実行します

```python:train.py
import logging
logging.getLogger().setLevel(logging.INFO)
from cognitive_service_vision_model_customization_python_samples import ResourceType

resource_type = ResourceType.SINGLE_SERVICE_RESOURCE # or ResourceType.MULTI_SERVICE_RESOURCE
resource_type = ResourceType.MULTI_SERVICE_RESOURCE

resource_name = None
multi_service_endpoint = None

if resource_type == ResourceType.SINGLE_SERVICE_RESOURCE:
    resource_name = '{specify_your_resource_name}'
    assert resource_name
else:
    multi_service_endpoint = 'https://<コグニティブサービス名>.cognitiveservices.azure.com/'
    assert multi_service_endpoint

resource_key = '<コグニティブサービスのキー>'

from cognitive_service_vision_model_customization_python_samples import TrainingClient, Model, ModelKind, TrainingParameters, EvaluationParameters

model_name = '<作成するモデル名>'
dataset_name = '<作成したデータセット名>'
eval_dataset = None

training_client = TrainingClient(resource_type, resource_name, multi_service_endpoint, resource_key)
train_params = TrainingParameters(training_dataset_name=dataset_name, time_budget_in_hours=1, model_kind=ModelKind.GENERIC_OD)  # checkout ModelKind for all valid model kinds
eval_params = EvaluationParameters(test_dataset_name=eval_dataset.name) if eval_dataset else None
model = Model(model_name, train_params, eval_params)
model = training_client.train_model(model)
logging.info(f'Start training: {model.__dict__}')

training_client = TrainingClient(resource_type, resource_name, multi_service_endpoint, resource_key)
model = training_client.wait_for_training_completion(model_name, 30)
```

学習プログラムを実行します
このプログラムは学習が終了するまで30秒に一度進捗ステータスを確認しています
学習時間(time_budget_in_hours)にもよりますがかなり時間がかかります

```shell-session
$ py train.py
$ 
```
エラーが出力しなければ学習が終了です(∩´∀｀)∩


# モデルの学習が「Failed (NotEnoughBudget)」で終了した場合

:::note alert
トレーニングが失敗する理由とは？また、何を行う必要がありますか?
https://learn.microsoft.com/ja-jp/azure/cognitive-services/computer-vision/concept-model-customization#why-does-my-training-fail-and-what-i-should-do
> notEnoughBudget: 指定した予算は、トレーニングしているデータセットとモデルの種類のサイズには不十分です。 より大きな予算を指定します。

:::

とあるのですがここで言う予算はお金では無く時間なので、「time_budget_in_hours」に大きな値を指定してください