接著往期的3篇繼續(xù),,一步步動手做: 自己動手做一個識別手寫數(shù)字的web應(yīng)用01
自己動手做一個識別手寫數(shù)字的web應(yīng)用02 自己動手做一個識別手寫數(shù)字的web應(yīng)用03 如果你練習(xí)里前面三篇,,相信你已經(jīng)熟悉了Docker和Keras,以及Flask了,,接下來我們實現(xiàn)一個提供給用戶輸入手寫字的前端web頁面。 前端畫板我們可以自己用最基本的canvas寫,,也可以選擇封裝好的開源庫: 下面介紹2個比較好的模擬手寫效果的畫板庫: 1 signature_pad https://github.com/szimek/signature_pad/ 2 drawingboard.js https://github.com/Leimi/drawingboard.js 這邊我選擇的是signature_pad,。 HTML代碼: <!doctype html> <html lang="zh"><head>
<meta charset="utf-8">
<title>mnist demo</title>
<meta name="viewport" content="width=device-width,initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" href="./static/css/main.css"></head>
<body onselectstart="return false">
<div id="mnist-pad">
<div class="mnist-pad-body">
<canvas></canvas> </div>
<div class="mnist-pad-footer">
<div class="mnist-pad-result">
<h5>識別結(jié)果:</h5>
<h5 id="mnist-pad-result"></h5>
</div> <div class="mnist-pad-actions">
<button type="button" id="mnist-pad-clear">清除</button>
<button type="button" id="mnist-pad-save">識別</button> </div>
</div></div>
<script src="./static/js/signature_pad.js"></script>
<script src="./static/js/mnist.js"></script>
<script src="./static/js/app.js"></script>
</body>
</html> 移動端注意要寫這句標(biāo)簽,把屏幕縮放設(shè)為no,,比例設(shè)為1:
<meta name="viewport" content="width=device-width,initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"> CSS代碼: body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100%;
user-select: none;
margin: 0;
padding: 0;
} h5 {
margin: 0;
padding: 0
} #mnist-pad {
position: relative;
display: flex;
flex-direction: column;
font-size: 1em;
width: 100%;
height: 100%;
background-color: #fff;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.08) inset;
padding: 16px;
} .mnist-pad-body {
position: relative;
flex: 1;
border: 1px solid #f4f4f4;
} .mnist-pad-body canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
} .mnist-pad-footer {
color: #C3C3C3;
font-size: 1.2em;
margin-top: 8px;
margin-bottom: 8px;
} .mnist-pad-result {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 8px;
} .mnist-pad-actions {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
} #mnist-pad-clear {
height: 44px;
background-color: #eeeeee;
width: 98px;
border: none;
font-size: 16px;
color: #4a4a4a;
} #mnist-pad-save {
height: 44px;
background-color: #3b3b3b;
width: 98px;
border: none;
font-size: 16px;
color: #ffffff;
} CSS樣式都是一些常用的,,有興趣可以自己實現(xiàn)個簡單的UI。 JS代碼,,有3個文件: signature_pad.js 這是引用的開源庫,; mnist.js 這是我們給開源庫寫的一些擴展,下文會介紹,; app.js主要是一些初始化,,事件綁定,請求后端接口的處理,。 先來看看app.js: 1 初始化畫板,,綁定按鈕事件; var clearBtn = document.getElementById("mnist-pad-clear"); var saveBtn = document.getElementById("mnist-pad-save");
var canvas = document.querySelector("canvas");
var mnistPad = new SignaturePad(canvas, { backgroundColor: 'transparent', minWidth: 6, maxWidth: 8 }); clearBtn.addEventListener("click", function (event) {
mnistPad.clear();
});
saveBtn.addEventListener("click", function (event) { if (mnistPad.isEmpty()) {
alert("請書寫一個數(shù)字");
} else {
mnistPad.getMNISTGridBySize(true,28,img2text);
}
}); 注意minWidth及MaxWidth的設(shè)置,,我試驗下來,,比較好的數(shù)值是6跟8,識別效果較好,,也可以自行試驗修改,。 ministPad的方法,getMNISTGridBySize將把截取畫板上的手寫數(shù)字,,并縮放成28x28的尺寸,,然后調(diào)用img2text函數(shù)。 img2text主要是把28x28的圖片傳給后端,,獲取識別結(jié)果,,這邊由于canvas的數(shù)據(jù)是base64,需要用到轉(zhuǎn)化為blob的函數(shù),,dataURItoBlob(github上有寫好的),,轉(zhuǎn)化后通過構(gòu)造一個表單,,注意文件名predictImg一定要與后端flask接受函數(shù)里的寫的一致。調(diào)用XMLHttpRequest請求后端接口即可,。 2 這一步“如何把canvas生成的圖片上傳至后端”是個很典型的問題,。 function img2text(b64img){
var formData = new FormData(); var blob = dataURItoBlob(b64img);
formData.append("predictImg", blob);
var request = new XMLHttpRequest();
request.onreadystatechange = function () { if (request.readyState == 4) { if ((request.status >= 200 && request.status < 300) || request.status == 304) { console.log(request.response) document.querySelector('#mnist-pad-result').innerHTML=request.response;
};
}
};
request.open("POST", "./predict");
request.send(formData);
}; 3 還有一個比較重要的函數(shù): 畫板根據(jù)屏幕尺寸自適應(yīng)的代碼(尤其是PC端,記得加): function resizeCanvas() { var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight; // canvas.getContext("2d").scale(ratio, ratio);
mnistPad.clear();
},; window.onresize = resizeCanvas;
resizeCanvas(); 到這一步可以試一下前端的輸入效果先: 接下來完成mnist.js 4 signature_pad有個方法是toData,,可以獲取所有手寫輸入的坐標(biāo)點。
var ps=mnistPad.toData()[0]; mnistPad._ctx.strokeStyle='red'; ps.forEach((p,i)=>{
mnistPad._ctx.beginPath();
mnistPad._ctx.arc(p.x, p.y, 4, 0, 2 * Math.PI);
mnistPad._ctx.stroke();
}) 我們可以在chrome的控制臺直接試驗,。 紅色的圈圈就是所有的坐標(biāo)點,,只要求出如下圖所示的紫色框,第一步也就完成了,。 5 給signature_pad擴展個getArea方法: SignaturePad.prototype.getArea = function() { var xs = [],
ys = [];
var orign = this.toData();
for (var i = 0; i < orign.length; i++) { var orignChild = orign[i]; for (var j = 0; j < orignChild.length; j++) {
xs.push(orignChild[j].x);
ys.push(orignChild[j].y);
}
};
var paddingNum = 30; var min_x = Math.min.apply(null, xs) - paddingNum; var min_y = Math.min.apply(null, ys) - paddingNum; var max_x = Math.max.apply(null, xs) + paddingNum; var max_y = Math.max.apply(null, ys) + paddingNum; var width = max_x - min_x,
height = max_y - min_y; var grid = {
x: min_x,
y: min_y,
w: width,
h: height
}; return grid;
}; 測試下: 注意paddingNum,,我設(shè)置了個30的值,把邊框稍微放大了下,,原因見mnist手寫字訓(xùn)練集的圖片就知道啦,。 到這一步,我們的手寫字?jǐn)?shù)據(jù)集是下圖這樣的: 6 我們還需要把邊框變成方形,。
再寫個轉(zhuǎn)換函數(shù): SignaturePad.prototype.change2grid = function(area) { var w = area.w,
h = area.h,
x = area.x,
y = area.y; var xc = x,
yc = y,
wc = w,
hc = h; if (h >= w) {
xc = x - (h - w) * 0.5;
wc = h;
} else {
yc = y - (w - h) * 0.5;
hc = w;
}; return {
x: xc,
y: yc,
w: wc,
h: hc
}
} 原理如下圖,,判斷下長邊是哪個,然后計算出x,y,width,height即可,。 寫好代碼后,,試一下: 紅框是最后要提交的范圍。 這個時候,,還要處理下,,把圖片變成黑底白字的圖片,因為MNIST數(shù)據(jù)集是這樣的,。 7 主要代碼如下: ctx.fillStyle = "white";
ctx.fillRect(0, 0, grid.w, grid.h);
ctx.drawImage(img, grid.x, grid.y, grid.w, grid.h, 0, 0, size, size); var imgData = ctx.getImageData(0, 0, size, size); for (var i = 0; i < imgData.data.length; i += 4) {
imgData.data[i] = 255 - imgData.data[i];
imgData.data[i + 1] = 255 - imgData.data[i + 1];
imgData.data[i + 2] = 255 - imgData.data[i + 2];
imgData.data[i + 3] = 255;
}
ctx.putImageData(imgData, 0, 0); 畫上背景,,遍歷像素,把顏色反色下就ok啦,。 最后都測試下: ps: 今天我用上了Markdown Here美化了代碼塊的展示,,推薦下: 使用 Markdown Here 瀏覽器插件,能夠直接在微信公眾平臺的圖文編輯器中把 Markdown 轉(zhuǎn)換成帶樣式的文本,,從而避免拷貝引起的樣式丟失,,再對「代碼塊」的縮進、換行,、字號,、行間距進行微調(diào)即可。 https:///get.html 最后,注意下MNIST數(shù)據(jù)集里的數(shù)據(jù),,對應(yīng)的是灰度圖,,28x28的尺寸,黑底白字,,并且數(shù)字是像素的重心居中處理的。本文沒有介紹如何把web前端的手寫字根據(jù)重心居中處理這一內(nèi)容,,將會挑選合適時機介紹,,用上了可以提高識別率哦! 相關(guān)源代碼,,可以留言獲取,。 這個系列也基本上完成了,如果你有疑問可以留言,。 我要不要考慮開個面授課?。?strong style="white-space: normal;">如果有10個人以上在下方留言區(qū)留言,我就考慮開設(shè)了),,大家抱著電腦,,我們一起動手花個半天,親手實現(xiàn)一個識別手寫數(shù)字的web應(yīng)用,。 技術(shù)棧: Docker+Keras+Flask+JS+HTML+CSS,。 涉及到的內(nèi)容都可以講解。 地點限于:上海,。 近期熱文: 如何技術(shù)地識別雙十一的“騙”局 人工智能設(shè)計師之智能排版v0.0.3
全民刷軍裝背后的AI技術(shù)及簡單實現(xiàn)
碼字不易,,開啟新的打賞方式:
本公眾號定期更新關(guān)于 設(shè)計師、程序員發(fā)揮創(chuàng)意 互相融合的指南,、作品,。 主要技術(shù)棧: nodejs、react native,、electron Elasticsearch Solidity Keras
|