4
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pythonの顔認識モデルをHerokuにデプロイしてFlutterから利用①

Last updated at Posted at 2020-05-15

Pythonの強力な顔認識モデルをHerokuにデプロイしてモバイルアプリから呼び出せるようにしました。

第一回はPython側のコードとHerokuへのデプロイまで、第二回ではFlutterでそのデプロイした処理を呼び出す部分を紹介したいと思います。

第二回のFlutterでの呼び出し部は以下の記事を参照ください。
Pythonの顔認識モデルをHerokuにデプロイしてFlutterから利用②

この記事の投稿者について

Twitterで顔認識を活用したアプリ開発についてつぶやいています。
https://twitter.com/studiothere2

noteにアプリ開発の日記を連載しています。
https://note.com/there2

顔認識モデル

利用ライブラリ

Python face_recognitionを利用しています。
https://github.com/ageitgey/face_recognition

ドキュメントが非常に充実しており、とても簡単に利用できます。
ベンチマークで99.38%の精度ということで、人間と同等以上のパフォーマンスが期待できます。

その他利用ライブラリ

あまり深く考えていませんが、Herokuでよく利用されていて軽量そうなものを選びました。

  • Web開発フレームワーク:Flask
  • HTTPサーバー:Gunicorn

Pythonソースコード解説

必要最小限のシンプルなものですが、骨子がわかるかと思います。

run.py
import face_recognition
from flask import Flask, jsonify, request, redirect
app = Flask(__name__)
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

まずは必要なライブラリのインポートと定数の宣言です。
appの変数名はGunicornの起動時にも利用します。

run.py
@app.route('/')
def hello_world():
    return 'hello'

rootで呼ばれた時に、単純にhelloだけを表示します。
これはサーバーが生きているかどうかの確認に便利です。

run.py
def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

画像ファイルが送られてきたときに、そのファイルの拡張子がpng, jpg, jpeg, gifのいずれかであることを確認します。
これら以外の場合はface recognitionの処理対象外としています。

run.py
@app.route('/embedding', methods=['GET', 'POST'])
def get_embedding():
    # Check if a valid image file was uploaded
    if request.method == 'POST':
        if 'file' not in request.files:
            print("file not in request.files")
            return redirect(request.url)

        file = request.files['file']

        if file.filename == '':
            print("file name is blank")
            return redirect(request.url)

メインの顔認識処理部です。
/embeddingで呼び出します。
POSTでinput typeがfileでファイル名に上記のpng, jpg, jpeg, gifのいずれかの場合のみ処理します。
それ以外の場合はリダイレクトで呼び出し元に戻します。その場合、response code302となります。

run.py
        if file and allowed_file(file.filename):
            print("file found")

            # The image file seems valid! Detect faces and return the result.
            img = face_recognition.load_image_file(file)
            emb = face_recognition.face_encodings(img)[0]
            return jsonify(emb.tolist())

正しいファイル形式でPOSTされてきた場合、face_recognitionにファイルを読み込ませて、それを使って128次元のFloatのEmbeddingを取得します。
このEmbeddingを使うと、他の顔との類似度を算出する事ができます。
この部分は別途モバイルアプリ側で実装しますので、Python側ではEmbeddingした顔の特徴量情報のみを返します。
face_recognitionの戻り値はNumpyの配列なので、tolist()を呼び出してPythonの配列にする必要があります。
それをJson化したものを戻します。

run.py
    # If no valid image file was uploaded, show the file upload form:
    return '''
    <!doctype html>
    <title>upload</title>
    <h1>Please select image and upload</h1>
    <form method="POST" enctype="multipart/form-data">
      <input type="file" name="file">
      <input type="submit" value="Upload">
    </form>
    '''

GETで呼ばれた場合は、ファイルアップロード用のHTMLを出力し、直接ファイルアップロードできるようにしています。

run.py
if __name__ == "__main__":
    app.run(host='0.0.0.0')

