Face++を用いて顔認識してみた

#実行環境
**Ubuntu 16.04** 
**Python 3.7**
**opencv-python 4.1.0.25**
#概要
中国が開発したAPI「[Face++](https://www.faceplusplus.com/)」を用いて、顔認識をしてみる。
手順は以下の通り。

**①アカウント登録
②利用するAPIを取得
③デモの確認
④プログラム
⑤顔認識**


##**①アカウント登録**
まず、Face++を利用するためにアカウント登録する必要がある。
公式ページで登録を行い、自分の**API Key**と**API Secret**をゲットする。
これらが各APIを利用する際に必要となる。

作成したAPI KeyとAPI SecretはFace++の Apps/API Key で確認できる。
![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/dd6bed5c-a243-f1cb-7847-06b04322936f.png)

##**②利用するAPIを取得**
今回作成した顔認識プログラムで利用するAPIは3種類。
いずれもFacial Recognitionというグループに存在し、
顔を識別するために**Detect API**と**Search API**、顔を登録するために**FaceSet Create API** を用いる。
各APIのパラメータなどは公式の[API reference](https://console.faceplusplus.com/documents/5678948)で確認できる。
![b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/85d030bd-e2f0-39de-a458-30e77b5c9826.png)
今回、赤枠内のAPIを使用する。


##**③デモの確認**
Search APIなどはFace++の公式ページで[デモ](https://www.faceplusplus.com/face-searching/#demo)があるため、どのような出力が得られるのか見てみよう。
![Screenshot from 2019-11-13 10-34-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/356ceaf1-a947-a2d8-90a5-72487ef311bf.png)


上図の**<font color="blue">Probe Face</font>**が比較元となる画像で、
類似度が高い順にデータベースの画像から**<font color="orange">Candidate Face</font>**が選ばれる。
（作成する顔認識プログラムでは、<font color="blue">Probe Face</font>を未知の入力画像とし、
<font color="orange">Candidate Face</font>を認識したい人物の画像として用意しておく。）

各画像の比較結果はJSONファイルとして出力される。

```結果.json
{
  "time_used": 420,
  "thresholds": {
    "1e-3": 62.327,
    "1e-5": 73.975,
    "1e-4": 69.101
  },
  "faces": [
    {
      "face_token": "8d25b53edf9e20bad18bebe3b0c8e06c",
      "face_rectangle": {
        "width": 252,
        "top": 170,
        "height": 252,
        "left": 102
      }
    }
  ],
  "results": [
    {
      "confidence": 97.076,
      "user_id": "",
      "face_token": "8323ce719cb1129e4abf4ada1129cbc9"
    },
    {
      "confidence": 93.254,
      "user_id": "",
      "face_token": "fd6c81b63615b62b8506f33a6748fd95"
    },
    {
      "confidence": 66.15,
      "user_id": "",
      "face_token": "192672385b603e6b54cf884cd019a620"
    },
    {
      "confidence": 63.826,
      "user_id": "",
      "face_token": "34bbf05899b53968dcee620aa06a35e7"
    },
    {
      "confidence": 57.875,
      "user_id": "",
      "face_token": "ee6cbca281f449d3ed6040ca63b4c52c"
    }
  ],
  "image_id": "+b1mqy/4tPkV6QMTyRVGyA==",
  "request_id": "1573608536,e6ac4f2a-62fb-40a9-890b-696eba9a32a1"
}
```
resultsキーの各要素が<font color="blue">Probe Face</font>と<font color="orange">Candidate Face</font>との比較結果を表している。
2枚の画像の類似度は**"confidence"**として出力される。
上の結果を見ると、<font color="orange">Candidate Face1</font>のconfidenceは97.076、
<font color="orange">Candidate Face2</font>のconfidenceは93.254であり、高い類似度になっている。
それ以外の結果は、類似度70を下回っている。
この類似度を利用し、顔認識プログラムを作成する。

##④プログラム
顔認識のプログラムは以下の通り。
APIの使い方については下記URLを参考にした。
https://github.com/Doarakko/api-challenge/tree/master/facepp-api

```Python:main.py

import time
import re
import base64
import requests
import glob
import cv2

API_KEY = '自分のAPI Key'
API_SECRET = '自分のAPI Secret'

# 顔を検出するAPI
def detect_image(img):
    endpoint = 'https://api-us.faceplusplus.com'
    response = requests.post(
        endpoint + '/facepp/v3/detect',
        {
            'api_key': API_KEY,
            'api_secret': API_SECRET,
            'image_base64': img,
        }
    )
    # 1秒スリープ
    time.sleep(1)
    # レスポンスのステータスコードが200以外の場合
    if response.status_code != 200:
        print('[Error] ')
        return -1
    resources = response.json()
    return resources

# 顔を識別するAPI
def search_image(img,face_set):
    endpoint = 'https://api-us.faceplusplus.com'
    res, dst_data = cv2.imencode('.jpg', img)
    img_base64 = base64.b64encode(dst_data)
    # 顔検出APIにリクエストを送る
    faces=detect_image(img_base64)
    for face in faces["faces"]:
        try:
            response = requests.post(
                endpoint + '/facepp/v3/search',
                {
                    'api_key': API_KEY,
                    'api_secret': API_SECRET,
                    'face_token': str(face["face_token"]),
                    'faceset_token': face_set["faceset_token"],
                    'return_result_count': 1,
                }
            )
            # 1秒スリープ
            time.sleep(1)
            # レスポンスのステータスコードが200以外の場合
            if response.status_code != 200:
                return -1
            resources = response.json()
            print("成功")
            print(resources)
            img=draw_img(face,resources,img)
        except Exception as e:
            return -1
    #画像を保存
    cv2.imwrite("./output/result_"+filename, img)
    return resources

# 一番確率の高い顔の情報を描画
def draw_img(face,resources,img):
    try:
        left = face["face_rectangle"]["left"]
        top = face["face_rectangle"]["top"]
        right = left + face["face_rectangle"]["width"]
        bottom = top + face["face_rectangle"]["height"]
        # 矩形を描画
        cv2.rectangle(img, (left, top), (right, bottom), (0, 0, 255), 3)
        font = cv2.FONT_HERSHEY_SIMPLEX
        # 確率の描画
        cv2.putText(img, str(resources["results"][0]["confidence"]) + "%", (left, top), font, 0.5, (255, 255, 255), 3, cv2.LINE_AA)
        cv2.putText(img, str(resources["results"][0]["confidence"]) + "%", (left, top), font, 0.5, (0, 0, 0), 1, cv2.LINE_AA)
        # 名前の描画
        cv2.putText(img, str(resources["results"][0]["user_id"]) , (left, top-20), font, 0.5, (255, 255, 255), 3, cv2.LINE_AA)
        cv2.putText(img, str(resources["results"][0]["user_id"]) , (left, top-20), font, 0.5, (0, 0, 0), 1, cv2.LINE_AA)
    except Exception as e:
        print("none")

    return img

# 比較する顔画像を登録
def create_faceset(face_list,tags):
    id_list = []
    for (img_path,face_id) in zip(face_list,tags):
        img_file = base64.encodebytes(open(img_path, 'rb').read())
        resources = detect_image(img_file)
        set_userid(resources["faces"][0]["face_token"],face_id)
        id_list.append(resources["faces"][0]["face_token"])

    id_list=str(id_list).replace("'", "")
    id_list=str(id_list).replace("[", "")
    id_list=str(id_list).replace("]", "")
    id_list=str(id_list).replace(" ", "")

    print(resources)
    print(id_list)

    endpoint = 'https://api-us.faceplusplus.com'
    try:
        response = requests.post(
            endpoint + '/facepp/v3/faceset/create',
            {
                'api_key': API_KEY,
                'api_secret': API_SECRET,
                'display_name': 'facebank',
                'face_tokens': id_list,
            }
        )
        # 1秒スリープ
        time.sleep(1)
        # レスポンスのステータスコードが200以外の場合
        if response.status_code != 200:
            return -1
        resources = response.json()
        print(resources)
        return resources
    except Exception as e:
        return -1

def set_userid(face_token,user):
    endpoint = 'https://api-us.faceplusplus.com'
    try:
        response = requests.post(
            endpoint + '/facepp/v3/face/setuserid',
            {
                'api_key': API_KEY,
                'api_secret': API_SECRET,
                'display_name': 'facebank',
                'face_token':face_token,
                'user_id':user,
        }
        )
        # 1秒スリープ
        time.sleep(1)
        # レスポンスのステータスコードが200以外の場合
        if response.status_code != 200:
            return -1
        resources = response.json()
        print(resources)
        return resources
    except Exception as e:
        return -1

if __name__ == '__main__':
    # 識別したい画像を取得
    filename="input.jpg"
    img=cv2.imread('./input/'+filename)
    # フォルダ内の画像を取得
    face_list = glob.glob('./facebank/*/*.jpg')
    face_list.sort()
    tags=[]# 各名前(user_id)格納用
    # 登録データから読み取り
    for face_path in face_list:
        tags.append(str(re.search(r'./facebank/(.+)/.+', face_path).group(1)))
    # APIで比較する顔を定義
    face_set = create_faceset(face_list,tags)
    # 識別するAPIにリクエストを送る
    resources = search_image(img,face_set)

```

各APIの引数などについては[公式のリファレンス](https://console.faceplusplus.com/documents/5678948)を参照。

フォルダ階層は以下の通り。

```
─ facebank/
    ├ A/ 
    │ └ A.jpg
    ├ B/ 
    │ └ B.jpg
    ├ C/ 
    │ └ C.jpg
    ├ D/ 
    │ └ D.jpg
    └ E/
      └ E.jpg
─ input/
    └ input.jpg
─ output/
    └ result_input.jpg
─ main.py
```
inputフォルダには識別する画像を入れる。
facebankフォルダの中に各人物名のフォルダを用意し、
そのフォルダ内に顔写真を入れておく。(顔写真の名前は任意)

##⑤顔認識
いざ、顔認識。
登録＆認識する写真は、人物のフリー素材を公開している「[ぱくたそ](https://www.pakutaso.com/)」から拝借した。
今回認識対象として登録した写真は以下の5枚。

- ezakiさん <img width="200" alt="ezakisan" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/919c4326-151d-ebef-7540-778ca948fba1.jpeg">
- murataさん <img width="200" alt="muratasan" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/c25f2653-1af9-fc5e-b7e4-ee1ad26807f2.jpeg">
- narishigeさん <img width="200" alt="muratasan" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/f443a3e8-0cc6-a18c-875d-04fb8fe3af4f.jpeg">
- okawaさん <img width="200" alt="muratasan" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/3416fa33-272c-a1d7-ef03-6855200aef75.jpeg">
- yuseiさん <img width="200" alt="muratasan" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/bb3b5a91-30ea-1c29-7259-29eb4c425207.jpeg">

そして、認識結果がこちら！
![result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/3f13603c-697b-44d2-b46d-3795b22e08ef.jpeg)
![result_helpIMGL6330_TP_V.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/4e82fecf-5bce-0da3-25c2-2345762db4c2.jpeg)
![result_PAK86_sotugyousei15213941_TP_V.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/22f3fc01-9b87-4cf4-69b5-facd94cc2f29.jpeg)
![result_N612_shibuyagawadedenwasuruikemen_TP_V.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/04b36cd2-2d8d-07f2-d154-8c40bbe95cff.jpeg)

なんと顔を登録した人は80%以上の類似度で全員正解！
顔写真の登録枚数は各人物1枚なのに高い精度だ。

顔を未登録の人も、登録した人の中で最も高い類似度の人として識別されているが、
下図のように低い類似度になっていることが分かる。
![333.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/6ca01fe7-48fa-260d-af00-8f001760a10a.jpeg)　![222.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/d60ca08a-c81c-6f12-7187-c76748273ca1.jpeg)

Face++にアカウント登録すると、WEBページの[デモ](https://www.faceplusplus.com/face-searching/#demo)で自前の画像で顔認識などができるため、
気になる方はお試ししてみてはいかがでしょうか。