Pythonで作るサーバーレスな顔認識API

この記事は[Python-AdventCalandar-2016](http://qiita.com/advent-calendar/2016/python)の16日目の記事です。


こんにちは。Pythonしてますか？顔認識してますか？サーバーレスしてますか？

この記事はPythonで手軽に顔認識APIサーバーを立てたいという方に向けた記事になります。アドベントカレンダーでは学術的な話を書こうと思っていたのですが、色々とタイミングが良かったのでこの題目にしました。

最初に宣言しておきますが、今回はAWSをフルに使っていきます。


## 顔認識について

OpenCVで行うことも非常に容易ですが、今回はAWSをフルに使っていく！ということで、今年のre:inventで発表されたサービス「Amazon Rekognition」を使っていきます。

一言で言えば、物体や顔を高精度に認識したり、検索したりできる凄いサービスです（雑

詳細は https://aws.amazon.com/jp/rekognition/ をご覧ください。

料金表は以下となっています。使えば使うほど料金がかかるのでご注意を。

<img width="866" alt="スクリーンショット 2016-12-16 13.38.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/73483/2beff51d-3b7d-7398-ca2a-039bb9cd4878.png">

https://aws.amazon.com/jp/rekognition/pricing/

## サーバーレスアーキテクチャについて

AWSでサーバーレスアーキテクチャといえば「API Gateway + Lambda」ですね。

今回はPython製の「Chalice」というライブラリを使ってそれらを管理します。

https://github.com/awslabs/chalice

### Chaliceを使ってみる

chaliceコマンドを使用してとりあえず「hello world」してみます。

``` bash
$ pip install chalice

# frekoの部分は適当な自分のプロジェクト名にしてください
$ chalice new-project freko && cd freko
$ cat app.py

from chalice import Chalice

app = Chalice(app_name="helloworld")

@app.route("/")
def index():
    return {"hello": "world"}

$ chalice deploy
...
Your application is available at: https://endpoint/dev

$ curl https://endpoint/dev
{"hello": "world"}
```

ここまで出来たら、あとはS3とRekognitionのAPIを叩くだけです。

## AWSのAPIを使う

### AWSの設定

既に作成されている人が多数かと思いますが、まずは`aws-cli`を使って設定を書き出します。

今回のサンプルではリージョンは `eu-west-1` を選択しました。Rekognitionが対応しているリージョンならばどこでも良いと思います。


``` bash
$ pip install awscli
$ aws configure
```
https://github.com/aws/aws-cli

boto3というPython製のAWS-SDKもインストールしておきます。

``` bash
$ pip install boto3
```

https://github.com/boto/boto3


### S3へ画像をアップロードする

GUIでポチポチやるのが面倒なので全部APIで作ります。

```py
REGION = 'eu-west-1'

BUCKET = 'freko-default'
S3 = boto3.resource('s3')

# 指定したバケット名が存在していなければS3にバケットを作成する
def create_s3_bucket_if_not_exists():
    exists = True
    try:
        S3.meta.client.head_bucket(Bucket=BUCKET)
    except botocore.exceptions.ClientError as ex:
        error_code = int(ex.response['Error']['Code'])
        if error_code == 404:
            exists = False
    if exists:
        return
    else:
        try:
            S3.create_bucket(Bucket=BUCKET, CreateBucketConfiguration={
                'LocationConstraint': REGION})
        except Exception as ex:
            raise ChaliceViewError("fail to create bucket s3. error = " + ex.message)
    return

# ファイルをS3にアップロードする
def upload_file_s3_bucket(obj_name, image_file_name):
    try:
        s3_object = S3.Object(BUCKET, obj_name)
        s3_object.upload_file(image_file_name)
    except Exception as ex:
        raise ChaliceViewError("fail to upload file s3. error = " + ex.message)
```

### ReKognitionAPIを使って顔認識する

```py 

REKOGNITION = boto3.client('rekognition')

# S3のバケットにあるファイルを指定して、顔認識する
def detect_faces(name):
    try:
        response = REKOGNITION.detect_faces(
            Image={
                'S3Object': {
                    'Bucket': BUCKET,
                    'Name': name,
                }
            },
            Attributes=[
                'DEFAULT',
            ]
        )
        return response
    except Exception as ex:
        raise ChaliceViewError("fail to detect faces. error = " + ex.message)
```

Attributesに「ALL」を指定するとより多くの情報が得られます。

### ポリシーを変更する

Chaliceはデプロイ時にコードで使っているAPIを調べて勝手にポリシーを設定してくれるのですが、まだプレビュー版なので全APIには対応していないようです。S3にファイルをアップする際に使用した`upload_file`などは読み取ってくれませんでした。。

S3とReKognitionをpolicy.jsonのStatementに追加します。（テスト用なのでフルにしてます）

``` bash
$ vim .chalice/policy.json

"Statement": [
    {
      "Action": [
        "s3:*"
      ],
      "Resource": "*",
      "Effect": "Allow"
    },
    {
      "Action": [
        "rekognition:*"
      ],
      "Resource": "*",
      "Effect": "Allow"
    }

    ...

]
```

ポリシーを自動生成せずデプロイするコマンドは以下です。Makefileとかを適当に作って登録しておきましょう。

```
$ chalice deploy --no-autogen-policy
```

### APIキーを設定する

エンドポイント叩き放題だと怖いので、APIキーによる認証とリクエスト制限を一応入れておきます。

詳細は http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-api-keys.html をご覧ください。

Chaliceでは`api_key_required=True`にすればAPIキー認証が必要なAPIが出来上がります。

```py
@app.route('/face', methods=['POST'], content_types=['application/json'], api_key_required=True)
```

## デプロイしたAPIを叩いてみる

テスト用にレナさんを使用します。

![lena.jpg](https://qiita-image-store.s3.amazonaws.com/0/73483/1912d9e9-eff3-9d60-b1dc-7891cefcb503.jpeg)

APIのパラメータには name = ファイル名, base64 = 画像をbase64エンコードした文字列 を入力するようにします。

APIキーとURLは各自設定してください。

``` bash
$ (echo -n '{"name":"lenna.jpg", "base64": "'; base64 lenna.jpg; echo '"}') | curl -H "x-api-key:your-api-key" -H "Content-Type:application/json" -d @- https://your-place.execute-api.eu-west-1.amazonaws.com/dev/face | jq
```

単純に画像に顔があるかどうかを判断するだけなら、FaceDetailsに値が存在する && Confidenceバリューが大きいかどうかを見るだけで十分そうです。

```json
{
  "exists": true,
  "response": {
    "FaceDetails": [
      {
        "BoundingBox": {
          "Width": 0.4585798680782318,
          "Top": 0.3210059106349945,
          "Left": 0.34467455744743347,
          "Height": 0.4585798680782318
        },
        "Landmarks": [
          {
            "Y": 0.501218318939209,
            "X": 0.5236561894416809,
            "Type": "eyeLeft"
          },
          {
            "Y": 0.50351482629776,
            "X": 0.6624458432197571,
            "Type": "eyeRight"
          },
          {
            "Y": 0.5982820391654968,
            "X": 0.6305037140846252,
            "Type": "nose"
          },
          {
            "Y": 0.6746630072593689,
            "X": 0.521257758140564,
            "Type": "mouthLeft"
          },
          {
            "Y": 0.6727028489112854,
            "X": 0.6275562644004822,
            "Type": "mouthRight"
          }
        ],
        "Pose": {
          "Yaw": 30.472450256347656,
          "Roll": -1.429526448249817,
          "Pitch": -5.346992015838623
        },
        "Quality": {
          "Sharpness": 160,
          "Brightness": 36.45581817626953
        },
        "Confidence": 99.94509887695312
      }
    ],
    "ResponseMetadata": {
      ...
    },
    "OrientationCorrection": "ROTATE_0"
  }
}
```


ちなみに顔認識されなかった場合は以下のようなレスポンスになります。

```json
{
  "exists": false,
  "response": {
    "FaceDetails": [],
    "ResponseMetadata": {
      ...
    }
  }
}
```

## 最後に

AWS-SDKの使い方みたいな記事になってしまいました...。反省せずに、次回はS3にPutされたらRekognitionを呼ぶみたいなかっこいいことをやっていきたいと思います。


コード全文は以下にあります。参考になれば幸いです。

https://github.com/gotokatsuya/freko