Microsoft の AI サービス Cognitive Services を使った OCR アプリ (Python, Flask) を git push トリガーで自動で Azure にデプロイするハンズオン

今回作る Web アプリは、文字の含まれる画像をアップロードすると、それを認識して文字列にして表示してくれる OCR (文字認識) アプリです。
アプリには Python 3, Flask, Microsoft の AI サービスの Cognitive Services などを使用しています。
クラウド (Microsoft Azure) へのデプロイは git push トリガーの自動デプロイとなっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/9bf43636-3b5e-52cf-ad51-a96b5e2941df.png)

### Thank you!

この OCR アプリは Microsoft 社のエンジニア [Christopher Harrison](https://twitter.com/geektrainer) さんによって作られた「 [sign-reading](https://github.com/geektrainer/sign-reading) 」という web アプリです。
これを題材にハンズオンをしていきましょう。

# 0. 必要なソフトウェアのインストール

1. git https://git-scm.com/downloads
2. Python3 https://www.python.org/downloads/
3. Visual Studio Code (VSCode) https://code.visualstudio.com/download

# 1. 初めにクラウド側の設定

まずはデプロイ先の web アプリのデプロイや、Cognitive Services のインスタンス (リソース) 立てたりなど、
クラウド (Azure) 側の設定をしていきましょう。

## Azure 無料アカウントの作成 (もしなければ)

もし Azure にアカウントをまだ持っていない場合 https://azure.com/ からアカウントを作成します。最初の30日間は無料で使えますので、このハンズオンにはお金はかかりません。

## Azure ポータルから Cloud Shell

GUI 画面ポチポチでもできますが、今回は Azure CLI (コマンドライン インターフェース) を使っていきましょう。

まず、Web ブラウザで、Microsoft Azure のポータルページ [portal.azure.com](https://portal.azure.com/?WT.mc_id=docs-StudentHackathon-machiy) を開きます。

画面上部に「**Cloud Shell**」を開けるボタンがあるので、そこをクリックします。

**Azure Cloud Shell** とは、Azure リソースを管理するための、Web ブラウザ上で利用できるシェル環境です。

参考）**Azure Cloud Shell** の公式ドキュメント
→ [https://docs.microsoft.com/ja-jp/azure/cloud-shell/overview](https://docs.microsoft.com/ja-jp/azure/cloud-shell/overview?WT.mc_id=docs-StudentHackathon-machiy)


![Azure Cloud Shell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/02571018-bb75-80b1-1d79-353d33e55edb.png)

画面上部の「**Cloud Shell**」ボタンを押すと、
Web ブラウザの中の、画面下半分に shell ウィンドウが現れます。

![Azure Cloud Shell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/cfe69f58-0556-f32e-3ee4-ba762d4f3434.png)

### Azure Cloud Shell 初めての人

**Cloud Shell** 立ち上げるのが初めての人は、
**Cloud Shell** 立ち上げる用の Storage Account (保存領域) を作ってねという画面になるので、画面の指示に従ってください

![Azure Cloud Shell 初めて](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/d5fb5412-0552-84ac-ea8e-49845e8719e9.png)

しばらく待つと、準備が出来て、このような画面になります。

![Azure Cloud Shell 初めて](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/506ac6c9-8e0f-5f83-c54e-a9e5072ec4a8.png)



## Cloud Shell で az hack インストール

ここで、前述した `az hack` エクステンション (拡張コマンド) を入れます。
今回のハンズオン用に、
素早くそれ用の Azure のリソースを作ってくれる便利コマンドです。

`az extension add --name 入れたいエクステンションの名前` で、その指定のエクステンションを入れることができます。

````shell
az extension add [--name]
                 [--pip-extra-index-urls]
                 [--pip-proxy]
                 [--source]
                 [--yes]
````

参考）az hack コマンドの公式ドキュメント
→ [https://docs.microsoft.com/ja-jp/cli/azure/ext/hack/hack](https://docs.microsoft.com/ja-jp/cli/azure/ext/hack/hack?WT.mc_id=docs-StudentHackathon-machiy&view=azure-cli-latest)

このハンズオンでは、このハンズオン用の `az hack` コマンドを使いたいので、以下のコマンドを **Azure Cloud Shell** に打ちましょう。

````bash
az extension add --name hack
````
参考）az extension の公式ドキュメント
→ [https://docs.microsoft.com/ja-jp/cli/azure/extension](https://docs.microsoft.com/ja-jp/cli/azure/extension?view=azure-cli-latest&viewFallbackFrom=azure-cli-latest&WT.mc_id=docs-StudentHackathon-machiy)

![Azure Cloud Shell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/a9200cd8-c14c-074e-bebd-f0702f1af190.png)

入りました。（az hack コマンドはまだ preview 版のようですね。でもちゃんと動くので大丈夫です）

````shell
az hack create --name signdemo --runtime python --ai --location westus2 --output yaml
````

(リソースのデプロイ先の場所 `--location` を、普段は `japaneast` (東日本リージョン) や `japanwest` (西日本リージョン) に指定しますが、今回は本社の作ったハンズオンに合わせて `westus2` (米国西部 2) のままでいきます。)

もし `westus2` で問題がある場合は `westus` で試してみてください。

ちなみに Azure Location 一覧は `az account list-locations -o table` で表示させることができます。[詳細はブログで書いた](https://qiita.com/chomado/items/beb41f7472d2bae97276)ので興味があったらどうぞ。

また、ここでは Python 製の Web アプリを作るので、オプションで `--runtime python` を指定しました。

上記のコマンド (az hack create ～) を叩くと、画面が走り出します。私は5分くらい待ちました。

![Azure Cloud Shell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/c2763348-b563-dde7-72a7-3b11a472858f.png)


ちなみに。
上のコマンドでは、我々はアプリの名前を `--name signdemo` と『signdemo』と指定しましたが、
ここはデプロイされた Web アプリの URL の一部となるので、世界で一意となるように、このハンズオン (のコマンド az hack を使った場合) では、新設設計で最後に勝手に短いランダムな文字列が付くようになっています。(ハンズオンだから皆同じの入れるからね)（私の場合「signdemo78a6e」になった）

## デプロイ先の Web ページを見てみる

Cloud Shell の出力の最後にデプロイ先の Web サイトの URL が載っていますので、クリックしてみます。

![Azure Cloud Shell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/3f1b944d-f0ce-ad5b-d825-aaa58ce0f4df.png)

私の場合の URL は「 [https://signdemo78a6e.azurewebsites.net/](https://signdemo78a6e.azurewebsites.net/) 」でした。（後で私はリソース消すかもしれないからもしリンク先が 404 になってたらすみません）

![azureCLI5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/3ac2d640-44ce-9d27-edc5-3e72f343c75e.png)

こんな感じの Web サイトが表示されていたらオーケーです。
Python 3.6.6 で動いているようですね。

## 出力情報の確認

さっきの出力された情報を見てみます。

↓ こんな感じの yaml の出力があったと思います。(上で叩いたコマンドで `--output yaml` を指定してたので)

````yaml
Settings and keys:
  Note: All values are stored as environmental variables on website
  Values:
    COGSVCS_CLIENTURL: https://westus2.api.cognitive.microsoft.com/
    COGSVCS_KEY: 00000000000000000000000000000000
    DATABASE_HOST: signdemo78a6e.mysql.database.azure.com
    DATABASE_NAME: signdemo78a6e
    DATABASE_PASSWORD: 00000000-0000-00000-00000-0000000000000
    DATABASE_PORT: '3306'
    DATABASE_USER: database_admin@signdemo78a6e
Website url: https://signdemo78a6e.azurewebsites.net
````

読んでみましょう。
この上の方の

````yaml
COGSVCS_CLIENTURL: https://westus2.api.cognitive.microsoft.com/
COGSVCS_KEY: 00000000000000000000000000000000
````

の部分が Azure Cognitive Services の情報で、
その下↓がデータベースの情報となっています。

````yaml
DATABASE_HOST: signdemo78a6e.mysql.database.azure.com
DATABASE_NAME: signdemo78a6e
DATABASE_PASSWORD: 00000000-0000-00000-00000-0000000000000
DATABASE_PORT: '3306'
DATABASE_USER: database_admin@signdemo78a6e
````

これらは

> Note: All values are stored as environmental variables on website

とある通り、デプロイした Web app の環境変数としてストアされています。(つまりアプリにハードコードしなくて良いということ）

## 何がデプロイされたか見てみる

では、画面上で何がデプロイされたか見てみましょう。
ポータルの「リソースグループ」を見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/9527613f-dd14-d460-73ce-c84be9146f70.png)

「リソースグループ」をクリックすると、さっきコマンド叩いて作ったリソースグループ (私の場合「signdemo78a6e」) が生えているのが分かります。↓

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/38dfb4b1-ce58-b320-c3cd-4d60da3ae229.png)

これをクリックして、このリソースグループの中にどんなリソースが作られているかを確認します。

![azureCLI6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/35d76181-da84-fd93-94b0-aaa6c453a4c5.png)

私の場合、無料の App Service Plan (アプリの動くサーバー) を既に持っていたのでそちらが使われましたが、
人によってはそれも含めて新規で作られるので、人によって 4 つ入っていると思います↓。

![azureCLI7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/6c394aca-62b7-b267-fa09-45b4cd591bbb.png)

上から、

 | |サービス名|説明
----|----|----|----
1| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/945d3cd9-e329-75f2-fd58-4314b429d6f2.png) |[App Service Plan](https://docs.microsoft.com/ja-jp/azure/app-service/overview-hosting-plans?WT.mc_id=docs-StudentHackathon-machiy)|Web アプリの乗るサーバのようなもの
2|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/d4a597c1-cbd9-9575-7aff-62f0b87391d3.png)| [Cognitive Services](https://docs.microsoft.com/ja-jp/azure/cognitive-services/?WT.mc_id=docs-StudentHackathon-machiy)|Microsoft の AI 系サービス
3| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/26df0483-c500-43ab-b226-0c395435ef5b.png)|[Azure Database for MySQL](https://docs.microsoft.com/ja-jp/azure/mysql/?WT.mc_id=docs-StudentHackathon-machiy)|Azure のマネージドな MySQL
4|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/85de7914-58f9-2135-c9b4-ee94873814e3.png)|[App Service](https://docs.microsoft.com/ja-jp/azure/app-service/?WT.mc_id=docs-StudentHackathon-machiy)|Webアプリ、Web API、モバイルバックエンドをホストするためサービス。App Service Plan の上に乗って動く

## App Service を見てみる

### アプリケーション設定 - 環境変数

では、このリソース一覧の中の **App Service** をクリックして、中を見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/166feb85-4a90-141a-5543-f3546c423509.png)

左側のメニューの「設定 (Settings)」の「構成 (Configuration)」から
「アプリケーション設定 (Application Settings)」を見ることができます。

![azureCLI8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/7987c3a5-d8bf-c177-9e38-f414337acbea.png)

データベースの情報や、Cognitive Services の API キー設定など、先ほどの Cloud Shell での出力で見た内容ですね！（セキュリティ上、データの実際の中身は非表示になっていますが、確認したいときはそれぞれの値の目のマークをクリックしたら見れます。）

これらの値は環境変数として、Python のプログラムからアクセスすることができます。

### デプロイセンター - Git push トリガーの自動デプロイ

![azureCLI9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/1271ba42-537e-db4e-3cf3-221a0d9d93b0.png)

左側のメニューの「デプロイメント (Deployment)」の「**デプロイセンター** (Deployment Center)」をクリックします。

「**デプロイセンター** (Deployment Center)」とは、Azure App Service のデプロイに関する設定をするところで、2018年冬に正式リリースされたサービスです。Web アプリの自動デプロイとかの設定が簡単にできます。

その中の「展開の資格情報 (Deployment Credentials)」をクリックします。

ここに書いてある「ユーザ名 (私の場合 "$signdemo78a6e")」と「パスワード」は後で使うことになります。

# 2. 次はアプリをローカルで動かしてみる

一度 Azure ポータルから離れて、
ローカルでの作業が始まります。

## Git Clone

今回動かす Web アプリのコードはこちらに上がっています
[https://github.com/geektrainer/sign-reading](https://github.com/geektrainer/sign-reading)

これを clone します。
Terminal や PowerShell、コマンドプロンプトなど (ちなみに私は [Windows Terminal](https://devblogs.microsoft.com/commandline/introducing-windows-terminal/?WT.mc_id=docs-StudentHackathon-machiy) で [PowerShell Core](https://docs.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-6&WT.mc_id=docs-StudentHackathon-machiy) を使っています) を開いて、
お好きなディレクトリに git clone しましょう。

````bash
$ git clone https://github.com/geektrainer/sign-reading
````
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/07dd92a0-39bf-064d-38e9-8f8a88f5f35f.png)

sign-reading ディレクトリに移動します。

````bash
$ cd sign-reading
````
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/9e788120-930e-69be-fbcd-6bcd2dbcb9df.png)

このディレクトリを Visual Studio Code で開きます。

````bash
$ code .
````

そうすると、このディレクトリを Visual Studio Code で開くことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/4c58d15a-a4f6-6908-72c1-ba5a57293436.png)



もし、ここで開いた Visual Studio Code に、まだ Python の環境が入っていなかったら「Python のエクステンション入れる？」とダイアログが出て来るので、Install をクリックして入れておきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/5adf67f1-8a50-03e5-eb23-81d1eeac3ad6.png)


## app.py を開く

まず、アプリの本体となる `app.py` を開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/fca1ae50-c7fd-4c25-8e65-388c93071261.png)

すると、いくつかのところに波線（エラー）が出ていることが分かります。

これは、例えば
「僕は flask なんて知らないよ」（定義が見つからない）
などと言っています。

なので、これらの依存しているパッケージのインストールをしていきましょう。

## 依存するパッケージのインストール

まずは何が必要なのか確認するために `requirements.txt` を開きます。

````python
# web framework
flask

# System variables
python-dotenv

# Azure SDK components
azure-cognitiveservices-vision-computervision
````


これら 3 つが必要なようですね。

 まず環境を作っておきます。
ターミナル (私は Visual Studio Code のターミナルウィンドウを使っています) で

````bash
$ python -m venv venv
````

そしてインストールするコマンド叩きます

````bash
$ pip install -r requirements.txt
````
どんどんインストールが走ります。ちなみにもしmacを使っていてpython2もインストールされている場合 `python` でなく `python3`、`pip`の代わりに `pip3` を叩く必要があるかも知れません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/4aefc3a5-837e-fa9f-1bd9-8b8ae25718c6.png)

インストールが終わると、あの `app.py` のエラー波線が消えましたね！

## API キーの設定ファイル追加

では、Sign-reading フォルダの中に `.env` ファイルを生やしましょう。

「新しいファイル作成」アイコンをクリックします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/216fc3de-b561-2024-c588-ece7de92ee17.png)

そして `.env` と名付けます。

空っぽのファイルができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/cd93d76b-3660-8bc0-d136-0f8adb7aab1c.png)

