PepperとAzure Face APIで顔認証。

既に製品としての活用事例があるPepper　×　MS AzureのFaceAPIを試してみた。
ドキュメントがしっかりしていて、実装しやすいので是非お試しを..:heart_eyes:

#Azure FaceAPI　ざっくりしたまとめ
FaceAPIには、顔認証に欠かせない主なAPIが5つあります。顔の検出を行うDetect、顔の認識を行うIdentify、Verify、Find Similar、Groupです。

##Face Detection(顔の検出)
####Detect
写真から人間の顔を検出するAPI。

- 検出された顔にはFaceIdが割り振られる。FaceIdの期限は、Detectコール後24時間。
- 一つの画像から最大64の顔を検出
- 画像は、バイナリもしくはURLで指定
- 検出した顔の示す四角形（left, top, width, height）を取得できる
- オプションで、gender, age, head pose, facial hair, glasses等の要素を取得できる

詳しくは、こちら
https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236

##Face Recognition(顔認識)
顔を認識するためのAPIとして、以下の４つが用意されている。

####Identify（顔識別）
Detectにより割り振られたFaceIdと一致する顔を、指定するPersonGroup（後述、顔のセット）から検索し、候補者を返すAPI。

- 候補の顔は、類似点を計算しPersonGroupの中から高い順に候補を返す。
- PersonGroupが事前にトレーニングされている必要がある。
- １回のリクエストで10人以内の顔を特定することができる。

詳しくは、こちら。
https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395239

####Verify（顔検証）
2つの顔（2つのDetectされた顔、もしくは、1つのDetectされた顔とPersonオブジェクト）が同一人物のものかどうかの認証をするAPI。

詳しくは、こちら。
https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f3039523a

####Find Similar（類似の顔を検出）
顔のセット（FaceList）を与えて、その中から問い合わせた顔（Detectされた顔）と類似の顔を検索するAPI。

- 検索に使う顔のセットには、FaceList（後述）、もしくは、DetectされたFaceIdの配列を指定することができる。
- `matchPerson`と`matchFace`の2つのモードがある。
    - `matchPerson`は、顔の閾値を利用してできるだけ同じ人の顔を検索する。（ない場合は空で返される）
    - `matchFace`は、閾値は無視して、類似性は低くとも、とにかく類似の顔としてランクされたものを返す

詳しくは、こちら。
https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395237

####Group（顔のグループ化）
Detectにより検出された複数の顔を、見た目の類似性に基づいてグループ化するAPI。

- グループは　、似ている顔のグループ、MessyGroup（似ている顔が見つからなかった顔のグループ）に分けられる。
- 少なくとも2以上の顔をが必要となる。最大で1000の顔をグルーピングすることができる。
- 同一人物の顔が異なるグループに分けられていることがあるので注意が必要になる。

詳しくは、こちら
https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395238

##顔認証するために事前に用意する顔のセット
上記5つのAPIを使用するには、顔のセットを事前に登録しておく必要がある。
顔のセットとして、PersonGroupとFaceListの2つが用意されている。
使用するAPIによって、PersonGroupとFaceListを使い分ける。

####Person Group
Identifyで使用される顔のセット。

- グループの中に、一人の顔を表すPersonオブジェクト（後述）を、最大で1000まで登録できる。
- Identifyでは、Person Groupの中のPersonの顔から特定する。
- Person Groupを操作するAPIとして、生成・削除などのAPIが用意されている。
- PersonGroupに新しいPersonオブジェクトを生成したり、Personオブジェクトの情報を更新した場合、PersonGroupをトレーニングさせる必要がある。（トレーニングされるまで情報は更新されない。）

詳しくはこちら
https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395244

####Person
Person Groupに含まれる、一人の情報を表すのがPersonオブジェクトである。

- Personオブジェクトには、248枚までの画像を追加することができる
- 中に含まれる顔の情報は、persistedFaceIdとして保持される。（FaceIdのような期限はない）
- URLもしくは、バイナリデータにより顔を追加できる。

