StramlitでFaceAPIを用いた顔検出アプリの作成＆Herokuでのデプロイ

#はじめに
中の人はエンジニアのeの字も知らない初学者です。文章やコードに不備があるかもしれませんのであたたかい目でみていただければと思います。。

また，理解が不足している部分があればぜひコメントで教えて頂きたいです。

#作成の経緯と参考サイト
AIについて勉強していた折に，卒業アルバム作りにAIを活用したという記事を見まして。
DXとしてもすごくアツい話題だと思ったので，最終目標を「擬似卒アル作成アプリ」として勉強を始めました。

FaceAPIというものを使うと顔認識が簡単にできるとのことなので，まずは初めの段階としてFaceAPIを使って顔を認識するアプリを作成してみました。

（FaceAPIにたどり着くまでにも紆余曲折を経たのですが，それについては別の記事を書ければと考えています。）

以下のサイトに大変お世話になりました。
[【Microsoft Azure Face】Pythonで画像の中の顔を認識してみる(SDK)](https://buralog.jp/microsoft-azure-face-detection2-by-python/)


#1.Azure FaceAPIのダウンロード

Microsoft Azureの公式サイト
[クイック スタート:Face クライアント ライブラリを使用する](https://docs.microsoft.com/ja-jp/azure/cognitive-services/face/quickstarts/client-libraries?tabs=visual-studio&pivots=programming-language-python)
に従い，①Azureのアカウントを作成②Faceリソースの作成③キーとエンドポイントの取得
![key&endpoint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478147/37c9459e-8dc1-b9c5-2769-d100017d1366.png)

#2.実行するコードを記述

```py:main.py
import streamlit as st
from azure.cognitiveservices.vision.face import FaceClient
from msrest.authentication import CognitiveServicesCredentials
from PIL import Image, ImageDraw, ImageFont
import os

# タイトルを設定
st.title("顔検出アプリ")

# ローカル環境変数よりキーとエンドポイントを取得
KEY = os.environ.get('KEY')
ENDPOINT = "https://エンドポイントのURL/"

# サブスクリプションキー情報を使用してインスタンス化
face_client = FaceClient(ENDPOINT, CognitiveServicesCredentials(str(KEY)))

# Streamlitでアップローダーを作成
uploded_file = st.file_uploader("jpg画像をアップロードしてください。顔を検出します。", type="jpg")

if uploded_file is not None:
    # 画像をtmb.jpgとして一時保存
    img = Image.open(uploded_file)
    img.save("tmb.jpg")
    image_data = open("tmb.jpg", 'rb') # 画像をバイナリーデータに変換
    
    # 顔の検出
    detected_faces = face_client.face.detect_with_stream(
                        image_data,
                        return_face_landmarks=True,
                        return_face_attributes=['accessories','age','emotion','gender','glasses','hair','makeup','smile'])
    if not detected_faces:
        raise Exception('画像から顔を検出できませんでした。')
    
    # 認識された顔周辺に四角を描く関数
    def getRectangle(faceDictionary):
        rect = faceDictionary.face_rectangle
        left = rect.left
        top = rect.top
        right = left + rect.width
        bottom = top + rect.height
        
        return ((left, top), (right, bottom))
    
    # 認識された顔の上に年齢を描く関数
    def getAge(faceDictionary):
        rect = faceDictionary.face_rectangle
        left = rect.left
        top = rect.top - 30
        
        return (left, top)
    
    # イメージオブジェクト生成
    image_data = Image.open("tmb.jpg")
    drawing = ImageDraw.Draw(image_data)
    
    # 関数を呼び出して、顔に四角を描く
    for face in detected_faces:
        drawing.rectangle(getRectangle(face), outline='Red', width = 3)
        drawing.text(getAge(face), str(face.face_attributes.age), font = ImageFont.truetype("arial.ttf", size=30), align = 'Left', fill = 'Red')
    
    st.image(image_data, caption='Uploaded Image', use_column_width=True) 
```
#3.Herokuでのデプロイ
###①必要なファイルを作成
必要なファイルは以下の6種類です。
・main.py
・requirements.txt

```requirements.txt
streamlit
Pillow
msrest
azure-cognitiveservices-vision-face
```

・setup.sh

```setup.sh
mkdir -p ~/.streamlit/

echo "\
[general]\n\
email = \"メールアドレス\"\n\
" > ~/.streamlit/credentials.toml

echo "\
[server]\n\
headless = true\n\
enableCORS=false\n\
port = $PORT\n\
" > ~/.streamlit/config.toml
```

・Procfile

```Procfile
web: sh setup.sh && streamlit run main.py
```

・フォントファイルのコピーたち（下記で説明しています。）

###②Gitでデプロイ
作業ディレクトリに移動します。

```
$ cd "作業ディレクトリへのパス"
```
Herokuにログイン

```
$ heroku login
```
create new appから新しくアプリを立ち上げます。
![CreateNewApp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478147/6dc68605-0693-ab7c-2e92-aad9620b8c83.png)

Settingsからアプリ名をコピーします。
![CopyName.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478147/bd3f9d18-e07c-1625-cf36-38be2ffdc694.png)

```
$ heroku git:remote -a "アプリ名"
```
サブスクリプションキーを環境変数に設定します。

```
$ heroku config:set KEY=xxx
```
Gitの初期化とcommitをします。

```
$ git init
$ git add. 
$ git commit -m "first commit"
```
インスタンスを作成します。

```
$ heroku create
```
pushして開きます。

```
$ git push heroku master
$ heroku open
```
アプリが正常に作動すれば完了です。

# 躓いた点と解決法

###①サブスクリプションキーを公開しない工夫→環境変数の設定
セキュリティ対策のため，初めは`seacret.json`を通してキーを得ようとしたのですが，うまくいきませんでした。

Herokuにデプロイする際に，ターミナルで以下を実行することで，ローカルに環境変数を設定することができました。

```
heroku config:set KEY="xxx"
```
恐らくWeb開発をしている方にとっては常識なんだろうなー…でもこの段階でも躓きました。

###②画像をバイナリーデータに変換できない→一度名前を付けて保存
FaceAPIに渡す画像データはバイナリデータでないといけないということで，ここもかなり苦労しました。当初は以下のコードで`BytesIO`を用いてバイナリデータを返そうとしました。

```py
    img = Image.open(uploded_file)
    with io.BytesIO() as output:
        img.save(output, 'jpeg')
        image_data = output.getvalue()
```
が，以下のエラー

```
AttributeError: 'bytes' object has no attribute 'read'
```
どうやら`bytes`オブジェクトは`read`に対応していませんよーといったことのようです。アップロードされた画像を，一度名前を付けて`open`を使用して保存することによって解決できました。

```py
    # 画像をtmb.jpgとして一時保存
    img = Image.open(uploded_file)
    img.save("tmb.jpg")
    image_data = open("tmb.jpg", 'rb') # 画像をバイナリーデータに変換
```

###③drawing.textのフォント指定がうまくいかない→Fontファイルもアップロード
Streamlitはターミナルで`streamlit run "ファイル名".py`と実行することによってアプリのデモを見ることができます。デモ上でアプリが問題なく作動したのを確認して，いざデプロイ！完了して，動作確認のため画像をアップロードしたところ，エラーが。。。

エラーメッセージは特になかったと記憶しています。しかし，以下のコードで対応する`font`が見つからないとのだろうなということは何となくわかりました。

```py
    # 関数を呼び出して、顔に四角を描く
    for face in detected_faces:
        drawing.rectangle(getRectangle(face), outline='Red', width = 3)
        drawing.text(getAge(face), str(face.face_attributes.age), font = ImageFont.truetype("arial.ttf", size=30), align = 'Left', fill = 'Red')
```

解決策として，Fontsファイルにあった該当の文字フォントファイルを，作業ディレクトリに丸ごとコピーしました。フォントファイルも一緒にデプロイすることで，なんとか解決できました。どこかで調べたわけではないので，正攻法ではないと思いますが…
![FontsFile.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478147/93deb2f8-9acd-e828-6bd7-f30456f04b73.png)

###④Streamlitへのデプロイで公開されない→Herokuでデプロイ
Streamlitにてアプリをデプロイ後，念のため…と思って他のアカウントから開いてみたところ
`You do not have access to this app or it does not exist`とエラー。
![StremlitError.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2478147/b3b5a9f3-e37b-57a1-8f37-05a27f19324b.png)
恐らくGithubのレポジトリを非公開にしていることが原因かと思います。Githubのレポジトリを非公開にしているのは，`.gitignore`ファイルをうまく扱えず`seacret.json`が丸出しになってしまったためです。

以下のサイトを参考にしてHerokuでアプリをデプロイすることで解決しました。
[【簡単爆速第2弾】Streamlitをherokuにデプロイ](https://qiita.com/Nate0928/items/632e7d54c2f9c17e242b#procfile)



#まとめ
FaceAPIを用いてWebアプリを作ることができました。

正直ここに辿り着くまでに多くのエラーと戦い，かなり苦労しました。無事にアプリが作動した時にはそれはそれは嬉しかったです。叫びました笑

そして今回はStreamlitの力を借りてHTMLやCSSを使わずの実装だったので，まだまだ勉強の余地はありますね。

おわり