ここに、あの Azure で先ほど作っておいた Cognitive Services のキーなどをコピペしてきます。

（もし Cloud Shell の出力がセッション切れで消えてしまったという場合は、[Azure ポータル](https://portal.azure.com/?WT.mc_id=docs-StudentHackathon-machiy) 画面ポチポチの「アプリケーション設定」から取ってくることができます）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/da68fa9b-b342-1984-2602-411d55a9cad5.png)

````txt:.env
COGSVCS_CLIENTURL=https://westus2.api.cognitive.microsoft.com/
COGSVCS_KEY=コピペしてきた値
````
これで、アプリ本体の `app.py` から、今定義した API キーを読み込むことができます

↓ app.py
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/56d578c3-2ff0-aab7-632a-ffc39cf8c663.png)

## Flask 

ここで `app.py` の中身を見てみましょう。

````python:app.py
# app.py
import os, base64, json, requests
from flask import Flask, render_template, request
````

**Flask** をインポートしていますね。

Flask とは、Python の軽量な Web アプリフレームワークです。

Python の Web アプリ開発用のフレームワークはたくさんありますが、
他のはとてもリッチというか、たくさんのことが出来過ぎるので
今回は、軽量で学習コストも低めの Flask を使っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/766202ba-3837-2d71-593b-a470a4854665.png)

