顔面偏差値No.1アイドルをAIに判定してもらおう！！～Face++ API～【検証】

#企画趣旨
**「結局、顔面偏差値が一番高いアイドルは誰なのか？」**

アイドル界隈ではよく耳にする話題です。

アイドルのヲタクに聞いたら、自分の推しているアイドルの名前が返ってくると思います。
たしかにそれは正しいと思います。

でもやっぱり客観的な意見も気になります。

客観的な意見を得るためにアンケートという形もありですが、
せっかく**顔認識**が発展しているのだから**AI**から意見をもらいたいなと思いました。

今回はその備忘録です。

私の顔で試したところスコアは100点満点56点でしたが（←ちょっと不服）、
**果たしてNo.1アイドルとそのスコアは？？**

（結果は**目次番号15**にあります）

#全体の流れ
今回の企画は大きく3部構成になっております。

- [前編]画像ファイルのスクレイピング（目次番号1～6まで）
- [中編]Face++のAPIによる判定（目次番号7～11まで）
- [後編]DataFrameのビュー作成と可視化（目次番号12～16まで）

#前編の目次
- 1.前編のゴール
- 2.利用する技術
- 3.ホームページから画像をスクレイピング
- 4.取得した情報をDataFrameに格納
- 5.DataFramをCSV出力
- 6.サンプルコード



#1.前編のゴール
サイトから複数の画像ファイルをスクレイピングしDataFrameに格納します。