Person GroupとPersonオブジェクトの関係のイメージは以下のような感じです。
Identifyでは、以下のようなPersonを含むPerson Groupのどれかを指定し、顔を認証します。
![personGroup_person.png](https://qiita-image-store.s3.amazonaws.com/0/68056/5357a17b-b4e4-fa25-6b82-c2c2432ca6fa.png)

詳しくはこちら
https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f3039523b

####Face List
Face Listは、Find Similarで使用される顔のセットです。

- Face Listは、顔のグループで、中に最大1000まで持つことができる。
- Face Listに追加された顔に期限はない。
- Face Listを操作するAPIとして、追加・削除等のAPIが用意されている。

詳しくはこちら
https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395250


#Pythonで実装。
顔の登録と認証をPepperで試してみました。認証はIdentifyで行います。

##顔の登録
Personオブジェクトに顔を登録します。
※先にPersonオブジェクトを追加するためのPerson Groupを生成しておきます。

[流れ]

1. Pepperのタブレットで入力された名前（登録名となる）を受け取る。
2. 入力された名前を登録名として、Personオブジェクトを生成する（Create a Person）
3. 作ったPersonにPepperのカメラで撮った顔を追加する（Add a Person Face）
4. Personオブジェクトへの顔の追加が成功したら、PersonGroupのトレーニングをする（Train Person Group）


Choregrapheはこんな感じになりました。
![スクリーンショット 2016-11-30 11.47.26.png](https://qiita-image-store.s3.amazonaws.com/0/68056/2bb55c69-00e8-7231-3ea7-58074730a2da.png)


###Create a Person
タブレットから名前を受け取ったら、Create a Personで、特定のPerson Groupに新しいPersonオブジェクトを追加します。
Create a Person APIを使用するための関数

```
#-----------------------------------
# Create a Person POST Request
# param1: name--Personオブジェクト登録名
# param2: user_data--説明（Optional）
#-----------------------------------
def create(self, name, user_data=None):
    import requests, json
    url = "https://api.projectoxford.ai/face/v1.0/persongroups/"
    url += str(self.memory.getData("AzureData/PersonGroupId"))
    url += "/persons"

    #Request Header
    headers = {
      'ocp-apim-subscription-key': self.memory.getData("AzureData/SubscriptionKey"),
      'Content-Type': "application/json"
    }

    #Request Body
    payload = {
        'name': name,
        'userData': user_data
    }

    try:
        res = requests.post(url, data=json.dumps(payload), headers=headers)
        if res.status_code == 200:
            return res.json()
        else:
            error = res.json()
            errorCode = error["error"]["code"].encode("utf8")
            self.logger.info(errorCode)
            errorMessage = error["error"]["message"].encode("utf8")
            self.logger.info(errorMessage)
            return None

    except requests.exceptions.RequestException as e:
        self.logger.info(e)
        return None
```

###Add a Person Face
続いて、生成されたPersonオブジェクトに顔を追加します。
Add a Person Face APIを使用するための関数

```
#--------------------------------------------------------------
# Add a Person Face POST Request
# param1: image--画像のバイナリデータ
# param2: person_id--Create a Personで生成されたPersonオブジェクトのID
# param3: user_data--(Optional)
# param4: target_face--(Optional)
#--------------------------------------------------------------
def add_face(self, image, person_id, user_data=None, target_face=None):
    import requests
        
    personGroupId = self.memory.getData("AzureData/PersonGroupId")
    url = "https://api.projectoxford.ai/face/v1.0/persongroups/" + personGroupId + "/persons/" + person_id + "/persistedFaces"
        
    #Request Header
    headers = {
      'ocp-apim-subscription-key': self.memory.getData("AzureData/SubscriptionKey"),
      'Content-Type': "application/octet-stream",
      'cache-control': "no-cache",
    }

    #Request Body
    params = {
        'userData': user_data,
        'targetFace': target_face,
    }

    try:
        res = requests.post(url, headers=headers, params=params, data=image)
        if res.status_code == 200:
            return res.json()
        else:
            error = res.json()
            errorCode = error["error"]["code"].encode("utf8")
            self.logger.info(errorCode)
            errorMessage = error["error"]["message"].encode("utf8")
            self.logger.info(errorMessage)

            #self.onFailure(errorMessage)

    except requests.exceptions.RequestException as e:
        self.logger.info(e)
        #self.onFailure(e)

```


###Train Person Group
顔が登録できたら、最後にPerson Groupをトレーニングします。
Train a Person Group APIを使用するための関数

```
#----------------------------
# Train a Person Group POST
#----------------------------
def train(self):
   import requests, json
   
   #SubscriptionKey  
   subscriptionKey = self.memory.getData("AzureData/SubscriptionKey")
   #Person Group ID
   personGroupId = self.memory.getData("AzureData/PersonGroupId")
        
   url = "https://api.projectoxford.ai/face/v1.0/persongroups/"
   url += str(personGroupId)
   url += "/train"

   #Request Header
   headers = {
      'ocp-apim-subscription-key': str(subscriptionKey),
      'Content-Type': "application/json"
   }

   #Request Body
   payload = {
       'personGroupId': person_group_id
   }

   try:
       res = requests.post(url, data=json.dumps(payload), headers=headers)
       if res.status_code == 202:
           return res
       else:
           self.logger.info(res.status_code)
           #self.onFailure()
                
   except requests.exceptions.RequestException as e:
       self.logger.info(e)
       #self.onFailure(e)

```

##顔の認証
Identifyで、指定したPersonGroupの中の顔と一致するものがあるか、検索する。

[流れ]
1. Pepperのカメラの画像に人間の顔があるか、検知する。（Detect）
2. Detectで検知したFaceIdを使用して、PersonGroupから顔の認証を行う。（Identify）
3. 候補者が見つかったら、見つかった人のPersonIDから、その人の情報を取得する。（Get a Person）

Choregrapheは、下のようにしてみた。
![スクリーンショット 2016-11-30 13.17.18.png](https://qiita-image-store.s3.amazonaws.com/0/68056/e0b56a0b-cbe5-9036-a9bd-75bf327bab01.png)


###Detect
Pepperでイメージをキャプチャし、顔が存在するか確認しに行く。返されるFaceIdを使用して次のIdentifyを行う。
Detect APIを使用するための関数

```
#-------------------------------
# Detect POST Request
# param1: image--画像のバイナリデータ
#-------------------------------
def detect(self, image):
    import requests

    url = "https://api.projectoxford.ai/face/v1.0/detect"

    #Request Header
    headers = {
      'ocp-apim-subscription-key': self.memory.getData("AzureData/SubscriptionKey"),
      'Content-Type': "application/octet-stream",
      'cache-control': "no-cache",
    }

    #Request Parameter
    params = {
        'returnFaceId': True,
        'returnFaceLandmarks': False,
        'returnFaceAttributes': "age,gender"
    }
    
    try:
        res = requests.post(url, headers=headers, params=params, data=image)

        if res == []:    
            self.noFaceDetected()
        else:
            self.logger.info(res)
            if res.status_code == 200:
                data = res.json()
                #self.logger.info(data)
                if data == []:    #配列が空の場合、顔が検出されていない
                    self.noFaceDetected(data)    
                else:
                    #FaceId
                    faceid = data[0]["faceId"].encode("utf8")
                    #Gender
                    gender = data[0]["faceAttributes"]["gender"].encode("utf8")
                    self.memory.insertData("AzureData/Gender", gender)
                    #Age
                    age = data[0]["faceAttributes"]["age"]
                    self.memory.insertData("AzureData/Age", age)

                    self.onSuccess(faceid)
            else:
                self.logger.info(res.status_code)
                error = res.json()
                self.logger.info(error["error"]["message"].encode("utf8"))
                self.onFailure(res.status_code)

    except requests.exceptions.RequestException as e:
            self.logger.info(e)
            self.onFailure(e)
   
```

###Identify
Detectから返されるFaceIdで、指定のPersonGroupから一致する顔を検索する。
Identify APIを使用するための関数

```
#-------------------------------
# Identify POST Request
# param1: face_ids --Detectで返されるFace IDの配列
# param2: max_candidates_return --Optional
# param3: threshold --Optional
#-------------------------------
def identify(self, face_ids, max_candidates_return=1, threshold=None):
    import requests, json
    url = 'https://api.projectoxford.ai/face/v1.0/identify'

    #Request Header
    headers = {
      'ocp-apim-subscription-key': self.memory.getData("AzureData/SubscriptionKey"),
      'Content-Type': "application/json"
    }

    #Request Body
    payload = {
        'personGroupId': self.memory.getData("AzureData/PersonGroupId"),
        'faceIds': face_ids,
        'maxNumOfCandidatesReturned': max_candidates_return,
        'confidenceThreshold': threshold,
    }

    return requests.post(url, data=json.dumps(payload), headers=headers).json()


```

###Get a Person
Identifyで特定された候補者のPersonIdから、Get a Personで名前を取得する。
Get a Person APIを使用するための関数

```ruby:PythonScript.py
#-------------------------------
# Get a Person GET Request
# param1: personId --取得したいPersonオブジェクトのID
#-------------------------------
def get_a_person(self, personId):
    import requests
    personGroupId = self.memory.getData("AzureData/PersonGroupId")
    url = "https://api.projectoxford.ai/face/v1.0/persongroups/" + personGroupId + "/persons/" + personId

    #Request Header
    headers = {
      'ocp-apim-subscription-key': self.memory.getData("AzureData/SubscriptionKey"),
      'Content-Type': "application/json"
    }

    return requests.get(url, headers=headers)
```

#実際に顔認証してみて。

認証は問題なくできました！
年齢や笑顔といった要素は、少し怪しいのですが、認証は素晴らしい...:clap::clap:
Personオブジェクトには、5枚ほど顔を登録しました。
多く写真を登録するば、より正確なデータが得られるのか・・・？
![IMG_1455.JPG](https://qiita-image-store.s3.amazonaws.com/0/68056/6d773ffe-278c-64d3-1939-f05d4d0af37f.jpeg)