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

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控件大小、字体等,让它更美观。

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

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

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

组件类型事件
pictureBox1Mouse下双击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. 路径中不能有中文字符,否则可能找不到模型。

进阶

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/500341.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重力加速度换算_中考物理重难点汇总——公式换算大全

初中物理中最重要的部分就是公式了&#xff0c;在这之中公式的换算可以说是一个难点&#xff0c;也是一个重点。力学部分一、速度公式火车过桥(洞)时通过的路程s&#xff1d;L桥&#xff0b;L车声音在空气中的传播速度为340m/s 光在空气中的传播速度为3108m/s二、密度公式(ρ水…

[人工智能教程] 人工智能暑期课实践项目建议

哈工大人工智能暑期课实践项目建议 这个博客介绍了暑期课实践作业的建议。 时间&#xff1a;7/10 - 7/22. 一周上课&#xff0c; 一周项目实践。 要求&#xff1a;项目实践的过程请用公开的博客记录。 项目的源代码请放到 github 中。 每4 ~ 5 人一个小组&#xff0c;从下…

新手一小时就写出人工智能应用 - 看图识熊

来不及了&#xff0c;先上车&#xff1a; 人工智能开发案例 熊的分类 如何安装必要的工具并配置环境呢&#xff0c;请看这个详细的解说 今后会有更详细的文字版在这个专题出现。 如果有对这个教程有疑问&#xff0c;请在这里留言。

c++ 线性回归_模型之母:简单线性回归的代码实现

模型之母&#xff1a;简单线性回归的代码实现关于作者&#xff1a;饼干同学&#xff0c;某人工智能公司交付开发工程师/建模科学家。专注于AI工程化及场景落地&#xff0c;希望和大家分享成长中的专业知识与思考感悟。0x00 前言 在《模型之母&#xff1a;简单线性回归&最小…

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

AI应用开发实战 - 定制化视觉服务的使用 本篇教程的目标是学会使用定制化视觉服务&#xff0c;并能在UWP应用中集成定制化视觉服务模型。 前一篇&#xff1a;AI应用开发实战 - 手写识别应用入门 建议和反馈&#xff0c;请发送到 https://github.com/Microsoft/vs-tools-for-…

server sql 众数_sql 语句系列(众数中位数与百分比)[八百章之第十五章]

众数众数就是出现最多的那个数。select sal,count(*) as cntfrom empwhere DEPTNO20group by sal通过分组把他们的行数计算出来。那么最关键的部分在于&#xff0c;你如何知道最大值。是的我们可以查出当前最大值&#xff0c;然后再取出最大值的sal。但是这肯定要用到两个临时视…

【干货】快速部署微软开源GPU管理利器: OpenPAI

介绍 不管是机器学习的老手&#xff0c;还是入门的新人&#xff0c;都应该装备上尽可能强大的算力。除此之外&#xff0c;还要压榨出硬件的所有潜力来加快模型训练。OpenPAI作为GPU管理的利器&#xff0c;不管是一块GPU&#xff0c;还是上千块GPU&#xff0c;都能够做好调度&a…

python调用ansysworkbench_Workbench通过Python设定材料参数

微信公众号&#xff1a;CAE技术分享以下一段代码是在Workbench中通过Python设定材料的弹性模量和泊松比参数&#xff1a;# encoding: utf-8# Release 16.0SetScriptVersion(Version"16.0.361")template1 GetTemplate(TemplateName"EngData")system1 temp…

现代软件工程 结对/团队作业 - 汉字的 2048 + 俄罗斯方块

一个很有趣的软件工程/编程作业&#xff0c;如果把汉字构成的规律运用在 2048 俄罗斯方块这样的游戏中&#xff0c;会有什么效果呢? (链接1&#xff0c; 链接2) 既然是软件工程的作业&#xff0c; 那就要体现出一些工程的特性&#xff1a; 作业要求&#xff1a; 1) 学生自行…

invoke 按钮点击_h5+ app内点击按钮实现复制功能 实现方法

