Vue.js+Flaskで画像のアップロード機能

# 概要
今回はフロントエンドにVue.js、バックエンドにFlaskを用いた画像認識アプリを作ります。
ひとまず今回は画像アップロード機能までの実装です。

# 環境
- Docker
- Vue-cli
- flask (pipenv)

上記の環境で環境構築しました。
手順や詳細は以下のリンクを参照してください。

[Vue + Flask on Docker](https://qiita.com/kasayu/items/43734e570dc458a5be15)

# 要所の説明
### Vue
詳細を説明するのは、以下のコード。

```javascript:Home.vue
// 画像をサーバーへアップロード
    onUploadImage () {
      var params = new FormData()
      params.append('image', this.uploadedImage)
      // Axiosを用いてFormData化したデータをFlaskへPostしています。
      axios.post(`${API_URL}/classification`, params).then(function (response) {
        console.log(response)
    })
```
1. 取得した画像はBase64化がなされている。「data:image/jpeg:base64,〜」
2. FormDataにより、データをHTTPリクエストで「キー：値」の形式へ。
3. Axiosを適用し、'127.0.0.1:5000/classification'+ POSTメソッドでデータを送信。

### Flask

詳細を説明するのは、以下のコード。

```python:app.py
@app.route('/classification', methods=['POST'])
def uploadImage():
    if request.method == 'POST':
        base64_png =  request.form['image']
        code = base64.b64decode(base64_png.split(',')[1]) 
        image_decoded = Image.open(BytesIO(code))
        image_decoded.save(Path(app.config['UPLOAD_FOLDER']) / 'image.png')
        return make_response(jsonify({'result': 'success'}))
    else: 
        return make_response(jsonify({'result': 'invalid method'}), 400)
```

1. FormDataの内部に「data:image/jpeg:base64,〜」が存在。ファイル名を取得。
2. Pillow(PIL)で画像を取得。
3. 画像の保存。

#全体像

## Vue
```vuejs:Home.vue
<template>
  <div>
    <div class="imgContent">
      <div class="imagePreview">
        <img :src="uploadedImage" style="width:100%;" />
      </div>
      <input type="file" class="file_input" name="photo" @change="onFileChange"  accept="image/*" />
      <button @click='onUploadImage'>画像判定してくだちい・・・</button>
    </div>
  </div>
</template>

<script>
import axios from 'axios'

const API_URL = 'http://127.0.0.1:5000'
export default {
  data () {
    return {
      uploadedImage: ''
    }
  },
  methods: {
    // 選択した画像を反映
    onFileChange (e) {
      let files = e.target.files || e.dataTransfer.files
      this.createImage(files[0])
    },
    // アップロードした画像を表示
    createImage (file) {
      let reader = new FileReader()
      reader.onload = (e) => {
        this.uploadedImage = e.target.result
      }
      reader.readAsDataURL(file)
    },
    // 画像をサーバーへアップロード
    onUploadImage () {
      var params = new FormData()
      params.append('image', this.uploadedImage)
      // Axiosを用いてFormData化したデータをFlaskへPostしています。
      axios.post(`${API_URL}/classification`, params).then(function (response) {
        console.log(response)
      })
    }
  }
}
</script>
```
## Flask
###留意点

- CORSにより、異なるオリジン(プロトコルやドメイン、ポート)でもリソースを共有できる。
- CORSは異なるWebアプリケーションを持つ場合、必須。
- app.config['JSON_AS_ASCII'] = False により日本語対応可能。


```python:app.py
# render_template：参照するテンプレートを指定
# jsonify：json出力
from flask import Flask, render_template, jsonify, request, make_response

# CORS：Ajax通信するためのライブラリ
from flask_restful import Api, Resource
from flask_cors import CORS 
from random import *
from PIL import Image
from pathlib import Path
from io import BytesIO
import base64

# static_folder：vueでビルドした静的ファイルのパスを指定
# template_folder：vueでビルドしたindex.htmlのパスを指定
app = Flask(__name__, static_folder = "./../frontend/dist/static", template_folder="./../frontend/dist")

#日本語
app.config['JSON_AS_ASCII'] = False
#CORS=Ajaxで安全に通信するための規約
api = Api(app)
CORS(app)

UPLOAD_FOLDER = './uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# 任意のリクエストを受け取った時、index.htmlを参照
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):
    return render_template("index.html")

@app.route('/classification', methods=['POST'])
def uploadImage():
    if request.method == 'POST':
        base64_png =  request.form['image']
        code = base64.b64decode(base64_png.split(',')[1]) 
        image_decoded = Image.open(BytesIO(code))
        image_decoded.save(Path(app.config['UPLOAD_FOLDER']) / 'image.png')
        return make_response(jsonify({'result': 'success'}))
    else: 
        return make_response(jsonify({'result': 'invalid method'}), 400)

# app.run(host, port)：hostとportを指定してflaskサーバを起動
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
```

#様子
<img width="706" alt="スクリーンショット 2020-07-29 18.47.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253129/64ac9716-9c5b-aa6e-2cbf-051ec66693e7.png">

こんな感じ

#非常に参考になりました
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS
[画像をPOST、顔検出、canvasで顔にお絵かき](https://qiita.com/s-yoshiki/items/d4a460b2ab56e72220b9)