## app.py：Cognitive Services 接続部

vision クライアントを定義して、
.env ファイルに書いておいた API キーを使って
Cognitive Services に接続しています

````python:app.py
# Create vision_client
from msrest.authentication import CognitiveServicesCredentials
from azure.cognitiveservices.vision.computervision import ComputerVisionClient
from azure.cognitiveservices.vision.computervision.models import ComputerVisionErrorException

vision_credentials = CognitiveServicesCredentials(key)
vision_client = ComputerVisionClient(endpoint, vision_credentials)
````

### 日本語対応

ちなみに、今のデフォルトのままではこれは英語対応となっていますので、
日本語対応したい場合、少し追記する必要があります。

`client.recognize_printed_text_in_stream` メソッドの引数に `language="ja"` を追記します。

現在

````python:app.py
# 今こうなってる
def extract_text_from_image(image, client):
    result = client.recognize_printed_text_in_stream(image=image)
````

↓

````python:app.py
# こうしてください
def extract_text_from_image(image, client):
    result = client.recognize_printed_text_in_stream(image=image, language="ja")
````



## app.py：出力部

Cognitive Services から返ってきた、解析結果のテキストをひとつひとつ読み取って出力しているところです。

````python:app.py
def extract_text_from_image(image, client):
    result = client.recognize_printed_text_in_stream(image=image)

    lines=[]
    if len(result.regions) == 0:
        lines.append("Photo contains no text")
    else:
        for region in result.regions:
            for line in result.regions[0].lines:
                text = " ".join([word.text for word in line.words])
                lines.append(text)
    return lines
