Custom Vision 物体検知モデル⇒画像切抜き⇒分類モデルを自動化する Azure Functions（Python）

# はじめに

Custom Vision をはじめ、コードを書かずに AI モデルが作成できるサービス/ツールはたくさんあり、AI を活用して自社の業務課題を解決しようと挑戦している方も多いことと思います
実際には、1つの AI モデルだけで実装しようとすると業務利用に達するレベルになかなかいきませんが、複数の AI モデルを組み合わせることで精度高く実現することができるケースが多く、
本例は Custom Vision の物体検知モデルと分類モデルを組合わせた 2 ステップアプローチで、画像系 AI ではよく使われている効果的なアプローチです
※実際は部品や装置の異常検知シナリオですが、フルーツとりんごで抽象化表現で説明させて頂きます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/91d6dc7d-3129-02b1-28ba-de631fb93d29.png)

本記事では、下図の点線赤枠内の物体検知モデル⇒画像切抜き⇒分類モデルを実装するメイン処理のPythonコードと、それをAzure Functions 化する部分を実装・解説します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/947ebe94-c479-0115-6900-a3869e64b9cf.png)

Custom Vision の物体検知モデルと分類モデルは作成されている前提として、本記事では説明しません
Custom Vision でモデル作成については以下ドキュメントを参考にして下さい

https://docs.microsoft.com/ja-jp/azure/cognitive-services/custom-vision-service/get-started-build-detector

# Python でメイン処理作成
まずは、Python で物体検知モデル⇒画像切抜き⇒分類モデルのメイン処理を作成します

## 開発環境
Pythonコードは Github に上げているコードを参考にして下さい

https://github.com/mimocihz/function-CV2step-AutoAnomalyDetection/tree/main/python_pg

作成した Python コードを Functions に組み込み、Azure へデプロイするため、Visual Studio Code で開発するのが便利です
また、Functions でサポートされている言語バージョンを選択するように気を付けましょう
今回は、Python 3.9.x で開発しました

https://docs.microsoft.com/ja-jp/azure/azure-functions/supported-languages#languages-by-runtime-version

## 処理の流れ
⓪環境定義
①Blob Storageから分析対象画像ファイルを取得
②検知モデル呼出
③検知モデルの解析結果(JSONファイル）から検知画像の数だけ以下ループ処理
　・検知画像の座標を取得
　・画像切取り別名保存
　・切取画像を分類モデルにかける
　・分析モデルの解析結果＝正常と異常の確率から判定値を記録
　・判定値に合わせて色を変えて元画像に枠線を書き戻して判定画像保存
　・判定一覧に行追加
④解析結果画像と判定一覧ファイルをBlob Storageに保存

### ⓪ 環境定義
・画像処理するけど、切取りや画像貼り付け合成レベルなので、OpenCV は使わずに Pillow（PIL）ライブラリでOK
・Functions のアプリ環境変数になること意識し、Custom Vision や Blob Storage のキー情報は環境変数化 (.envファイル参照)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/1c3b4a3c-3446-379a-fbd8-c0d586ea418a.png)

Custom Vision の URL 及び Key 情報はここから↓確認できます
外部から Custom Vision モデルを呼び出すには Publish しておく必要があります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/495505c2-1373-c12d-bb86-d5211be99982.png)

### ① Blob Storage から分析対象画像ファイルを取得
・下記役割で Blob Storage にコンテナー作成しておく
（今回は３つコンテナーを作成しましたが、1コンテナー内でフォルダ分けでもよいと思います）
　- input：解析対象の画像ファイル置き場
　- crop：検知モデルで切り取った画像ファイル置き場
　- results：解析結果画像と判定一覧ファイル置き場
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/4dc010fb-970b-1940-ecf8-86e881cb28fd.png)
・Functions ではインプットファイル名はパラメータで渡ってくる想定だけど、Python コードの時点ではベタ指定で動作確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/65214f36-a7f9-0267-a0de-5ab1b554bf6a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/aac36618-43f9-16fa-2a2a-c0ac7db4c58c.png)

### ② 検知モデル呼出
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/bcd8d613-3125-7090-c5b6-a89e6664f856.png)

