AreYouHara - お前はハラなの？顔画像から判定してやるよぉ！！！【プレゼント企画あり】

どうもこんにちは、ハラです。

世界には２種類の人が存在します。そう、**ハラかハラ以外か**。
そこでハラかどうか判定するWebアプリを作りました。
![hara.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/7f42ee9c-7519-50bc-2fd8-8b4a7964171d.png)

:::note info
最後にはプレゼント企画もあるのでぜひ最後まで見ていってください。
:::

# どんなアプリか
アプリはこちらから遊べます。
※アップロードした画像はサーバーに保存していないので安心して遊んでください！

https://areyouhara.streamlit.app/

アプリはいたってシンプルです。顔の画像をアップロードするとハラかハラじゃないのか判定してくれます。

まずは笑顔のハラ画像を入力してみましょう。んー、いい笑顔。
![スクリーンショット 2022-12-19 0.41.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/f670c802-100b-a1f4-6167-5b0edd64b36c.png)
これはハラの画像なので結果はもちろん「ハラ」判定。
顔がどれくらい似ているかのパーセンテージを同時に出してくれます。
もちろんハラなので100%、当たり前ですね。

また、保存されている12枚のハラ画像の中から最も似ている画像を右側に表示してくれます。
笑顔のハラ画像は、凛々しいハラ画像と似ていたようですね。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/78d6b715-f191-3075-c93c-5ba43d421c58.png">

次に、フリー画像で拾ってきたグッドの人を入力してみましょう。
![スクリーンショット 2022-12-19 0.47.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/80facdbb-51f7-b0a0-7967-786d0c96ef3a.png)
結果はもちろん「not ハラ」。
類似度も24%とかなり低いです。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/1401f198-7c08-15de-783e-fd0200a26fc8.png">

# ハラかどうかの判定方法
次のような手順でハラかどうかを判別します。
1. Facenetが入力した画像を特徴ベクトルへと変換する
1. 特徴ベクトルのコサイン距離を計算する
1. コサイン距離が一定以上近ければハラだと判定する

このとき、コサイン距離はコサイン類似度を１から引いたもので、0なら「最も似ている」、2なら「最も似ていない」とするものです。

下の画像中では正解画像が１枚のみになっていますが、実際は正解ハラ画像は12枚あります。すべての正解ハラ画像とコサイン距離を計算し、一番似ているハラ画像とその類似度を出力します。
![類似度.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/9a55db24-6158-764e-f006-3d56398095e3.png)



ここでいう特徴ベクトルは、512次元の顔の特徴を表したベクトルです。
イメージとしては下のの画像のように鼻のサイズや肌のテカリ具合など、512個の特徴を数値化したようなものです。

※ 画像はあくまでイメージで、実際は各特徴が何を意味しているのかは人間にはわかりません
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/661359f9-2f08-18c8-3473-c239dfc9002d.png">

Facenetについてもっと知りたいよって方は、こちらが大変参考になります。

https://qiita.com/koshian2/items/554b3cbef6aeb1aab10d

# AreYouHaraの精度
AreYouHaraは顔認証のためにGoogleが開発したFacenetを用いているため、本人であるかの識別能力は驚くほど高いです。
横を向いていたり、サングラスをかけていたりしても関係なく、ハラであれば類似度は100%となります。
ここまで精度が高いと逆に怖いですね。
![スクリーンショット 2022-12-21 14.52.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/ab9fd9c7-2275-3218-1e2d-ef4f4ff79515.png)

ただ、極端に顔が隠れている場合や顔が変形しているときには100%とならないときもあります。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/413778e4-6781-7729-25e7-8d9b4cca0723.png">

変顔をしてと類似度が高くならないようにする実験も行ってみましたが、どんなにやばい顔をしても80%を切ることはできませんでした。
極限まで顔が変形した変顔画像を載せるのは恥ずかしいので今回はやめておきます。

# コードの解説
ソースコードはこちらに公開しています。気軽に覗いてみてください。

