PepperとAzure Face APIで顔認証。


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

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として、以下の4つが用意されている。

Identify(顔識別)

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

  • 候補の顔は、類似点を計算しPersonGroupの中から高い順に候補を返す。
  • PersonGroupが事前にトレーニングされている必要がある。
  • 1回のリクエストで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の配列を指定することができる。
  • matchPersonmatchFaceの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のどれかを指定し、顔を認証します。

詳しくはこちら
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はこんな感じになりました。

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は、下のようにしてみた。

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を使用するための関数

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)

実際に顔認証してみて。

認証は問題なくできました!
年齢や笑顔といった要素は、少し怪しいのですが、認証は素晴らしい...
Personオブジェクトには、5枚ほど顔を登録しました。
多く写真を登録するば、より正確なデータが得られるのか・・・?