````



## アプリをローカルで動かす

いよいよアプリをローカルで動かしてみましょう。

````bash
$ flask run
````

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/c923f980-4de1-7297-4c0f-0af7e534a802.png)

すると一瞬で web サーバが立ち上がりました！
下の方に

> Running on http://127.0.0.1:5000

と書いてあるので、そこをクリックして、飛んでみます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/f59cb64a-c0fc-8e95-0371-a5eb9a628eb2.png)

おお！ローカルで動いてる！

それで、試しにサンプルの写真を食わせてみます。

「Upload Photo」をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/8b4027d8-0d2f-20c8-2322-a671d9191a63.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/77118faa-2e48-bf41-d104-be30965353a4.png)


すごい！認識した！動いてる！

では、次はクラウドへのデプロイですね！
今はまだローカルで動いてるだけだから私しか見れないからね

# 3. Azure へのデプロイ

まず、ローカルで動いている Web サーバを止めます。( 実行したターミナルに戻り `Ctrl + C` で止まります)

それで、今回は git 連携をしているので、
git push トリガーで自動デプロイすることが可能です。（かっこいい！！！！！！！！！！！）

````bash
$ git remote add azure https://gitのユーザ名@signdemo自分の番号.scm.azurewebsites.net/signdemo自分の番号.git
````