在项目中由于要实现点击按钮复制功能&#xff0c;我用的是H5APP&#xff0c;实现代码如下(包括安卓IOS)&#xff1a;/*** //参数copy是要复制的文本内容* tip 复制成功后的提示语&#xff0c;可空*/function copy_fun(copy,tips) {if(!tips){tips"已成功复制到剪贴板"…

机器学习平台建设

本文从机器学习平台的架构开始&#xff0c;再到具体的功能&#xff0c;然后从需求的角度带给读者思考&#xff0c;找到合适的机器学习平台建设之路。最后&#xff0c;推荐了微软开源开放的机器学习平台OpenPAI&#xff0c;是可私有部署的机器学习训练平台。 本文不少要点都可以…

型管件的作用_管道工程基础 - 管件和管道附件的布置规定

概述1.1 管件的用途1.2 管件的种类根据管件的端部连接形式可将管件分为对焊连接管件、承插焊连接管件、螺纹连接管件、法兰连接管件以及其它管件。管件和管道附件的布置2.1管件的布置(1)弯头宜选用曲率半径等于1.5倍公称直径的长半径弯头&#xff1b;输送气固、液固两相流物料的…

现代软件工程 作业汇总

文档属于这个专栏&#xff1a; ​​​​​​现代软件工程讲义 目录_SoftwareTeacher的博客-CSDN博客 备份&#xff1a;现代软件工程课程 参考&#xff1a;各个学校的编程/软工作业列表 ------- 软件工程的作业 ------- 很多老师反映软件工程的作业题不好出&#xff0c;学生做的…

现代软件工程 作业 第一周博客作业

(软件工程作业汇总) 第一周的博客作业 (这是历届学生的练习中&#xff0c; 从各个老师的作业中汇集而来&#xff0c;特别是北航的作业&#xff0c;东北师大的作业) 1. 介绍自己&#xff0c;建博客 在 http://www.csdn.net 建账号。关注老师和助教&#xff0c;关注一些和你一起…

java grpc 客户端处理 go 服务端多返回值_grpc基础实践(二)

在此篇中我们将简要介绍关于grpc对java客户端的实现。在开始开发前&#xff0c;我们需要先导入io.grpc grpc-netty 1.11.0io.grpc grpc-protobuf 1.11.0io.grpc grpc-stub 1.11.0如果是Android除了这几个包外&#xff0c;你可能还需要一个javax.annotation:javax.annotation-ap…

现代软件工程 作业 团队第一个作业

这是现代软件工程作业系列的一部分 1. 团队成员介绍 介绍每个成员(照片&#xff0c;主页&#xff0c;技术特长&#xff0c;在团队中担任的角色)。 建议拍一张有创意的合影。 在介绍的时候&#xff0c;可以采用艺术照等形式&#xff0c; 保护同学的隐私&#xff0c;不想说明真实…

asp.net 文本框显示xml格式数据_Excel 办公小技巧,查找和替换数据,您值得拥有...

在数据处理的过程中&#xff0c;有时需要在工作表中查找一些数据&#xff0c;以便查看或修改数据。若工作表的数据能一目了然&#xff0c;则可手动进行查找&#xff0c;但在记录繁多的工作表中查找所需数据并替换&#xff0c;效率比较低&#xff0c;而且极易出现遗漏。这时就可…

webpack打包后的文件夹是空的_vue+webpack 打包文件 404 页面空白的解决方法

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云文件存储NAS是一个可共享访问&#xf…

微软认知服务应用秘籍 – 与机器人聊知识

在本篇博客中&#xff0c;我们将会学习到零代码情况下&#xff0c;如何利用已有的技术建立自己的知识问答系统&#xff0c;这种系统的可以广泛适用于学校、企业、客服、政府公开信息等领域&#xff0c;代替传统的电话咨询、电子邮件沟通等高人工负荷的方式。 建立知识库 什么…

.net 开发怎么实现前后端分离_ASP.NET Core模块化前后端分离快速开发框架介绍

源码地址GitHub&#xff1a;https://github.com/iamoldli/NetModular演示地址地址&#xff1a;http://118.24.75.170:6220/账户&#xff1a;admin密码&#xff1a;admin前端框架演示地址(临时)地址&#xff1a;http://progqx5cu.bkt.clouddn.com/skins/index.html#/账户&#x…