### ③ 検知モデルの解析結果(JSONファイル）から検知画像の数だけ以下処理ループ
　・検知画像の座標を取得
　（thresholdで指定した確率以上の画像のみ出力対象とすることで余計な検知画像を排除している）
　・画像切取り別名保存
　・切取画像を分類モデルにかける
　・分析モデルの解析結果＝正常と異常の確率から判定値を記録
　・判定値に合わせて色を変えて元画像に枠線を書き戻して判定画像保存
　（probabilityの値で任意の閾値で2種類以上の判定を作り出している）
　・判定一覧に行追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/13928efc-d266-6458-3495-234622d2ecac.png)

### ④ 解析結果画像と判定一覧ファイルを Blob Storage に保存
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/e116d97f-91a1-502d-db94-98c01495bfdf.png)

### Pythonコードを実行して動作確認
正常に実行できれば、Blob Storage に結果ファイルが出力されるはずです
問題なければ、コードを.pyにエクスポートしておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/65e3bc85-f5a8-e4d0-5130-f80f8eb3c220.png)

# Azure Functions (Python) 作成・デプロイ
続いて、先ほど作成した Python コードを Functions に組み込みます

## Azure Functions とは
Azure Functions は、サーバーレスアプリケーションの1種です
何かの処理コードを実行するためには、必ずその実行環境＝サーバーが必要になります
サーバーレスとは、実際にサーバーがないわけではなく、ユーザーがサーバーの構築や管理を意識することなく利用できるというものです
ユーザは最も重要なコード開発に集中でき、残りの部分はAzure Functions が管理・処理します
また、従量課金プランでは、Functionsが実行されている間だけコストしか発生しません

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-overview

Azure Functions の開発には、関数の実行をホストするための Function App が存在する必要があります
コード言語によっては、Azure ポータルでFunction Appの作成とコード編集が可能ですが、
Python の場合はポータル内編集がサポートされておらず、Functions をローカルで開発してから Azure のFunction App に公開する必要があります

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-function-app-portal

## 手順
①Visual Studio Code でローカル Azure Functions (Python)プロジェクト作成
②requirements.txt にPython外部ライブラリを追加
③local.settings.json を環境変数追加
④__init__.py の import ライブラリ追加 と main 処理を開発したPythonコードに書き換える
⑤Azure Functions (Python) プロジェクトをAzureにデプロイする 
⑥Azureポータルで Function App が作成されていることを確認
⑦Function App をテスト実行してみる

### ① Visual Studio Code で Azure Functions (Python) プロジェクト作成
こちらのドキュメント手順通りに進めれば作成できるはずですが、入り口が少し変わってました(2022年6月時点)

https://docs.microsoft.com/ja-jp/azure/azure-functions/create-first-function-vs-code-python#create-an-azure-functions-project

Visual Studio Codeでまっさらなフォルダを作成して開いておく
アクティビティバーの Azure アイコンを選択し、[WORKSPACE]の右の＋ボタンをクリックし、 [Create Function…]をクリック 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/b8529782-5b0d-690b-ba6e-5137caa34fcc.png)

言語は Python を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/c983c3cf-ab1e-7121-4bb9-2da5b591c389.png)

言語バージョンを選択（Python メイン処理を開発したバージョンを選択）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/425946e5-f9fe-8468-3e98-ba3166514963.png)

Triggerを選択（今回はHTTP triggerを選択）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/7e250e31-e970-03ae-b93d-0364b1718aea.png)

名前はデフォルトのまま（必要に応じて変更して下さい）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/2ea9e386-11a6-ee77-02cf-26b4f97820d2.png)

認証レベルは Function を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/d55cdc20-750b-a8f2-ef7f-7f54461fa04f.png)

しばらくするとFunctions（Python）プロジェクトが作成される
アクティビティバーの エクスプローラー アイコンを選択し、いくつかファイルが作成されていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/b24dcd32-43ff-72dc-0d02-f95867fcf754.png)