↑「自分の番号」のところは app service の自分のインスタンスの名前を入れてください。 (私の場合は `78a6e`)
↑「gitのユーザ名」のところは 自分の git のユーザ名を入れてください。 (私の場合は `chomado`)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/e9924e1f-0d89-8d8d-c722-ab1499d0d9ba.png)

## git push でデプロイ！

````bash
$ git push azure
````

をターミナルで打ちます。

すると、git の資格情報を訊ねるダイアログが出てきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/d32b1901-024e-1f6f-4914-8f18be05d128.png)

[Azure ポータル](https://portal.azure.com/?WT.mc_id=docs-StudentHackathon-machiy) の「デプロイセンター」の
「展開 (デプロイ) の資格情報」の中の
ユーザ名とパスワードをそれぞれ入れてください。

![azureCLI11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/38020c6a-fb57-d194-3991-37da26086ce4.png)

それでOKを押すと、どんどん `git push azure` が進んでいきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/0201fd6c-c5bb-f788-06ef-f307495c0aae.png)

（ローカル Git から Azure へのデプロイが初めての人で、git push がうまくいかなかった方は、
　こちらのドキュメントを参考に、デプロイユーザを構成する必要があります → [Azure App Service へのローカル Git デプロイ](https://docs.microsoft.com/ja-jp/azure/app-service/deploy-local-git?WT.mc_id=docs-StudentHackathon-machiy)）


終わったら、実際にデプロイ先の web サイトを確認してみましょう。

[Azure ポータル](https://portal.azure.com/?WT.mc_id=docs-StudentHackathon-machiy) から web サイトの URL を確認します。

![azureCLI12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24609/687ff788-9b5d-da4f-b869-bbcb26d5e4d7.png)

クリックすると、更新されたページを見ることができます！