FlaskとKerasを使って顔が似てる芸能人を探すアプリを作ってみよう 1

<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_CHTML"></script>
<script type="text/x-mathjax-config">



# 何をするのか
* 芸能人の顔画像複数枚の中から自分の顔画像と最も似ている物を検出して表示する簡易なwebアプリを作製.
* ディープラーニングモデルの構築にはkeras、APIの作製にはFlaskを使用します.

#使用するモデル
* VGG16というモデルを使用します。

#作りたいもの
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647807/f1f839d8-a57b-1ace-1dfd-654325a05674.png)

* 芸能人の顔写真複数枚の中から自分の顔と一番似ている顔の写真をAIによって判断し表示させるアプリ
* 選ばれた芸能人の顔と自分の顔の類似度(どれだけ似ているかの指標)も画像と一緒に表示



##ディレクトリ構造
root/
　├ keras_vggface(顔認識用のAIモデルが置かれたディレクトリ)/
　├face_app_demo/
　│  └ static/css/
　│  └ templates/
　│  └ app.py
　│  └ image_enter(芸能人の顔写真を保存するディレクトリ)
　│  └ image_user (ユーザーの顔写真を保存する保存する)


#何を使うのか
* APIの作成にはPythonのwebアプリケーションフレームワークである**Flask**を使用
* AIモデルの使用にはPythonのディープラーニングフレームワークである**Keras**を使用
* 環境構築にはDocker環境で動作

#実際に作っていこう
## 0.画像を受け取ってブラウザ上で表示するでの流れ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647807/47417b65-a914-9f76-360e-ef73cf22a414.png)

上の画像に示されるように、ブラウザから送った芸能人の写真をブラウザ上で表示するまでのフローは以下の通り大まかに6ステップに分けれられます。

**①html側で芸能人の画像を受け取りサーバーに送る**
**②サーバー側で受け取った画像をディレクトリに保存する**
**③ディレクトリに保存された画像のパスを取得する**
**④③で取得した画像のパスをブラウザ側に送信する**
**⑤ブラウザ側で表示したい画像をサーバ側にリクエストする。**
**⑥⑤で要求されている画像をディレクトリから取得してブラウザ側に送信する。**

以下、順を追って①〜⑥の処理を実装していきましょう。

## 1.ブラウザから複数の画像を受け取る
* まずはブラウザから複数の芸能人の画像を受け取る機能を実装しましょう。
* 今回、ブラウザから受け取った画像はローカルディレクトリimagesに保持する形になります。
* flaskを用いて以下のように画像を受け取るURLを定義します。
* まずはブラウザ側で画像を受け取る処理を書いて行きましょう。

###①html側で芸能人の画像を受け取りサーバーに送る

<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647807/eabeed8c-0936-e0d6-f2d0-5bd9fafd0e51.png">


```html:layout.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="../static/css/style.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
    <title>API Sample</title>
  </head>
  <body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
      <a class="navbar-brand text-dark" href="#">似てる芸能人検索アプリ</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
          <div class="navbar-nav ">
          </div>
      </div>
  </nav>
    {% block content %}{% endblock %}
  </body>
</html> 
```



* 具体的に画像を受け取ったする処理は`{% block content %}{% endblock %}`の部分に書いて行きます。
* index.htmlというファイルを用意してblockの中身を書いて行きましょう。

```html:index.html
{% extends "layout.html" %}
{% block content %}
<div class="enter_btn">
    <h2>芸能人の写真をアップロードしてください</h2>
    <form action = "/upload" method="post" enctype="multipart/form-data">
        <p><input type="file" id = "upload_files" name = "upload_files" multiple="multiple" class="form-control">
            <input type="submit" value="送信" class="form-control btn btn-primary">
    </form>
</div>

{% endblock %}

```
* 画像データは`<form>`を用いて受け取ります。`action`で行う処理を指定します。
* 受け取った画像は`upload_files`というリストとしてflask側に渡されることになります。

###②サーバー側で受け取った画像をディレクトリに保存する
* サーバー側の処理は以下のように書きます。いくつかの部分に分けて書きますが実際は全て同じファイルapp.pyに書いています。

```python:app.py
#必要なモジュールをインポート
import os
import sys 
from flask import (
     Flask, 
     request, 
     redirect, 
     url_for, 
     make_response, 
     jsonify, 
     render_template, 
     send_from_directory)
```
* 次に画像を保存するディレクトリを指定し、Flaskでアプリを作る際に必要な定型文を書いて行きます。
* Flaskでアプリケーションを作成する際には必ず`app = Flask(__name__)`を冒頭に書くようにしましょう。

```python:app.py
# 画像のアップロード先のディレクトリ
UPLOAD_FOLDER_ENTER = './image_enter' #芸能人の写真用
UPLOAD_FOLDER_USER_FACE = './image_user' #ユーザーの写真用

#FlaskでAPIを書くときのおまじない
app = Flask(__name__)
```
* ここからはルーティングを書いて行きます。
* ルーティングについては詳しく解説しませんが、不安な方は以下のリンクなどで復習しましょう。
    * https://tsuyopon.xyz/2019/02/22/what-is-the-routing/
    * https://www.subarunari.com/entry/2017/09/28/001946
    * https://qiita.com/zaburo/items/5091041a5afb2a7dffc8
    * https://aiacademy.jp/media/?p=57


* `/upload`というURLに画像をアップロードする処理を書いて行きましょう。

