专栏首页MixLab科技+设计实验室自己动手做一个识别手写数字的web应用04

自己动手做一个识别手写数字的web应用04

接着往期的3篇继续,一步步动手做:

自己动手做一个识别手写数字的web应用01

自己动手做一个识别手写数字的web应用02

自己动手做一个识别手写数字的web应用03

如果你练习里前面三篇,相信你已经熟悉了DockerKeras,以及Flask了,接下来我们实现一个提供给用户输入手写字的前端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>识别结果:</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>

移动端注意要写这句标签,把屏幕缩放设为no,比例设为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样式都是一些常用的,有兴趣可以自己实现个简单的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("请书写一个数字");
    } else {
         mnistPad.getMNISTGridBySize(true,28,img2text);
    }
});

注意minWidth及MaxWidth的设置,我试验下来,比较好的数值是6跟8,识别效果较好,也可以自行试验修改。

ministPad的方法,getMNISTGridBySize将把截取画板上的手写数字,并缩放成28x28的尺寸,然后调用img2text函数。

img2text主要是把28x28的图片传给后端,获取识别结果,这边由于canvas的数据是base64,需要用到转化为blob的函数,dataURItoBlob(github上有写好的),转化后通过构造一个表单,注意文件名predictImg一定要与后端flask接受函数里的写的一致。调用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 还有一个比较重要的函数:

画板根据屏幕尺寸自适应的代码(尤其是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,可以获取所有手写输入的坐标点。

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的控制台直接试验。

红色的圈圈就是所有的坐标点,只要求出如下图所示的紫色框,第一步也就完成了。

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,我设置了个30的值,把边框稍微放大了下,原因见mnist手写字训练集的图片就知道啦。

到这一步,我们的手写字数据集是下图这样的:

6 我们还需要把边框变成方形。

再写个转换函数:

  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数据集是这样的。

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 转换成带样式的文本,从而避免拷贝引起的样式丢失,再对「代码块」的缩进、换行、字号、行间距进行微调即可。

https://markdown-here.com/get.html

最后,注意下MNIST数据集里的数据,对应的是灰度图,28x28的尺寸,黑底白字,并且数字是像素的重心居中处理的。本文没有介绍如何把web前端的手写字根据重心居中处理这一内容,将会挑选合适时机介绍,用上了可以提高识别率哦!

相关源代码,可以留言获取。

这个系列也基本上完成了,如果你有疑问可以留言。

我要不要考虑开个面授课啊(如果有10个人以上在下方留言区留言,我就考虑开设了),大家抱着电脑,我们一起动手花个半天,亲手实现一个识别手写数字的web应用。

技术栈:

Docker+Keras+Flask+JS+HTML+CSS。

涉及到的内容都可以讲解。

本文分享自微信公众号 - 科技Mix设计Lab(Design-AI-Lab),作者:shadow

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-11-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 以图搜图技术栈

    先看个案例: ? 这是什么动画 https://whatanime.ga/ 一个用于通过动画截图找出处的搜索引擎。可以找到跟动画截图相似的动画片截图,并找到动画...

    mixlab
  • DIY一个Sketch插件,生成猫猫狗狗的全家福

    最近朋友圈都在玩的全家福: ? 看了下是使用 cocos2D 引擎制作的, http://www.cocos.com/creator 主要是图片合成,利用前端...

    mixlab
  • 谈设计与技术,以WEB布局为例

    本文基于“跨界”思维,以 WEB 布局为例,从3个方面,谈谈设计与技术的关系: 1 自适应布局与响应式布局 2 CSS 的布局特性演进 3 设计语言与 W...

    mixlab
  • Jquery和CSS3轻松实现放大镜效果

    每当打开淘宝,天猫等pc端时,看到心仪的物品时,点击图片时,便呈现出放大的效果。有没有去理解分析它的原理?是不是感觉非常的神奇?当真正地去接触,其实非常好理解。...

    Javanx
  • JavaScript IE9以下浏览器版本升级提示

    用户2836074
  • AS3性能优化

    本篇文章用来总结本人对AS3性能优化方面的认识及经验,可能会有一些错误,敬请不吝赐教.如果想了解更多,请参考ADOBE方面的相关只是介绍.

    py3study
  • 实现属于自己的TensorFlow(一) - 计算图与前向传播

    前言 前段时间因为课题需要使用了一段时间TensorFlow,感觉这种框架很有意思,除了可以搭建复杂的神经网络,也可以优化其他自己需要的计算模型,所以一直想自...

    Python中文社区
  • HTML5 Canvas API详解

    HTML5 是一个新兴标准,它正在以越来越快的速度替代久经考验的 HTML4。HTML5 是一个 W3C “工作草案” — ...

    业余草
  • 停启服务打包解压的脚本

    小小明童鞋
  • 原生JS实现轮播图

    越陌度阡

扫码关注云+社区

领取腾讯云代金券