#2.利用する技術
- 顔認識（中編で使用します）
    - [Face++](https://www.faceplusplus.com/)
- 言語
    - Python 38
- 環境 
    - Windows
- 主なライブラリ
    - BeautifulSoup
    - Pandas
- BIツール（後編で使用します）
    - MotionBoard

#3.ホームページから画像をスクレイピング
今回はももいろクローバーZが所属することで有名な**「スターダスト」**という事務所のアイドルをターゲットとします。
所属するアイドルの数は65人です（調査時点）。

スクレイピング対象のホームページは下記です。
https://www.stardust.co.jp/stardustplanet/
![alt](https://pbs.twimg.com/media/EYSA4GZUcAAPgKe?format=jpg&name=large)

このホームページから下記2点を目指します
- 画像ファイルの取得
- 人物名の取得

##画像ファイルの取得
まずは画像がどのタグの中に存在するのか確認します。
一番左上の「高城れに」さんでGoogleの検証をしてみます。
![検証.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637238/b0527dd8-78ef-e10d-3049-29257f3bf05c.png)

これを見ると**`img src`**の部分と検討が付くと思います。

ただここで注意点があります。
この**`img`**だけだと関係ない画像まで取得されてしまいます。

例えばアイドルの横にあるグループのロゴ。
これも**`img`**タグで囲まれてます。
![検証２.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637238/609eb117-9d54-5068-27c8-931667c388d2.png)

アイドルさんとロゴの区別する点として、**`img`**の1つ外側のタグが違います。
・アイドル　⇒　**`div`**
・ロゴ　⇒　**`a`**

そのため今回は

**「`div`**の中の**`img`」**という指定をしました。

##人物名の取得
次に人物名を取得します。

と言ってもタグの指定は画像ファイルとまったく同じで今回のホームページは大丈夫でした。
これは画像ファイルと人物名が同じ`div`の中の`img`に存在したからです。

つまり
**「`div`**の中の**`img`」**という指定です。
![検証.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637238/fa100130-da08-f4f7-eaf1-929613ab9995.png)


#4.取得した情報をDataFrameに格納

以下のような3つのカラムを持つDataFrameを用意します。
そして取得した情報をフィールドにセットします。
for文を回してこのDataFrameに全員の情報を格納します。


| name       | image_url        |file_path    |
|:-----------------|------------------:|:------------------:|
| 高城れに             | http://img.stardust.co.jp/upload/talent/photo/2/238.L.jpg             | 保存先のファイルパス              |

#5.DataFramをCSV出力

2次利用しやすいようにテーブルをCSVファイルとして出力します。

#6.サンプルコード

今回の内容をコードに落とすと以下のようになります。

```storeimages.py
import requests
from bs4 import BeautifulSoup
import os
import pandas as pd

# 取得したファイルを格納するローカルディレクトリ
images_dir = 'YOUR_LOCAL_DIRECTLY'

# 取得したデータフレームを格納するアウトプットディレクトリ
outputfiles_dir = 'YOUR_OUTPUT_DIRECTLY'

# スクレイピング対象のURL
url = 'https://www.stardust.co.jp/stardustplanet/'


headers = {'User-Agent': 'Mozilla/5.0'}
soup = BeautifulSoup(requests.get(url, headers=headers).content, 'lxml')

# 取得した情報を格納する変数
images = []
names = []
files_path = []


# 画像URLの取得
# <div>タグの中の<img>のsrcが画像のURL
for image_url in soup.select('div > img'):
    # imagesのリストに取得した画像URLを格納
    images.append('http:' + image_url.get('src'))

# 人物名の取得
# <div>タグの中の<img>のaltが人物名
for name in soup.select('div > img'):
    # namesのリストに取得した人物名を格納
    names.append(name.get('alt'))


for target in images:
    # files_pathのリストにローカルディレクトリと画像ファイル名を連結したパスを格納
    files_path.append(
        images_dir + target.split('/')[-1])
    # 画像URLをローカルファイルとして保存する
    re = requests.get(target)
    with open(images_dir + target.split('/')[-1], 'wb') as f:
        f.write(re.content)

# リストの値をテーブルのカラムへの格納
data = {
    'name': names,
    'image_url': images,
    'file_path': files_path
}
# テーブルを定義
df = pd.DataFrame(data)

# テーブルをCSVファイルとして出力する
df.to_csv(
    outputfiles_dir + 'storeimages.csv')

print('終了')
```
以上が[前編]です。



#中編の目次
- 7. 中編のゴール
- 8. Face++とは？
- 9. APIを利用する
- 10. 判定結果を格納
- 11 サンプルコード

#7. 中編のゴール
画像ファイルをFace++を利用して顔面偏差値を判定し、結果をDataFrameへ格納します

#8. Face++とは？
ホームページはこちらです。
https://www.faceplusplus.com/

私は最近知りました。
出会いは、「顔認識使ってみたいなー」「けど、有料サービスはちょっとハードル高いなー」
という思いから、**「顔認識　無料」**みたいなググり方。

こちらの[サイト](https://note.com/hidekiikeda/n/n5ff23d7706de)がFace++について詳しく勉強になりました。
ありがとうございます。

ひと言でまとめると、
**「中国国家でも利用される顔認識システムの会社のサービス」**
です。

そして今回の企画のカギ
**「顔面偏差値」**として解釈させていただいている**「beauty」**というスコアを取得することができます。
Face++のDetect APIを利用します。

[公式ドキュメント](https://console.faceplusplus.com/documents/5679127)による「beauty」の説明は下記。

**「美しさを100点満点で測定した値」**だそうです。
> Result of beauty analysis, including the following fields. The value of each field is a floating-point number with 3 decimal places between [0,100]. Higher beauty score indicates the detected face is more beautiful.

#9. APIを利用する
ユーザーの登録が必要です。
登録の手順は[こちら](https://qiita.com/s-kajioka/items/51ebc8c16a846ce43b1f)が参考になりました。
ありがとうございます。

ユーザー登録することでAPI KEYを取得できます。

大まかな流れは下記です。

>**画像ファイルをbase64 形式でエンコード**
**↓**
**パラメーターをセット**
ここでAPI KEY やReturnしてほしい属性を設定。
今回は「性別、年齢、beauty」を設定。
**↓**
**リクエスト**
**↓**
**JSON形式でレスポンスが返ってくる**


#10. 判定結果を格納
パラメーターで設定した「性別、年齢、beauty」をリストに入れて、そのリストの値を下記のようなDataFrameに格納します。

beautyは male_scoreとfemale_scoreとして返ってきます。
そのためリストもカラムも2つずつ用意します。
            

|    input_file    |    gender     |    age    |    beauty_male    |    beauty_female    |
|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:|
|判定する画像ファイル |        性別        |        年齢        |        男性としての顔面偏差値        |        女性としての顔面偏差値        |

#11. サンプルコード
今回の内容をコードに落とすと以下のようになります。

```detectapi.py
import requests
import json
import glob
import base64
import pandas as pd


# face++のURL
url = 'https://api-us.faceplusplus.com/facepp/v3/detect'


# 取得したイメージファイルを格納しているローカルディレクトリ
images = 'YOUR_IMAGE_FILES_DIRECTLY'

# 取得したデータフレームを格納するアウトプットディレクトリ
outputfiles_dir = 'YOUR_OUTPUT_DIRECTLY'

# 取得したJSONデータを格納リスト
gender = []
age = []
beauty_male = []
beauty_female = []

# ファイルパスを変数に格納
file_path = glob.glob(
    images)


list_df = pd.DataFrame(
    columns=['file_path', 'gender', 'age', 'beauty_male', 'beauty_female'])


# ファイル分繰り返して
for input_file in file_path:
    # バイナリファイルを開く。withはclose自動
    with open(input_file, 'rb') as f:
        img_file = base64.encodebytes(f.read())

    # パラメーターをセット
    param = {
        'api_key': 'YOUR_API_KEY',
        'api_secret': 'YOUR_API_SECRET',
        'image_base64': img_file,
        'return_landmark': 1,
        'return_attributes': 'gender,age,beauty'
    }

    # POSTする
    res = requests.post(url, data=param)

    # JSONファイルを読み込む
    data = json.loads(res.text)

    # JSON文字列として整形
    data_json = json.dumps(data, indent=4)

    # カラムにJSONデータを格納する
    for face in data['faces']:
        if 'attributes' in face:
            gender = face['attributes']['gender']['value']
            age = face['attributes']['age']['value']
            beauty_male = face['attributes']['beauty']['male_score']
            beauty_female = face['attributes']['beauty']['female_score']
        #顔認識できない場合はNoneをセット
        else:
            gender = None
            age = None
            beauty_male = None
            beauty_female = None
        # テーブルにレコードを追加
        temp_se = pd.Series(
            [input_file, gender, age, beauty_male, beauty_female], index=list_df.columns)
        list_df = list_df.append(temp_se, ignore_index=True)
# CSVファイルへ出力
list_df.to_csv(
    outputfiles_dir+'detectapi.csv')
print('finish')
```

以上が[中編]です。


さあラスト[後編]です。

#後編の目次
- 12. 後編のゴール
- 13. キー項目の作成
- 14. 左外部結合
- 15. BIツールで可視化＆結果発表
- 16. サンプルコード

#12. 後編のゴール
2つのDataFrameを結合し、BIツールで可視化します。

#13. キー項目の作成
前編と中編でそれぞれ作成したテーブルを見てみましょう。


- 前編で作ったDataFrame：storeimages.csv

| name       | image_url        |file_path    |
|:-----------------|------------------:|:------------------:|
| 高城れに             | http://img.stardust.co.jp/upload/talent/photo/2/238.L.jpg             | 保存先のファイルパス              |

- 中編で作ったDataFrame：detectapi.csv

|   input_file    |    gender     |    age    |    beauty_male    |    beauty_female    |
|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:|
|判定する画像ファイル |        性別        |        年齢        |        男性としての顔面偏差値        |        女性としての顔面偏差値        |

これら2つのDataFrameを1つにします。

2つを結合するためには共通するキー項目を作る必要があります。

今回は、**「画像ファイル名」**をキー項目とします。

それぞれのテーブルからは下記のようにキー項目を作成します。
storeimagesは「image_url」から文字列をsplitします。
detectapiは「input_file」から文字列をsplitします。

#14. 左外部結合
こうして「key」というカラムができたら2つのテーブルを結合します。

storeimagesを親としてdetectapiを結合します。

今回はどっちが親でも関係ないです。
storeimageのレコードは要するに画像の一覧です。
このテーブルのレコードは結合落ちしてほしくないので親にしました。

そして結合したあと、
顔面偏差値の降順にソートしました。（これは別に必要ないです。趣味です。

最後に必要なカラムだけに絞った結果が下記です。


|   name    |    beauty_female     |    age    |    
|:------------------:|:------------------:|:------------------:|
|人物名 |        顔面偏差値        |        推定年齢        |        

#15. BIツールで可視化＆結果発表
最後は結合して作ったDataFrameを可視化します。

ランキング表形式での可視化が分かりやすいなと思いました。

表形式での可視化はPythonだとちょっと苦手かなという印象が個人的にあります。

そこで**MotionBoard**というBIツールを使って可視化してみました。


![1_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637238/cc72ac27-59cc-f1a7-39ae-1adce55e7f4d.png)


1位大黒柚姫　さん
![418.L.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637238/acf75f76-a174-86a1-dfed-12746a2d655b.jpeg)

2位小泉遥香　さん
![2013.L.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637238/814f0c18-68c9-30f3-fd5d-366e933be931.jpeg)

3位華山志歩　さん
![614.L.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637238/d7927fee-bc09-c28d-944a-6862336c9797.jpeg)

（以上写真はhttps://www.stardust.co.jp/stardustplanet/）

冒頭の通りですが、
ちなみに試しに自分の写真を判定してもらったところ
スコアは56点でした。。

かなしい。

**アイドルさんは当たり前ですが顔面偏差値高いです！**

以上で今回の企画は終了です。

#16. サンプルコード

今回の内容をコードに落とすと以下のようになります。

```makeview.py
import numpy as np
import pandas as pd


# storeimages.csv
storeimages_csv = 'YOUR_PATH_storeimages.csv'

# detectapi.csv
detectapi_csv = 'YOUR_PATH_detectapi.csv'

# 取得したデータフレームを格納するアウトプットディレクトリ
outputfiles_dir = 'YOUR_OUTPUT_DIRECTLY'


# テーブルとして読み込む
df_images = pd.read_csv(
    storeimages_csv)
# テーブルとして読み込む
df_detectapi = pd.read_csv(
    detectapi_csv)

# 2つのテーブルを結合するキー項目を作成。
# イメージファイル名をキー項目とする
df_images['key'] = df_images['image_url'].str.rsplit('/', 1).str[1]

df_detectapi['key'] = df_detectapi['file_path'].str.rsplit('\\', 1).str[1]

# キー項目で結合。
df_view = df_images.merge(df_detectapi, how='left', on='key')

# beautyを降順ソート
df_view = df_view.sort_values(
    'beauty_female', ascending=False, na_position='last')

df_view = df_view.loc[:, ['name', 'beauty_female', 'age']]

# CSV出力
df_view.to_csv(
    outputfiles_dir+'view.csv')
#view.csvをBIツールで可視化する
```