AI应用开发实战 - 手写识别应用入门

手写体识别的应用已经非常流行了,如输入法,图片中的文字识别等。但对于大多数开发人员来说,如何实现这样的一个应用,还是会感觉无从下手。本文从简单的MNIST训练出来的模型开始,和大家一起入门手写体识别。

在本教程结束后,会得到一个能用的AI应用,也许是你的第一个AI应用。虽然离实际使用还有较大的距离(具体差距在文章后面会分析),但会让你对AI应用有一个初步的认识,有能力逐步搭建出能够实际应用的模型。

建议和反馈,请发送到
https://github.com/Microsoft/vs-tools-for-ai/issues

联系我们
OpenmindChina@microsoft.com

准备工作

  • 使用win10 64位操作系统的计算机
  • 参考上一篇博客AI应用开发实战 - 从零开始配置环境。在电脑上训练并导出MNIST模型。

一、 思路

通过上一篇文章搭建环境的介绍后,就能得到一个能识别单个手写数字的模型了,并且识别的准确度会在98%,甚至99%以上了。那么我们要怎么使用这个模型来搭建应用呢?

大致的步骤如下:

  1. 实现简单的界面,将用户用鼠标或者触屏的输入变成图片
  2. 将生成的模型包装起来,成为有公开数据接口的类。
  3. 将输入的图片进行规范化,成为数据接口能够使用的格式。
  4. 最后通过模型来推理(inference)出图片应该是哪个数字,并显示出来。

是不是很简单?

二、动手

步骤一:获取手写的数字

提问:那我们要怎么获取手写的数字呢?

回答:我们可以写一个简单的WinForm画图程序,让我们可以用鼠标手写数字,然后把图片保存下来。

首先,我们打开Visual Studio,选择文件->新建->项目

在弹出的窗口里选择Visual C#->Windows窗体应用,项目名称不妨叫做DrawDigit,解决方案名称不妨叫做MnistForm,点击确定。

此时,Visual Studio也自动弹出了一个窗口的设计图。

在DrawDigit项目上点击右键,选择属性,在生成一栏将平台目标从Any CPU改为x64

否则,DrawDigit(首选32位)与它引用的MnistForm(64位)的编译平台不一致会引发System.BadImageFormatException的异常。

然后我们对这个窗口做一些简单的修改:

首先我们打开VS窗口左侧的工具箱,这个窗口程序需要以下三种组件:
1. PictureBox:用来手写数字,并且把数字保存成图片
2. Label:用来显示模型的识别结果
3. Button:用来清理PictureBox的手写结果

那经过一些简单的选择与拖动还有调整大小,这个窗口现在是这样的:

一些注意事项

  1. 这些组件都可以通过右键->查看属性,在属性里修改它们的设置
  2. 为了方便把PictureBox里的图片转化成Mnist能识别的格式,PictureBox的需要是正方形
  3. 可以给这些控件起上有意义的名称。
  4. 可以调整一下label控件大小、字体等,让它更美观。

经过一些简单的调整,这个窗口现在是这样的:

现在来让我们愉快地给这些组件添加事件!

还是在属性窗口,我们选择某个组件,右键->查看属性,点击闪电符号,给组件绑定对应的事件。每次绑定后,会跳到代码部分,生成一个空函数。点回设计视图继续操作即可。

组件类型 事件
pictureBox1 Mouse下双击MouseDownMouseUpMouseMove来生成对应的响应事件函数。
button1 如上,在Action下双击Click
Form1 如上,在Behavior下双击Load

然后我们开始补全对应的函数体内容。

注意,如果在上面改变了控件的名称,下面的代码需要做对应的更改。