```python:app.py
@app.route('/upload', methods=['GET', 'POST'])
def uploads_file():
    # リクエストがポストかどうかの判別
    if request.method == 'POST':
        # ファイルがなかった場合の処理
        if 'upload_files' not in request.files:
            print("ファイルなし")
            return redirect(request.url)

        if request.files.getlist('upload_files')[0].filename:
            #画像オブジェクトを受け取る。
            uploads_files = request.files.getlist('upload_files')
            for uploads_file in uploads_files:
                #それぞれの画像に対してimage_enterまでのパスを定義作成してsaveメソッドを用いて保存する。
                img_path = os.path.join(UPLOAD_FOLDER_ENTER, uploads_file.filename)
                uploads_file.save(img_path)
        return redirect('/')

#スクリプトからAPIを叩けるようにします。
if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000)
```
* `uploads_file`ではブラウザ側から画像がPOSTされ、受け取った画像からディレクトリまでのパスを作成してそこに保存する処理を行います。
* 先ほどindex.htmlでブラウザで受け取った複数の画像ファイルを`uploaed_files`という名前で送信していましたが、`getlist`メソッドで無事それらが受け取られていることが確認できると思います。

##2.保存した画像をブラウザで表示する
###③ディレクトリに保存された画像のパスを取得する

```python:app.py
#ここから先は処理ごとにルーティングを行います。

#アプリのホーム画面のhtmlを返すURL
@app.route('/')
def index():
    return render_template(
        'index.html',
         enter_images=os.listdir(UPLOAD_FOLDER_ENTER)[::-1],
          user_images=os.listdir(UPLOAD_FOLDER_USER_FACE)[::-1]
          )

``` 
* flaskではルーティング処理は基本的に`@app.route('URL')`としてその後に関数で処理を書いて行きます。
* flaskではhtmlファイルをレンダリング(htmlファイルを出力)したい場合は`render_template`関数を用います。
* `render_template`では表示したいhtmlファイルの名前以外にも、サーバー側からフロント側に送りたい変数を引数にとることができます。
* 上の例では`enter_images`という変数に`'./image_enter'`内の全ての画像へのパスをリストにまとめて渡しています。
    * `[::-1]`はpythonのリスト表記で、リスト内の要素を後ろから逆順で並べ替えます。 
    *  (参考)pythonのリストTips
        *  https://qiita.com/ell/items/8057198ef33e554e54a2
        *  https://dot-blog.jp/news/python-basic-list-edit/



* app.pyを実行して http://0.0.0.0:5000/ にアクセスすれば以下のような画面になります。

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647807/bfbeac74-271d-d748-9943-234684bd81b6.png">

* 試しに画像を選択してみてください。選択した画像が`image_enter`に保存されていれば成功です。
* `uploads_file`では返り値として`redirect`メソッドを用いてホームのURLに戻るように設定しています。
これにより、画像を送信した後にホーム画面で引き続き処理を続けることができます。

## 2.受け取った画像を表示する
* 受け取った芸能人の画像は`image_enter`に保存されます。
* サーバー側から、`image_enter`に保存されている画像をブラウザに送りつける処理は以下のように`send_from_directory`関数を用いて書きます。
    * `send_from_directory`は第1引数にファイルが配置されているフォルダへのパス、第2引数はダウンロードするファイルの名前を指定します。

```python:app.py
#ディレクトリに保存されている画像をブラウザに送る処理
@app.route('/images/<path:path>')
def send_image(path):
    return send_from_directory(UPLOAD_FOLDER_ENTER, path)
```

* フロント側ではindex.htmlを以下のように書き換えて送られてきた画像を表示させます。

```html:index.html

{% extends "layout.html" %}
{% block content %}
<div class="enter_btn">
    <h2>Step1</h2> 
    <h3>芸能人の写真をアップロードしてください</h3>
    <form action = "/upload" method="post" enctype="multipart/form-data">
        <!-- <label>
            <input type="file" id = "upload_files" name = "upload_files" multiple="multiple">
        </label> -->
        <div class="custom-file">
            <input type="file" id = "upload_files" name = "upload_files" multiple="multiple" class="custom-file-input" required>
            <label class="custom-file-label" for="validatedCustomFile">Choose entertainers face images...</label>
            <div class="invalid-feedback">Example invalid custom file feedback</div>
        </div>
            <input type="submit" class="form-control btn btn-primary">
    </form>

    {% if enter_images %}
    {% for path in enter_images %}
        <div>
        <img src="images/{{ path }}" style="margin-top: 10px; vertical-align: bottom; width: 200px;">
        {{ path }}
        </div>
    {% endfor %}
    {% endif %}

</div>
{% endblock %}
``` 
* サーバー側のindexで変数として渡した`enter_images`がhtml内で用いられているのが確認できるかと思います。
* `<img src="images/{{ path }}" ...`としてサーバー側で定義した`send_image `から画像を送らせて表示させることができます。
* 今回新しく追加した部分について詳細に見ていきましょう。

```html
    {% if enter_images %}
    {% for path in enter_images %}
        <div>
        <img src="images/{{ path }}" style="margin-top: 10px; vertical-align: bottom; width: 200px;">
        {{ path }}
        </div>
    {% endfor %}
    {% endif %}
```

* `enter_images`にはimages_enterに保存されている芸能人の画像へのパスが入れられています。
* それらのパスを一つずつ`path`という変数に持たせて`<img>`タグを用いて表示させているわけですね。

app.pyを実行してhttp://0.0.0.0:5000/ にアクセスして、芸能人の写真を送信してみましょう。　 
<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647807/15e04800-dfac-58b0-501f-09dd7dd5dd0c.png">

*