Flaskで画像予測のWebアプリを作りHerokuにデブロイするまで

#はじめに
- なんでもいいからWebアプリを作りたかった初心者です。
- 今回１からWebアプリを作ったのでその流れを書いていきます。
- 予測モデルは自分で作る気が起きなかったので、imagenetで訓練されたものを使いました。

#環境
- Windows10
- Git Bush
- Python3.6.5
    - Flask 0.12.2
    - Keras 2.1.6

#コードの解説

###画像受け取り部分
まず画像を受け取れないと予測も何もできないなと思ったのでこの部分を作りました。

```html:gazo.html
    <p>画像をアップロードしてください</p>
    <form method="post" action="/send" enctype="multipart/form-data">
　　  <input type="file" id="img_file" name="img_file" class="col-sm-4">
  　　<input type="submit" value="送信" class="btn">
　　</form>
```

```python:app.py
@app.route('/send',methods = ['post'])
def posttest():
    img_file = request.files['img_file']
    fileName = img_file.filename

    #拡張子取り出し
    root, ext = os.path.splitext(fileName)
    ext = ext.lower()

    #拡張子判別
    gazouketori = set([".jpg", ".jpeg", ".jpe", ".jp2", ".png", ".webp", ".bmp", ".pbm", ".pgm", ".ppm",
                      ".pxm", ".pnm",  ".sr",  ".ras", ".tiff", ".tif", ".exr", ".hdr", ".pic", ".dib"])
    if ext not in gazouketori:
        return render_template('index.html',massege = "対応してない拡張子です",color = "red")
```
  
  

Flaskはmultipart/form-dataで画像を受け取ることが出来ます。
今回は受け取った画像のファイル名から拡張子を取得して、対応してないものをはじくことにしました。

###予測部分
pretrainのものを使っているだけなので非常に簡単です

```python:imagenet.py
from keras.applications.mobilenet import MobileNet, preprocess_input, decode_predictions
from keras.models import load_model
from keras.preprocessing import image

model = MobileNet(input_shape=(128,128,3), alpha=1.0, depth_multiplier=1, dropout=1e-3, include_top=True, weights=None, input_tensor=None, pooling=None, classes=1000)
model.load_weights("./model/kerasmobilenet.h5")

#英名を日本語に変換するjsonを読み込む
with open('./model/imagenet_class_index.json',encoding="utf-8") as f:
    zisyo = json.load(f)
    zisyo = {item["en"]: item["ja"] for item in zisyo}


class imagenet():
    def __init__(self,model):
        self.model = model
    def predict(self, img):
        #リサイズ
        img = image.load_img(img, target_size=(128, 128))
        img = image.img_to_array(img).reshape(1,128,128,3)
        pred = self.model.predict(preprocess_input(img))

        #予測を名前と確率にデコード
        top = decode_predictions(pred, top=5)
        desc = []
        score = []
        for i in range(5):
            desc.append(top[0][i][1])
            score.append(top[0][i][2]*100)
        return desc, score

bunrui = imagenet(model)
desc, score =  bunrui.predict(img_file)
for i in range(5):
    #順位付けと日本語変換と四捨五入
    desc[i] = [i+1,zisyo[desc[i]],round(score[i],1)]
```

  
予測モデルはMobileNetを利用しました。
予測精度がそこまで高いわけではないですが軽いのでWebアプリ向けでした。
受け取った画像を一律128*128にして予測モデルに入れています。
そのあとdecode_predictionsというメソッドで名前を出力して[日本語化のjson](https://gist.github.com/PonDad/4dcb4b242b9358e524b4ddecbee385e9)を使い日本語にしました。

###結果発表

```python:app.py
#画像書き込み用バッファに画像を保存してhtmlに返す
buf = io.BytesIO()
image = Image.open(img_file)
image.save(buf, 'png')
qr_b64str = base64.b64encode(buf.getvalue()).decode("utf-8")
qr_b64data = "data:image/png;base64,{}".format(qr_b64str)
return render_template('kekka.html',namae = desc ,img = qr_b64data)
```

```html:kekka.html
    <h1>予測結果</h1>
    <img src={{img}} alt="今回の画像" title="今回の画像">
    {% for ban,desc,kaku in namae %}
      <p class="zyuni">{{ban}}. {{desc}} {{kaku}}%</p>
    {% endfor %}
```

  
Flaskは、Jinja2 テンプレートエンジンを保持しているので結果をfor文で回して表示させることが出来ます。

#Herokuへのデブロイ
###Heroku用のファイルを作る


```
$ pip install gunicorn
$ pip freeze > requirements.txt
$ echo web: gunicorn app:app --log-file=- > Procfile
```
  
herokuを使う際にはGunicornというアプリケーションサーバーインストールするのが楽です。

###Heroku Gitを使う
まずWebでアプリ名を決めます。
Heroku CLIをダウンロードした後、Git Bashを使ってデブロイしました。

```
$ heroku login
$ heroku git:remote -a <HerokuAppName>
$ git push heroku master
```
これでheroku側で勝手に環境構築してくれてデブロイ出来ます。

#成果物
[サイト](https://imagenetekito.herokuapp.com/)と[Github](https://github.com/rookzeno/webdeyosoku)はここに置いときます。

タイトル画面はこんな感じです。
<img src="https://i.imgur.com/GLtGjwp.png" width=80%>

CSS力とデザイン力がなかったので殺風景なのは見逃してほしい。
これに画像を入れると

<img src="https://i.imgur.com/eFzKdfM.png" width=60%>

ちゃんと予測してくれます。
一位もアルプスなので大体あっている気がする。

#まとめ
初心者でもWebアプリは簡単に作れて楽しい。
　　
　　
　　
  
  
  



##今回分からなかったこと
ファイルの受け取りサイズを1MBに設定したら、1MB以上のファイルを送るとアプリが落ちてしまうようになったこと。

###参考元

[Flaskでmultipart/form-dataのファイルアップロードを実現する方法](https://qiita.com/5zm/items/ac8c9d1d74d012e682b4)

[Python+Flask+Herokuで簡単webアプリ](https://qiita.com/gogotanaka/items/a30764001d9f312465d8)

[人工知能に関する断創録]
(http://aidiary.hatenablog.com/entry/20170104/1483535144)