### ② requirements.txt にPython外部ライブラリを追加
実行に必要な外部ライブラリを追加します
※ローカルPython開発時にpip install で追加したライブラリはここに追加

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-python?tabs=asgi%2Cazurecli-linux%2Capplication-level

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/56b2ed5e-2bf7-86d9-956b-374d1f17cfd1.png)


### ③ local.settings.json を環境変数追加
・Pythonの外部ライブラリを使用するため “PYTHON_ENABLE_WORKER_EXTENSIONS”: “1 ”  追加
・各種環境変数追加（ Custom Vision の URL や KEY、Blob Storage の接続情報等）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/accf5083-0cc6-2ed1-914b-a8e5bf2e24c0.png)

### ④ __init__.py の import ライブラリ追加 と main 処理を開発した Python コードに書き換える
・ライブラリの import 部分追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/fb45d940-59d0-32ad-8891-bbb206628132.png)

・main 処理部分を開発したPython コード(.pyの内容)に書き換える
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/a1492458-22ba-2513-6002-48787e75a7a6.png)

・local.settings.json の環境変数を参照させるため、load_dotenv('.env') をコメントアウトする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/832049fd-a89f-a4e1-e952-fd064814bc29.png)

・Python 開発時にはベタ指定していた解析画像ファイル名を引数にする
・Function App から filename というパラメータで画像ファイル名を渡す想定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/ce631eb7-4f73-9a2c-0ad2-55c663cc69eb.png)

・最後にReturn 文を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/421cabc3-8d81-ba31-c85d-de0cd545ecd6.png)

### ⑤ Azure Functions (Python) プロジェクトを Azure にデプロイする 
アクティビティバーの Azure アイコンを選択し、[WORKSPACE]のリフレッシュボタンで最新プロジェクトを表示し、[Functions]を選択した状態で雲ボタンをクリックし、[Deploy to Function App…]をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/e557fbe1-0b78-c970-6e6b-2d9b557d40a6.png)

デプロイ先のサブスクリプションを選択する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/75c37ef3-eeeb-4d1f-492e-cd4e7ccf2206.png)

デプロイ先の Functions を選択（※先に Functions を空で作成しておく必要あり）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/8d7ae0d0-06b6-fd3d-e7d1-9c48321bf741.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/4d226435-b744-b9d1-af6f-72c390dc9108.png)

Depoly 完了後に、以下メッセージが出るので、[Upload settings]ボタンをクリックすると、
local.settings 情報が Functions > 構成 > アプリケーション設定に反映される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/84d4fc96-b0d8-c2e7-ef45-dfec718b680e.png)

### ⑥ Azureポータルで Function App が作成されていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/5a4cb328-9cbf-f5e0-df39-5546b10f1df0.png)

### ⑦ Function App の構成でアプリケーション設定に環境変数が引き継がれていることを確認
右側の編集（えんぴつマーク）をクリックして値の編集ができる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/e58eb4b7-4d0b-6ea3-134c-4e5067822436.png)

### ⑧ Function App をテスト実行してみる
関数 > HttpTrigger1 > コードとテスト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/63575580-05f2-598b-1efc-27e4231cdb6f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/4a6f027e-7c54-fd33-8578-52465cf79c23.png)

# 【参考】Azure Functions に GitHub からデプロイする方法
Azure Functions にGitHubからデプロイする方法についてはこちらの記事を参照下さい

https://qiita.com/komiyasa/items/707cbc458aebded315cf

# さいごに
本例は Custom Vision の物体検知モデルと分類モデルを組合わせた2ステップアプローチでしたが、Custom Vision に限らず、複数のAIモデルを組合わせることで最終的な目的の精度向上が期待できます
AI モデル間でうまく処理を繋げるために、前段の AI モデルの解析結果(JSONファイル)結果から座標を取得したり、検知された数をカウントしたりと少々加工してあげることで実装できる内容が広がります
本記事の Python コードや Functions（Python）コードをベースにカスタマイズ利用頂ければ幸いです

本記事で作成した Azure Functions（Python）をさらに Logic Apps でバッチ化する手順はこちらの記事を参照下さい

https://qiita.com/mimocihz/items/bae9c956edf3b01de453