https://github.com/yamato0811/AreYouHara

ハラの画像は簡単に見られないようにベクトル化したものしか載せていません。
実際にアプリを使ってみて、12個の表情の異なるハラを堪能してください。

### 使用した技術
- [Streamlit](https://streamlit.io/): PythonだけでサクッとWebUI付きのアプリケーションを作れちゃうフレームワーク。フロントエンドのことは考えなくてもなんとなくそれっぽいアプリが完成します。
- [keras-facenet](https://github.com/faustomorales/keras-facenet): 顔認証のための顔認識モデルFacenetを気軽に試せるライブラリです。
- [pillow](https://python-pillow.org/): 画像処理の有名なライブラリです。

できるだけ早く、簡単に作ることを大切にしたのでstreamlitを使用しました。Streamlit cloudにそのままデプロイできるため、高速でアプリが完成しました。フロント部分は考えなくていい反面、フロントをいじるのはめんどくさいのでとりあえず動かしたい！っていうアプリにはいいですね。

### 環境について
VScodeのDev Containerを使用して環境を構築しています。

https://code.visualstudio.com/docs/devcontainers/containers

### モデル、データの読み込み
facenetのモデルと、ハラ画像を読み込むためのdataloaderを読み込みます。

slteamlitでは`st.title('hogehoge')`や`st.write('hogehoge')`のように記述することでテキストの表示をすることができます。
便利な世の中ですね。

```python:streamlit.py
from distutils.command.upload import upload
import streamlit as st
from PIL import Image, ImageOps
import numpy as np
from keras_facenet import FaceNet

from utils import distance_to_similarity, crop, resize, adjust_img_margin
from dataloader import Dataloader

@st.cache(allow_output_mutation=True)
def load_facenet():
    return FaceNet()
@st.cache
def load_dataloader():
    return Dataloader()

# CSSの読み込み
with open('style.css') as f:
    st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)

st.title('Similarity with HARA')
st.write("早速あなたの顔の画像をアップロードしてみましょう")
uploaded_file = st.file_uploader('Choose a image file', type=['jpg','jpeg','png'])

facenet = load_facenet()
dataloader = load_dataloader()
```

### アップロードされた画像から顔を抽出
アップロードされた画像に適切な前処理を施したあと、画像の中から顔の領域だけを切り抜いてベクトル化します。
`facenet.extract()`を使うことで顔領域の抽出とベクトル化が一気にできちゃいます。
これまた便利です。

```python:streamlit.py
if uploaded_file is not None:
    image = Image.open(uploaded_file).convert('RGB')
    image = ImageOps.exif_transpose(image)  # 画像を適切な向きに補正する
    image = resize(image)

    img_array = np.array(image)
    extracts = facenet.extract(img_array) # 顔領域を抽出
```

### ハラ画像か判定
アップロードされた画像に顔領域がない場合は「Face detaction faild」を表示します。

アップロードされた画像に顔が複数存在していた場合は画像のメインであろう最大の顔を取得します。
その後、保存されている12枚すべてのハラの顔特徴ベクトルとのコサイン距離を算出し、距離が最も近い(似ている)ハラの顔を選択します。

最後に、コサイン距離 (2~0) を類似度 (0~100) へと変換します。
類似度が100%のときだけハラ認定をして、あとはハラではないと表示します。
```python:streamlit.py
    if len(extracts) < 1:
    # 顔が画像中に存在しないとき
        st.header('Face detection faild')
        st.image(
            img_array, 
            use_column_width=True
        )
    else:
        max_extract = max(extracts, key=lambda x:x['box'][2]*x['box'][3]) # もっとも大きい顔を取得(複数の顔が存在する場合がある)
        embed_img = max_extract['embedding'] # 512次元の特徴を取得

        distances = [(facenet.compute_distance(e, embed_img), p) for e, p in dataloader] # hara dataset内のすべての画像と距離を計算
        distance, path = min(distances)
        similarity = distance_to_similarity(distance) # 距離を類似度に変換
        
        # 判定結果
        if similarity == 100:
            st.text('You are HARA🎉🎉🎉')
        else:
            st.text('You are not HARA😞😞😞')
        st.text(f'Similarity : {round(similarity)} %')

        img_array = crop(img_array, max_extract['box'])
        uploaded_img, picked_img = adjust_img_margin(img_array, dataloader.load_img(path)) # 表示の際に適切なマージンがつくよう調整

        col1, col2 = st.columns(2)
        with col1:
            st.write("Upload Image")
            st.image(
                uploaded_img, 
                use_column_width=True
            )
        with col2:
            st.write("Most Similar Hara")
            st.image(
                picked_img, 
                use_column_width=True
            )
```

### コサイン距離を類似度に変換するロジック
ハラとどれくらい似ているのかを0~100%で表示できたほうが分かりやすいので、コサイン距離を百分率の類似度へと変換しました。

経験上コサイン距離が1を超えることは多くなかったので、コサイン距離が1を超えた場合は類似度0%としました。
また、ハラ本人の画像を入力しない限りコサイン距離が0.2を超えることはほぼなかったので、0.2を類似度100%のしきい値としました。

変換はグラフにするとこんな感じです。
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/08dca950-b071-b258-89a4-794fdf265305.png">

実際のコードはこちらです。単純ですね。

```python:utils.py
# 距離:2~0, 類似度:0~100
def distance_to_similarity(dis, min_dis=0.2, max_dis=1.0):
    if dis < min_dis:
        similarity = 100
    elif dis > max_dis:
        similarity = 0
    else:
        similarity = (100/(min_dis-max_dis))*dis + 100*max_dis/(max_dis-min_dis)
    return similarity
```

# おわりに
ハラかハラ以外かを判定するアプリを作成してみました。
ぜひ友人や家族と誰が一番ハラに似ているかを競い合ってみてください。きっと盛り上がりますよ。


今日はクリスマスイブということで、なんとハラサンタも駆けつけてくれました。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/a06ef542-641a-1dea-cf48-3b8d7d26d848.png">
もちろん類似度は100%です。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840684/fbcfea3e-eb22-a507-199c-dbbd8a3bdf28.png">


# ~~プレゼントキャンペーン🎁~~
終了しました。

メリークリスマス🎄🎅
今日はクリスマスイブということで、ハラサンタがプレゼント企画を準備しました。

▼アプリはこちら▼

https://areyouhara.streamlit.app/

### プレゼント内容
AreYouHaraに顔の画像をアップロードしてみて、以下の条件をクリアした方に先着でAmazonギフト券をプレゼントします。
顔画像は本人でなくてもOKです！ネットから拾ってきた画像なども対象とします。

**ハラにそっくりで賞**
- ~~類似度100%: Amazonギフト券3000円分【1名】~~
- ~~類似度95%~99%: Amazonギフト券2000円分【1名】~~
- ~~類似度90%~94%: Amazonギフト券1000円分【1名】~~

**ピッタリで賞**
- ~~類似度24%: Amazonギフト券300円分【1名】~~
- ~~類似度48%: Amazonギフト券300円分【1名】~~
- ~~類似度72%: Amazonギフト券300円分【1名】~~

### 参加方法
上記の類似度を達成した場合、類似度と画像部分のスクリーンショットを撮ってTwitterのDMで送ってください。
Twitterはこちら→ https://twitter.com/yamato_hara

### 注意事項
- ハラ本人の画像を使用、またはハラを加工した画像は無効です。
- 顔画像はどんなものでもOKです。(ネットから拾ってきた画像など)
- 複数の類似度を達成しても獲得できるのは1つのみです。
- プレゼントは先着のため、すでに達成された方がいる場合があります
(達成した方がいたら取り消し線を引けるよう努力はします)

サーバーは無料枠を使用していますので、ダウンしている可能性があります。
学生の財力でやっているのでそこは多めに見てください笑