年齢・性別判定APIを比較しました（その１）

[Nextremer Advent Calendar 2016](http://qiita.com/advent-calendar/2016/nextremer)の19日目の記事です。

# はじめに
顔画像から年齢や性別を判定するAPIは多くありますが、どのAPIが良いか気になり試してみました(2016年12月時点)。

今回比較したのは次の４つです。APIではないですが、参考のためフリーの年齢・性別モデルも比較しました。

結論から言うと、FaceAPIが良かったです。

- Microsoft社: [Face API](https://azure.microsoft.com/ja-jp/services/cognitive-services/face/)
- IBM社: [Visual Recognition](https://www.ibm.com/watson/developercloud/visual-recognition.html)
- PUX社: [顔検出(評価版)](http://www.pux.co.jp/%E8%A3%BD%E5%93%81%E6%83%85%E5%A0%B1/%E7%94%BB%E5%83%8F%E8%AA%8D%E8%AD%98/%E9%A1%94%E8%AA%8D%E8%AD%98/)
- その他：[フリーの年齢・性別モデル](http://www.openu.ac.il/home/hassner/projects/cnn_agegender/)

検証は下記サイトを参考に、有名人のInstagram画像と生年月日を用いました。

- [芸能人Instagramランキング](http://www.talentinsta.com)

なお、今回は性別判定についての記事になります。

# 準備

各種APIのユーザ登録を行い、Pythonで実装しました。
ユーザ登録の方法は割愛します。

### APIキーの準備
登録時に取得したAPIキーはconfigファイルにまとめました。
各APIキー取得のために`get_api_key()`を定義しています。

```python
cfg = ConfigParser.SafeConfigParser()
cfg.read('config/api.cfg')

def get_api_key(section):
    return cfg.get(section,'key') if cfg.has_section(section) and cfg.has_option(section, 'key') else {}

```

configファイルは以下の通りです。

```
[MSApi]
key = (Face APIのキー)

[IBMApi]
key =(Visual Recognitionのキー)

[PUXApi]
key =(PUX 評価版APIのキー)
domain = (PUX 評価版APIのドメイン)
```

### Microsoft社: Face APIの使用
pathの画像について判定します。
判定結果をgender(性別), age(類推年齢)の2つに保存しています。
ageは小数点まで求まります。

```python
def ms_predict(path):
    print("//// MS ////")
    # 該当のAPIキーを取得する
    ms_key = get_api_key('MSApi')
    if ms_key:
      CF.Key.set(ms_key)
      with open(path, 'rb') as image_file:
        result = CF.face.detect(image_file, landmarks=True, attributes='age,gender')
       # 顔画像を検出した場合に、gender,ageに値を入れる
       if len(result) > 0:
        target = result[0]["faceAttributes"]
        res["gender"] = target["gender"]
        res["age"] = target["age"]
        print(res)
      else:
        print("No Face")
    return res
```

### IBM社: Visual Recognitionの使用

pathの画像について判定します。
判定結果をgender(性別), lo(類推年齢の下限), hi（類推年齢の上限)の3つに保存しています。
loやhiは必ずしも存在しない場合があり注意が必要です。

```Python
def ibm_predict(path):
    print("//// IBM ////")
    # 該当のAPIキーを取得する
    ibm_key = get_api_key('IBMApi')
    if ibm_key:
      # Visual Recognition 無料プランでは、イメージのタグ付けおよび顔検出に対して、Bluemix アカウント 1 つにつき 1 日あたり 250 個のイベント (イメージ) を利用できます。
      IBM_recognition = VisualRecognitionV3('2016-05-20', api_key=ibm_key)
      with open(path, 'rb') as image_file:
        result = IBM_recognition.detect_faces(images_file=image_file)
      # 顔画像を検出した場合に、gender,lo,hiに値を入れる
      if len(result["images"][0]["faces"]) > 0:
        target = result["images"][0]["faces"][0]
        res["gender"] = target["gender"]["gender"]
        if target["age"].has_key("min"):
          res["lo"] = target["age"]["min"]
        if target["age"].has_key("max"):
          res["hi"] = target["age"]["max"]
        print(res)
      else:
        print("No Face")
    return res
```

### PUX社: 顔検出(評価版)の使用
pathの画像について判定します。
判定結果をgender(性別), age(類推年齢)の2つに保存しています。

```python
#PUXのdetectFaceAPIを使用
def pux_predict(path):
    print("//// PUX ////")
    # 該当のAPIキーを取得する
    pux_key = get_api_key('PUXApi')
    if pux_key:
      url = 'http://eval.api.polestars.jp:8080/webapi/face.do'
      # APIの規約に合わせるために一時的にjpegに変換
      im = Image.open(path)
      im.save("/tmp/tmp.jpg", "JPEG")
      with open("/tmp/tmp.jpg", 'rb') as image_file:
        data = {
          'apiKey': pux_key,
          'inputBase64': base64.b64encode(image_file.read()).decode(),
          'response': 'json'
        }
        result = json.loads(requests.post(url, data, headers={'Content-Type': 'application/x-www-form-urlencoded'}).text)
      # 顔画像を検出した場合に、gender,ageに値を入れる
      if result["results"]["faceRecognition"].has_key("detectionFaceInfo"):
        target = result["results"]["faceRecognition"]["detectionFaceInfo"][0]
        res["gender"] = "Male" if str(target["genderJudge"]["genderResult"]) == "0" else "Female"
        res["age"] = target["ageJudge"]["ageResult"]
        print(res)
      else:
        print("No Face")
    return res
```

### その他：フリーの年齢・性別モデルの使用
pathの画像について判定します。
判定結果をgender(性別), lo(類推年齢の下限), hi（類推年齢の上限)の3つに保存しています。
[こちら](https://github.com/spiorf/deepdream/tree/master/models/cnn_age_gender_models_and_data.0.0.2)を参考に、入出力を下記の形で実装しました。

```python
def org_predict(path):
    print("//// Original ////")
    input_image = caffe.io.load_image(path)

    # 顔画像を検出した場合に、gender,lo,hiに値を入れる
    res["gender"] = _predict_gender(input_image)
    res["lo"], res["hi"] = _predict_age(input_image)
    print(res)
    return res
```

## データの準備

Instagramより、本人のみ写った写真を1~10枚集めました。対象年齢は20歳〜83歳です。
175名分(男性:73名、女性:102名)で、計816枚集めました。
ちなみに、今回一番大変だったのは画像集めでした:frowning2:

対象者の年齢分布は次の通りです。

![スクリーンショット 2016-12-20 13.26.34.png](https://qiita-image-store.s3.amazonaws.com/0/8839/810b5301-e90a-54b4-2dcc-dfe1726ae8f1.png)



# 実験結果(性別判定)

## 正解と不正解、非検出数

![スクリーンショット 2016-12-20 9.38.27.png](https://qiita-image-store.s3.amazonaws.com/0/8839/b613cca9-b974-8cf6-9dc7-197f025079c6.png)

 - 正解：男性を男性、女性を女性と判定した数
 - 不正解：男性を女性、女性を男性と判定した数
 - 非検出：顔の認識が出来なかった数（正解 + 不正解 + 非検出 = 816）
 - 正解率：顔を認識した場合の正解の割合（正解 / (正解 + 不正解)）
 - 検出率：顔を認識した割合（(正解 + 不正解)/(正解+不正解+ 非検出)）

## 男性と女性の判定数
![スクリーンショット 2016-12-20 10.37.56.png](https://qiita-image-store.s3.amazonaws.com/0/8839/82ce67f5-30b6-77c4-dbb7-8013059feb08.png)

 - 男性：男性だと判定した数
 - 女性：女性だと判定した数

# 考察
- *FaceAPI*：**正解率が0.96**と最も高く、**検出率は0.86**と最も低い。顔検出が厳密に行われ、高い正解率になったのかも。
- *Visual Recognition*：**正解率が0.8**と低く、**検出率は0.99**と非常に高い。顔認識が不正確な場合でもなんらかの値を返しているみたい。
- *PUXの顔検出(評価版)*：**正解率が0.82**で、**検出率は0.87**だった。日本製APIのため有利かと思ってましたが、意外な結果でした。
- *その他のフリーの年齢・性別モデル*：**正解率が0.57**と最も低く、**検出率は1**で最も高い。顔認識が不正確だと男性として判定してそうです（実装によりますが）

正解率と検出率はトレードオフかもしれません。


# まとめ

 - 正解率で考えると、**FaceAPI**が最も性別判定に使える
 - 検出率で考えると、**Visual Recognition**がバランスが良い

今回は、性別判定のみで記事にしました。個人的には、APIのコール数が月30,000回まで無料で、性能も良いFaceAPIが使いやすいと思います。
それと、**は◯な愛さんの性別の真値に迷いました**。

（ちなみに、性別判定だけなら、[Amazon Rekognition](https://aws.amazon.com/jp/rekognition/)もできるので、そのうち比較してみたいと思います）

# 今後

次回は年齢判定について記事にします。日本製のモデルであるPUXの性能が気になるところです。

※追記：[その２](https://qiita.com/usi-isu/items/1725bf81c8dc8230a9a6)を書きました(2017/12/19) 。