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]


最後结果


<<:  【Day7】ERP核心模组篇-Purchase

>>:  D-23 方法 method ? property

Day28 Lab 2 - Object storage前端实作

我们的前端并不是一个精美的UI,在本次Lab中实做了前端有两个目的 方便测试 好的前端能减经後端的负...

Day-16 雇用问题, 指示器随机变数(indicator random variable), 随机化演算法

雇用问题 假设你要雇用新的办公助理,而你找了一个雇用代理人去帮你推荐应聘的人,雇用代理人每天会给你推...

Day 26: 人工智慧在音乐领域的应用 (AI作曲 - 生成对抗网路 Gan (干) )

今天我们来聊聊生成对抗网路 (Generative Adversarial Network, GAN...

【Day1】如何调整WIN10上应用程序的拨放音量?

在疫情期间相信大家都有在线上上课或会议的经验, 有时候我们会觉得老师上课的声音(或会议应用程序拨放的...

Day30 撒花~

大家好,我是乌木白,今天是铁人赛最後一天,谢谢大家在这些天不管是无意或是有意的点进来参观,都非常感...