废话少说上代码!

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;//用于优化绘制的结果
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using MnistModel;namespace DrawDigit
{public partial class Form1 : Form{public Form1(){InitializeComponent();}private Bitmap digitImage;//用来保存手写数字private Point startPoint;//用于绘制线段,作为线段的初始端点坐标private Mnist model;//用于识别手写数字private const int MnistImageSize = 28;//Mnist模型所需的输入图片大小private void Form1_Load(object sender, EventArgs e){//当窗口加载时,绘制一个白色方框model = new Mnist();digitImage = new Bitmap(pictureBox1.Width, pictureBox1.Height);Graphics g = Graphics.FromImage(digitImage);g.Clear(Color.White);pictureBox1.Image = digitImage;}private void clean_click(object sender, EventArgs e){//当点击清除时,重新绘制一个白色方框,同时清除label1显示的文本digitImage = new Bitmap(pictureBox1.Width, pictureBox1.Height);Graphics g = Graphics.FromImage(digitImage);g.Clear(Color.White);pictureBox1.Image = digitImage;label1.Text = "";}private void pictureBox1_MouseDown(object sender, MouseEventArgs e){//当鼠标左键被按下时,记录下需要绘制的线段的起始坐标startPoint = (e.Button == MouseButtons.Left) ? e.Location : startPoint;}private void pictureBox1_MouseMove(object sender, MouseEventArgs e){//当鼠标在移动,且当前处于绘制状态时,根据鼠标的实时位置与记录的起始坐标绘制线段,同时更新需要绘制的线段的起始坐标if (e.Button == MouseButtons.Left){Graphics g = Graphics.FromImage(digitImage);Pen myPen = new Pen(Color.Black, 40);myPen.StartCap = LineCap.Round;myPen.EndCap = LineCap.Round;g.DrawLine(myPen,startPoint, e.Location);pictureBox1.Image = digitImage;g.Dispose();startPoint = e.Location;}}private void pictureBox1_MouseUp(object sender, MouseEventArgs e){//当鼠标左键释放时//同时开始处理图片进行推理//暂时不处理这里的代码}}
}

步骤二:把模型包装成一个类

将模型包装成一个C#是整个过程中比较麻烦的一步。所幸的是,Tools for AI对此提供了很好的支持。进一步了解,可以看这里。

首先,我们在解决方案MnistForm下点击鼠标右键,选择添加->新建项目,在弹出的窗口里选择AI Tools->Inference->模型推理类库,名称不妨叫做MnistModel,点击确定,于是我们又多了一个项目,

然后自己配置好这个项目的名称、位置,点击确定

然后弹出一个模型推理类库创建向导,这个时候就需要我们选择自己之前训练好的模型了~

首先在模型路径里选择保存的模型文件的路径。这里我们使用在AI应用开发实战 - 从零开始配置环境博客中训练并导出的模型

note:模型可在/samples-for-ai/examples/tensorflow/MNIST目录下找到,其中output文件夹保存了检查点文件,export文件夹保存了模型文件。

对于TensorFlow,我们可以选择检查点的.meta文件,或者是保存的模型的.pb文件

这里我们选择在AI应用开发实战 - 从零开始配置环境这篇博客最后生成的export目录下的检查点的SavedModel.pb文件,这时程序将自动配置好配置推理接口,见下图:

类名可以自己定义,因为我们用的是MNIST,那么类名就叫Mnist好了,然后点击确定。

这样,在解决方案资源管理器里,在解决方案MnistForm下,就多了一个MnistModel

双击Mnist.cs,我们可以看到项目自动把模型进行了封装,生成了一个公开的infer函数。

然后我们在MnistModel上右击,再选择生成,等待一会,这个项目就可以使用了~

步骤三:连接两个部分

这一步差不多就是这么个感觉:

I have an apple , I have a pen. AH~ , Applepen

首先,我们来给DrawDigit添加引用,让它能使用MnistModel。在DrawDigit项目的引用上点击鼠标右键,点击添加引用,在弹出的窗口中选择MnistModel,点击确定。

然后,由于MNIST的模型的输入是一个28×28的白字黑底的灰度图,因此我们首先要对图片进行一些处理。
首先将图片转为28×28的大小。
然后将RGB图片转化为灰阶图,将灰阶标准化到[-0.5,0.5]区间内,转换为黑底白字。
最后将图片用mnist模型要求的格式包装起来,并传送给它进行推理。
于是,我们在pictureBox1_MouseUp中添加上这些代码,并且在文件最初添加上using MnistModel;

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e){//当鼠标左键释放时//开始处理图片进行推理if (e.Button == MouseButtons.Left){Bitmap digitTmp = (Bitmap)digitImage.Clone();//复制digitImage//调整图片大小为Mnist模型可接收的大小:28×28using (Graphics g = Graphics.FromImage(digitTmp)){g.InterpolationMode = InterpolationMode.HighQualityBicubic;g.DrawImage(digitTmp, 0, 0, MnistImageSize, MnistImageSize);}//将图片转为灰阶图,并将图片的像素信息保存在list中var image = new List<float>(MnistImageSize * MnistImageSize);for (var x = 0; x < MnistImageSize; x++){for (var y = 0; y < MnistImageSize; y++){var color = digitTmp.GetPixel(y, x);var a = (float)(0.5 - (color.R + color.G + color.B) / (3.0 * 255));image.Add(a);}}//将图片信息包装为mnist模型规定的输入格式var batch = new List<IEnumerable<float>>();batch.Add(image);//将图片传送给mnist模型进行推理var result = model.Infer(batch);//将推理结果输出label1.Text = result.First().First().ToString();}}

最后让我们尝试一下运行~

三、效果展示

现在我们就有了一个简单的小程序,可以识别手写的数字了。

赶紧试试效果怎么样~

注意

  1. 路径中不能有中文字符,否则可能找不到模型。

进阶

那么,如果要识别多个连写的数字,或支持字母该怎么做呢?大家多用用也会发现,如果数字写得很小,或者没写到正中,识别起来正确率也会不高。要解决这些问题,做成真正的产品,就不止这一个模型了。比如在多个数字识别中,可能要根据经验来切分图,或者训练另一个模型来检测并分割数字。要支持字母,则需要重新训练一个包含手写字母的模型,并准备更多的字母的数据。要解决字太小的问题,还要检测一下字的大小,做合适的放大等等。

我们可以看到,一个训练出来的模型本身到一个实际的应用之间还有不少的功能要实现。希望我们这一系列的介绍,能够帮助大家将机器学习的概念带入到传统的编程领域中,做出更聪明的产品。

AI应用开发实战系列之三:手写识别应用入门相关推荐

  1. AI应用开发实战 - 手写识别应用入门

    AI应用开发实战 - 手写识别应用入门 手写体识别的应用已经非常流行了,如输入法,图片中的文字识别等.但对于大多数开发人员来说,如何实现这样的一个应用,还是会感觉无从下手.本文从简单的MNIST训练出 ...

  2. AI应用开发实战系列之四 - 定制化视觉服务的使用

    AI应用开发实战 - 定制化视觉服务的使用 本篇教程的目标是学会使用定制化视觉服务,并能在UWP应用中集成定制化视觉服务模型. 前一篇:AI应用开发实战 - 手写识别应用入门 建议和反馈,请发送到 h ...

  3. AI应用开发实战系列之二:从零开始搭建macOS开发环境

    AI应用开发实战 - 从零开始搭建macOS开发环境 本视频配套的视频教程请访问:https://www.bilibili.com/video/av24368929/ 零.前提条件 一台能联网的电脑, ...

  4. 鸿蒙开发实战系列之三:网络请求(原生+ Retrofit)

    鸿蒙开发实战系列之一:鸿蒙开发实战系列之一:圆角 鸿蒙开发实战系列之二:鸿蒙开发实战系列之二:事件总线EventBus/RxBus 前言 过了一个漫长的中秋+国庆假期,大家伙的鸿蒙内功修炼的怎么样了? ...

  5. AI应用开发实战系列之一: 从零开始配置环境

    AI应用开发实战 - 从零开始配置环境 与本篇配套的视频教程请访问:https://www.bilibili.com/video/av24421492/ 零.前提条件 一台能联网的电脑,使用win10 ...

  6. Tensorflow之基于MNIST手写识别的入门介绍

    Tensorflow是当下AI热潮下,最为受欢迎的开源框架.无论是从Github上的fork数量还是star数量,还是从支持的语音,开发资料,社区活跃度等多方面,他当之为superstar. 在前面介 ...

  7. cnn神经网络可以用于数据拟合吗_使用Keras搭建卷积神经网络进行手写识别的入门(包含代码解读)...

    本文是发在Medium上的一篇博客:<Handwritten Equation Solver using Convolutional Neural Network>.本文是原文的翻译.这篇 ...

  8. Top K问题系列之三 手写代码

    Top K问题是面试时手写代码的常考题,某些场景下的解法与堆排和快排的关系紧密,所以把它放在堆排后面讲. 关于Top K问题最全的分类总结是在这里(包括海量数据的处理),个人将这些题分成了两类:一类是 ...

  9. AI应用开发实战 - 手写算式计算器

    扩展手写数字识别应用 识别并计算简单手写数学表达式 主要知识点 了解MNIST数据集 了解如何扩展数据集 实现手写算式计算器 简介 本文将介绍一例支持识别手写数学表达式并对其进行计算的人工智能应用的开 ...

最新文章

  1. Codeforces Round #535 (Div. 3)题解
  2. OpenSessionInViewFilter原理以及为什么要用OpenSessionInViewF
  3. textread函数_matlab基础textread函数实例
  4. 今日SGU 5.25
  5. 【代码】ReentrantLock使用tryLock进行尝试锁定
  6. jconsole 里的线程编号一直在增加_第三章_运行时数据区概述及线程
  7. ssm医院疫情隔离室管理系统答辩PPT模板
  8. 转:把二元查找树转变成排序的双向链表
  9. iOS开发之结构体底层探索
  10. Martin Fowler谈微服务的优缺点
  11. python获取日期的年月日_python datetime和time的一些疑惑解答 及 获取上年同期、上月等日期...
  12. JAVA排序:快速排序算法
  13. 八种排序java实现
  14. 吴恩达-deep learning 02.改善深层神经网络:超参数调试、正则化以及优化Week1
  15. 用JavaScript实现字体大小屏幕自适应
  16. mate9 android os,华为Mate9评测:全新EMUI 5系统 永不卡顿的安卓机?
  17. 网站建设和网站运营,网站如何进行宣传推广
  18. android自定义虚线,Android自定义view的方式绘制虚线
  19. 2345 php笔试题,2345浏览器笔试题
  20. nodejs之pathinfo/pathname的使用

热门文章

  1. 数据库 proc编程七
  2. c# 配置文件App.config操作类库
  3. iOS - Masonry自动布局
  4. JavaScript抽象类及Class.create备忘
  5. 【操作系统复习】进程控制
  6. Leetcode--923. 三数之和的多种可能
  7. c++错误functional:1526:9: error: no type named ‘type’ in ‘class std::result_of
  8. python调用c++的库传递二级指针
  9. 快递下单后取消订单_网约车定位地点不动,男子别的平台下单,没取消订单要付6.6元...
  10. xp打印服务器win10的系统连接不上,Win10系统访问WinXP系统共享打印机却连接不了的解决方法...