メイン処理で、Flaskを起動します。hostを0.0.0.0としておくことで、Herokuのデプロイ先でのURLで利用できるようにします。

動作確認

pip install -r requirements.txt

必要なライブラリはrequirements.txtにまとめていますので、上記のコマンドでまとめてインストールできます。
なおこのrequirements.txtはHerokuにデプロイする際も必要となります。
バージョンによってはcmake, dlib, face_recognitionの順にインストールしないとHerokuでうまくデプロイできないという情報があったので、以下のようにrequirements.txtに記載しておきました。

requirements.txt
boost==0.1
cmake
boost-py
dlib
face_recognition_models
face-recognition
Flask==1.1.2
gunicorn==20.0.4

gunicornを使ってローカルで起動してみます。

gunicorn run:app --log-file -

うまく起動したら以下のように表示されると思います。

$ gunicorn run:app --log-file -
[2020-05-15 09:02:24 +0900] [11143] [INFO] Starting gunicorn 20.0.4
[2020-05-15 09:02:24 +0900] [11143] [INFO] Listening at: http://127.0.0.1:8000 (11143)
[2020-05-15 09:02:24 +0900] [11143] [INFO] Using worker: sync
[2020-05-15 09:02:24 +0900] [11146] [INFO] Booting worker with pid: 11146

ブラウザでhttp://127.0.0.1:8000を開いてみましょう。
helloと表示されたら成功です。

続いて、http://127.0.0.1:8000/embeddingにアクセスすると以下のようにファイル選択のHTMLが表示されると思います。
人の顔画像のファイルをアップロードしてみてください。

image.png

次のように128個のFloatの配列が表示されたら成功です。
これがアップロードした画像の特徴量となるEmbeddingの中身です。

image.png

クラウド環境

Herokuの利用

WEBサービスのデプロイする先としてHerokuを選択しました。
Pythonで利用できること、デプロイが簡単でスケールしやすい事、利用料がリーズナブルだと感じた事からHerokuを選択しました。
試しの利用なら無料で利用できます。

対抗としてFirebaseのCloud Functionも考えましたが、今回のモデルは読み込みにある程度負担がかかりますので都度起動されるCloud Functionより常時起動しているVM型の方が良いと思いました。

Herokuも無料版だと30分利用がないとインスタンスが落ちてしまって呼び出し時に起動が走るようですが、有料版にすればインスタンスが落ちなくなります。
必要に応じて有料版に移行すればよいでしょう。

Herokuへのデプロイ

デプロイ作業は難しくありません。
Herokuへユーザ登録して以下のセットアップ手順に沿っていけばよいでしょう。
https://devcenter.heroku.com/articles/getting-started-with-python

Herokuのコマンドラインツールをインストールします。

sudo snap install heroku --classic

Herokuへログインします。
ブラウザが立ち上がって、ブラウザからログインできます。

heroku login

Herokuにアプリを登録します。
適当な名前でアプリが登録され、Herokuのダッシュボードで確認できるようになります。

heroku create

Procfileを作成します。
このファイルは、Herokuにどのようにサーバを起動すれば良いかを教えるファイルです。
以下のようにWEBとしてgunicornを起動するように指示を書いておきます。

web: gunicorn run:app --log-file -

以上で準備完了です。あとはgitコマンドでherokuのリポジトリにプッシュすればデプロイされます。
※事前にgitコミットを済ませておく必要があります。

git push heroku master

これでHerokuにデプロイされて、URLが表示されますので、そのURLにアクセスして動作する事を確認してみてください。

なお、初回のデプロイにはかなり時間がかかります。特にdlibがC++で書かれたライブラリでコンパイルが必要なため、インストール完了までかなり待たされました。
30分ぐらいかかったかもしれません。
エラー終了するのでなければ気軽に待ちましょう。

次回はFlutterからこのWEBサービスを呼び出す部分を紹介したいと思います。

4
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?