Google App Engine、FlaskとOpenCVの環境構築

# はじめに
- Google App EngineでのOpenCVの環境構築は、簡単です。昔は、大変だった様ですが、今(2019年11月)は改善された様です。
- 今回、以前作成した[Herokuでの顔認識アプリ](https://qiita.com/maeda_mikio/items/1a6899e104aac7b138fc)をGoogle App Engineへ移植します。と言っても、設定ファイル位しか違いが有りません。
- ソース一式は [ここ](https://github.com/maedamikio/public/tree/master/flask_opencv_app_engine) です。
- 参考: [App Engine スタンダード環境での Python 3 のクイックスタート](https://cloud.google.com/appengine/docs/standard/python3/quickstart)


# Google App Engine 環境の選定
- 今回は、スタンダート環境のPython3.7を利用します。理由は、以下の通りです。
- 最新のPythonバージョンであるため。
- スタンダード環境のPython3.7では、あらゆるフレームワーク、ライブラリ、バイナリを実行できるため。
- スタンダード環境のPython2.7では、Cコードによるユーザー提供のライブラリ(OpenCV等)を許可していないため。
- スタンダード環境のPython2.7の独自のAPIは、利用しないため。
- スタンダード環境は、無料枠があるため。フレキシブル環境は、無料枠がないため。
- こちらの情報を参照しました。
    - [Google App Engine での Python](https://cloud.google.com/appengine/docs/python/)
    - [App Engine 環境の選択](https://cloud.google.com/appengine/docs/the-appengine-environments)


# Cloud SDK
- デプロイに必要な[Cloud SDK](https://cloud.google.com/sdk/docs/)のMac版をインストールします。

## Pythonのバージョンの確認
- Cloud SDKは、バージョンは、2.7.9以上であればOKです。
- このバージョンは、あくまでCloud SDKが利用するものです。
- つまり、App Engine上のPython3.7とは、別のものです。ややこしい。
- しかし、今だに[Python2](https://pythonclock.org/)って大丈夫なんですかね？

```shell
$ python -V
Python 2.7.10
```

## パッケージのダウンロードと解凍
- tar.gzの解凍とインストールをします。
- 利用中のシェルにCloud SDKのパスを設定してくれます。

```shell
$ tar xvzf google-cloud-sdk-245.0.0-darwin-x86_64.tar.gz

$ ./google-cloud-sdk/install.sh
Welcome to the Google Cloud SDK!
省略
Modify profile to update your $PATH and enable shell command
completion?

Do you want to continue (Y/n)?  y
```

## SDKの初期化
- 新規プロジェクトは、ここで作っても良いです。私は、(Y/n)で n にしました。この後、作ります。

```shell
$ gcloud init
Welcome! This command will take you through the configuration of gcloud.
省略
You are logged in as: [xxx@gmail.com].

This account has no projects.

Would you like to create one? (Y/n)?  n
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/dbfbaed1-55e8-0a30-b6a4-84e3aa27c689.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/990435b5-b66b-8ac1-8d1c-73e857b76f52.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/42f820e5-4ba7-4349-ef0e-ea4d6c399e88.png)

## コンポーネントのアップデート

```shell
$ gcloud components update
```

## プロジェクトの新規作成
- 今回は、`flask-opencv-20191104`で作成しました。
- これは、アプリのURLの一部になるので、本番環境とする場合は、慎重に考えましょう。
- あと、作成したプロジェクトをデフォルトにするオプションを付けています。

```shell
$ gcloud projects create flask-opencv-20191104 --set-as-default
```

## App Engine アプリの初期化とリージョンの選択
- 作成したプロジェクトで App Engine の初期化をします。
- リージョンは、`asia-northeast1`は東京、`asia-northeast2`は大阪になります。今回は、東京を選びました。
- 後から、リージョンの変更は、できません。

```shell
$ gcloud app create --project=flask-opencv-20191104
省略
Please choose the region where you want your App Engine application
located:

 [1] asia-east2    (supports standard and flexible)
 [2] asia-northeast1 (supports standard and flexible)
 [3] asia-northeast2 (supports standard and flexible)
省略
Please enter your numeric choice:  2
```

## Python3.7用のApp Engine拡張機能のインストール

```shell
$ gcloud components install app-engine-python
```

# 顔認識アプリ

## app.yaml
- App Engineの設定ファイルです。Pythonのバージョンの指定だけでOKです。その他は、オプションで設定出来ます。
- 例えば、`entrypoint`は、`Gunicorn`、`uWSGI`や`Tornado`等を設定することが出来ます。
- 指定が無い場合は、`Gunicorn`が利用され、`requirements.txt`にパッケージが自動追加されます。また、`main.py`の`app`が利用されます。
- パフォーマンスの観点から`requirements.txt`に`gunicorn`を加えない方が良いそうです。
- 詳しくは [app.yaml リファレンス](https://cloud.google.com/appengine/docs/standard/python3/config/appref)を確認して下さい。

```python:app.yaml
runtime: python37
```

## main.py
- 以前紹介したHeroku用のアプリと同じです。
- 上記で解説した通り、`main.py`で`app`を利用していますね。

```python:main.py
import time

import cv2
import numpy as np

from flask import Flask, make_response, render_template, request
app = Flask(__name__)


face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
eye_cascade = cv2.CascadeClassifier("haarcascade_eye.xml")


@app.route("/", methods=["GET", "POST"])
def index():

    if request.method == "POST":

        start = time.time()

        count = int(request.form.get("count", 1))
        image = request.files.get("image")
        data = image.read()

        for _ in range(count):
            img = np.fromstring(data, np.uint8)
            img = cv2.imdecode(img, 1)
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

            faces = face_cascade.detectMultiScale(gray, 1.3, 5)
            for (x,y,w,h) in faces:
                cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
                roi_gray = gray[y:y+h, x:x+w]
                roi_color = img[y:y+h, x:x+w]
                eyes = eye_cascade.detectMultiScale(roi_gray)
                for (ex,ey,ew,eh) in eyes:
                    cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

        data = cv2.imencode(".jpeg", img)[1].tostring()

        response = make_response()
        response.data = data
        response.mimetype = "image/jpeg"

        end = time.time()

        print("opencv version: {}, elapsed second: {}".format(cv2.__version__, end - start))

        return response

    return render_template("index.html")
```

## requirements.txt
- Heroku用アプリとの違いは、`gunicorn`だけです。
- 上記の通り、App Engineがデプロイ時に自動的に追加してくれるので、ベストプラクティスに従い削除しています。

```requirements.txt
flask
numpy
opencv-python
```

## その他ファイル
- 以下は、Heroku用アプリと一緒です。

```
haarcascade_eye.xml
haarcascade_frontalface_default.xml
templates/index.html
```


# デプロイ

## Cloud Build API の有効化
- APIライブラリで Cloud Build API を有効化します。
- 課金等の設定も必要です。
- 今回は、無料枠の範囲内で試した後、プロジェクト毎削除する予定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/42d04e94-6feb-e5f7-e7ba-54827aa47f9c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/5c49fa48-2025-d2df-e9a5-c2b2a1cbfa9d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/a2781a4e-5a45-33d2-8fac-a1fce95a72c9.png)

## デプロイ
- デプロイ対象の `app.yaml`やソースコード、アプリのURL等が確認出来ます。

```shell
$ gcloud app deploy
Services to deploy:

descriptor:      [flask_opencv/app.yaml]
source:          [flask_opencv]
target project:  [flask-opencv-20191104]
target service:  [default]
target version:  [20191105t223639]
target url:      [https://flask-opencv-20191104.appspot.com]
````


# 動作確認
- 上記のURLをブラウザへ入力、もしくは `gcloud app browse`でブラウザが起動します。
- 画像(jpeg)と処理回数(1〜100)を送信し、顔認識の画像を受信します。
- 処理回数は、性能評価用に準備したものです。今回は、10回にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/284d4c7c-1eac-5ca4-ac0a-20e04e1982c9.png)

## 送信画像
- 顔認識に使う画像は、一部界隈ではおなじみの顔画像を使いました。
- Heroku用アプリで利用した物と同じです。

![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/43a5084b-8745-fdf4-3074-2aabb7237d4c.jpeg)

## ログの確認
- 今回は、約23秒でした。1回あたり 2秒ですね。Herokuよりは遅いな。
- まぁ、App Engineは、オートスケールするしな。きっと大丈夫。

```
$ gcloud app logs tail -s default
Waiting for new log entries...
省略
2019-11-05 13:51:03 default[20191105t223639]  "POST / HTTP/1.1" 200
2019-11-05 13:51:26 default[20191105t223639]  opencv version: 4.1.1, elapsed second: 23.51261281967163
```


# おわりに
- Google App EngineでFlaskとOpenCVを使う方法と性能評価をしました。
- 以前、Herokuで確認しましたが、ソースコードの変更は不要でした。設定ファイルが違うくらいです。しかもごくわずかです。
- 今後は、AWSとAzureでも同様の確認したいと思います。