cloudvisionApi + google App engine + python3でゲームのリザルト画像解析APIを作ってみた。


ある日突然、私がはまっているゲームの画像から、テキストを抽出したいという衝動にかられた。
勉強もかねて個人的メモを公開することにした。
結論から言うと、全ての文字を抽出することは出来なかったが、9割ほどは抽出が出来た。

目的

ゲームの画像からテキストを抽出する。

以下の画像から、機械学習を使い、「ドワーフ、 ナイト」などの種族名を抽出する。
gcpのapp engineや、python3など、普段使わない技術の習得も目的とする。

最終ソースコード
https://github.com/t-kabaya/autochessTextExtracter

使用技術

cloudvision api

google が開発した画像解析APIだ。エンドポイントに画像をPOSTするだけで、画像解析を行うことが出来る。

以下のサイトで、cloud vision apiを試すことが出来る。試しにゲームの結果画面で画像解析をする。
cloud vision apiを試す。

テキストを抽出

ラベル付け
文字、スクリーンショット、PCゲーム、テクノロジー、ソフトウェア、電子機器というラベルが付いた。
有名人の画像をアップロードすると、人物判定も行える。凄すぎる。

google app engine

google platformのサービスの一つ。
Awsのelastic beanstalkや、helokuと全く同じfass
elastic beanstalkや、helokuの辛さは知っていたので、新しい技術に挑戦してみた。
結果から言うと、これは大成功で、gaeなしでは生きられない体になってしまった。
cloud functionなどのサーバレスでも十分だったのだが、インフラを勉強したかったので、gaeを選択。

python3

機械学習ならpython3と言う事で、選択。

Flask

pythonで、ミニマルなAPIを作るにはこれ一択。(app engineの公式チュートリアルでも使用されている。)
とても使いやすく、5分で理解出来る。

実装準備

まず、macには、python2が標準インストールされているが、python3は、インストールされていない。

そのため、python3をmacにインストールし、その後、

pip3 install -U flask
を実行する。
python3 xxxx
pip3 xxxx
のように、python3を実行するには、各種コマンドに3をつけないといけないのが煩わしい。

pythonには、標準ライブラリに、unittestと言うライブラリがあり、単体テストを、pythonのみで記述出来る。
今回は、モックが必要な箇所以外は、このunittestでテストを記述した。

cloudvision apiを使うには二つの選択肢がある。
1 REST
2 クライアントSDKを導入する。
今回は、よりパフォーマンスの良いクライアントSDKを採用した。
以下のコマンドで、ライブラリをインポートする。
pip install --upgrade google-cloud-vision
ドキュメント

実装

mkdir awesomeProject
cd awesomeProject

google app engineは、app.yamlと言うファイルで、構成を管理する。
といっても、python3を使う場合、一行追加するだけだ。

touch app.yaml
app.yaml
runtime: python37

次に、flaskでエンドポイントを作成する。google app engineのデフォルトのポートは、8080だ。
flaskのデフォルトポートは、5000のため、flaskに8080を使うよう指示する。
またgoogle app engineでは、エントリーポイントは、main.pyと指定されているため、ルーティングは、main.py内で行う。

touch main.py
main.py
# [START gae_python37_app]
from flask import Flask

@app.route("/")
def extract_text_from_autochess_image():
    return 'hello world!'

if __name__ == '__main__':
  app.run(host='127.0.0.1', port=8080, debug=True)
# [END gae_python37_app]

ローカルで、python3 main.py
と実行した後、localhost:8080にアクセスして、'hello world!'と帰って来れば成功だ。

deploy

まず、deployには二つの方法がある。gcloudコマンドを使用して、ローカルマシンからデプロイする方法と、cloud shellを使用して、gcpコンソールからデプロイする方法だ。

今回は、より簡易的な、gcpコンソールからデプロイを試した。(gcloud コマンドを使用したデプロイの方が安全で早い)

まず。gaeコンソールから、gaeペインをクリックし、cloud shellを有効化する。すると、リモートサーバーにssh接続され、bashでサーバーを操作可能になる。

次に、現れたターミナルに、

git clone origin master
cd project
pip3 install -r requirements.txt
python3 main.py

と打ち、リモートサーバーにローカルマシンの環境を再現する。次は、flaskが動くかテストしてみよう。

python3 main.py

APIが立ち上がったら、テストはOK

作成したアプリのルートフォルダに移動し、以下の二個のコマンドを打つだけで、デプロイが完了する。

gcloud app create
gcloud app deploy

とても簡単だ。

アプリを更新するなら、以下の2コマンドで更新。

git pull
gcloud app deploy

実装編

さて、前回までで、google app engineへのデプロイが完了した。

次は画像解析の実装に移る。
コードは、以下に置いた。
https://github.com/t-kabaya/autochessTextExtracter

cloud vision apiを使って画像解析を行うには、以下の4行だけで良い。そうすると、画像内の文字列と、その位置がリターンされる。

extract_text_from_image.py
from google.cloud import vision

def detect_text_uri(image_uri):
    client = vision.ImageAnnotatorClient()
    image = vision.types.Image()
    image.source.image_uri = image_uri
    response = client.text_detection(image=image)

    texts = response.text_annotations

    return texts

このリターンされたobjectを、jsonに加工するのが割と手がかかる。
最終的に以下のエンドポイントで、jsonをリターンする。
詳しくはgithubのソースコードを読んで欲しい
flask慣れしていないとは言え酷いコードだ。
```

[START gae_python37_app]

from flask import Flask
from flask import jsonify
from flask import request
from index import create_synergy_text

app = Flask(name)

文字化け防止。flaskは、defaultではutf8をサポートしない。

app.config['JSON_AS_ASCII'] = False

TODO: 本来は、image_uriが存在しない場合、imageが、autochess_resultの物で無かった場合に個別のエラーメッセージを返してやるのが正しい。

エラーが帰っているのに、successというstatusが返っている。404, 200など、status codedを

GET param: image_uri: string

@app.route("/")
def extract_text_from_autochess_image():
try:
image_uri = request.args.get('image_uri')
if image_uri is None:
return 'image_uriパラメーターが必要です。'

response = create_synergy_text(image_uri)

return jsonify(response)

except:
return 'エラー autochessの画像を投稿してください'

if name == 'main':
app.run(host='127.0.0.1', port=8080, debug=True)

[END gae_python37_app]



## おまけ pythonで驚いたこと。
### List Comprehensions syntax
日本語ではリスト内包表記と言われる。

例えば、以下のfor文で、new_listを作成していたのを、

```python
new_list = []
for i in old_list:
    if filter(i):
        new_list.append(expressions(i))

以下のように一行で書ける。最初は、気持ち悪かったが、慣れるととても便利だ。インデント崩れを気にしなくて良いしね。

new_list = [expression(i) for i in old_list if filter(i)]

init.py

pythonでは、subdirectoryからファイルをインポートが出来ない。これを解決するには、init.pyという名前の空ファイルを置かないといけない。
この仕様は酷すぎる。

requirements.txt

このファイルで、依存関係を書き込む。wheelなどを使った方が良いよ。venvを使用して、仮想環境ごとに、ライブラリをインストールしてねと言われたが、今回は小規模apiなので、requirements.txtで事足りた。