iT邦幫忙

2021 iThome 鐵人賽

DAY 7
2
AI & Data

機器學習與前端網頁系列 第 7

Day 7 拖動上傳圖片辨識數字

今天要做的是...
做一個前端網頁,支援拖動圖片上傳,
把圖片轉成 base64 送給伺服器,伺服器將 base64 轉回圖片後進行辨識傳回結果。

這邊是 index.html,新增了一個 250x250 的 mycanvas,並改寫拖動事件,
mycanvas 在drop事件後會呼叫 fileReader 讀取檔案,
fileReader 讀取檔案後會寫入 img 變數,
image 物件被寫入後,會將內容畫在 mycanvas 上。
fileReader.result 和 image.src 都是字串,而格式為 data:image/png;base64, 後接 base64 字串,如下範例。
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA....

mybutton 被按下後,會將上段字串送至後端的 /mnist 路徑。

<!DOCTYPE html>
<html>

<head>
    <title>Page Title</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>

<body>
    <canvas id="mycanvas" width="250" height="250">
        Your browser does not support the HTML5 canvas tag.</canvas>
        <button id="mybutton">送出</button>
</body>
</html>
<script>

    var mycanvas = document.getElementById("mycanvas");
    var image = new Image();
    var ctx = document.getElementById("mycanvas").getContext("2d");
    var fileReader = new FileReader();

    mycanvas.ondragover = function (e) {
        e.preventDefault();
    }
    mycanvas.ondrop = function (e) {
        e.preventDefault();
        let f = e.dataTransfer.files[0];
        fileReader.readAsDataURL(f);
    }
    fileReader.onload = function () {
        image.src = fileReader.result;
    }
    image.onload = function () {
        ctx.drawImage(image, 0, 0, 250, 250);
    };

    var mybutton = document.getElementById("mybutton");
    mybutton.onclick = function() {
        $.post("/mnist",
        {
            "base64_str": image.src
        },
        function(data, status){
          alert("Data: " + data + "\nStatus: " + status);
          console.log(data);
        });
    }
    
</script>

後端這裡則是新增了一個路徑 /mnist,作為辨識網址,它會接收 base64_str 變數並依照","裁切。 (因為辨識只需要 base64 字串)
成功後 flask jsonify 會將物件以 json 格式回傳。

# a01_flask_server.py
import base64
from flask import Flask, render_template, request, jsonify
app = Flask("mnist")

import a06_mnist_api


@app.route("/")
def hello_world():
    return render_template('index.html')

@app.route("/mnist", methods = ['POST'])
def mnist():
    # base64tag = "data:image/png;base64"
    data = request.form.get("base64_str").split(",", 1)
    
    if len(data) == 2:
        return jsonify(a06_mnist_api.predict_from_base64(data[1]))

    return jsonify([False])


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5050)

這邊則是進行影像處理的部分,先將 base64_str 轉成 bytes-like object,
後使用 np.frombuffer 把 bytes-like object 轉成 numpy array,
使用 cv2.imdecode 灰階模式讀取 numpy array,
然後縮放圖片至 28x28,最後丟進模型預測。
flask jsonify 不支援將 numpy 的數值直接轉為 json,所以在最後用兩層迴圈將預測結果轉成 float,並四捨五入至小數點二位。

# a06_mnist_api.py
import tensorflow as tf
import numpy as np
import base64
import cv2
saved_model_path = "mnist"
model = tf.keras.models.load_model(saved_model_path)


def predict_from_base64(base64_str):
    decoded = base64.b64decode(base64_str)
    np_arr = np.frombuffer(decoded,np.uint8)
    imggray = cv2.imdecode(np_arr, cv2.IMREAD_GRAYSCALE)
    resized = cv2.resize(imggray, (28, 28))
    return predict_from_img_array(resized)


def predict_from_img_array(img_array):
    input_arr = np.array([img_array])  # Convert single image to a batch.
    predictions = model.predict(input_arr)
    return [ [round(float(j), 2) for j in i] for i in predictions]


最後結果


上一篇
Day 6 追加訓練
下一篇
Day 8 瀏覽器上畫圖
系列文
機器學習與前端網頁30

尚未有邦友留言

立即登入留言