Visual Studio 完全AI手册 - 使用模型搭建应用

Visual Studio 完全AI手册 - 使用模型搭建应用

请听题:
当我们手握一个模型,我们可以:

A:拿模型来垫显示器?
B:把模型裱起来挂在墙上?
C:把模型拿去发朋友圈?
D:把模型拿去构建应用!

本教程的目的是将使用模型构建应用的过程压缩到极简,以便各位读者能够快速上手构建应用,在短期内构建信心。

零、前提条件

  • 一台电脑,使用win10 64位操作系统
  • 请确保鼠标、键盘、显示器都是好的
  • 阅读了第一篇博客,能够在电脑上训练MNIST模型

一、思路

经过漫长的训练之后,我们手上有一个模型,这个模型能够用来识别手写的单个数字(0-9),并且识别的准确度是99%,那么我们要怎么使用这个模型来搭建应用呢?

一个正常且合理的思路是:

  1. 把模型包装成一个类,规定好识别功能的输入与输出格式 (这是最难的一步)
  2. 写一些代码,这些代码能够以某种格式获取手写的数字
  3. 再写一些代码,把第二步获得的手写的数字处理成第一步规定的输入格式
  4. 最后获得输出,把输出以某种形式展示出来

是不是很简单?只比把大象关进冰箱里多了一步。

说干就干,首先找个你觉得舒服的地点和姿势,打开你的Visual Studio 2017,咱们马上进入动手环节。

二、动手

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

提问:我们要怎么把模型包装成一个类呢?

回答:Tools for AI早就给我们准备好了一个这样的工具,请看这个链接:
https://github.com/Microsoft/vs-tools-for-ai/blob/master/docs/zh-hans/docs/model-inference.md

首先,点击文件->新建->项目

在弹出的窗口左侧点击已安装->AI Tools->Inference,然后选择模型推理类库

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

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

首先在模型路径里选择保存的模型文件的路径。

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

这里我们选择在Visual Studio 完全AI手册 - 从0开始配置环境这篇博客最后生成的output目录下的检查点的model.ckpt-8000.meta文件,同时需要自己配置一下推理接口,点击推理接口旁的添加,按照下图修改接口,然后点击确定。

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

现在解决方案资源管理器里,在解决方案MnistForm下,就有了这样的一个东西:

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

然后我们在MnistForm下点击右键,点击生成,等待一会,这个项目就可以拿来用了~

步骤二:获取手写的数字

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

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

首先,我们在解决方案MnistForm下点击鼠标右键,选择添加->新建项目,在弹出的窗口里选择Visual C#->Windows窗体应用,名称不妨叫做DrawDigit,点击确定,于是我们又多了一个项目,Visual Studio也自动弹出了一个窗口的设计图。

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

首先我们打开VS窗口左侧的工具箱,这个窗口程序需要以下三种组件:

  1. PictureBox:用来手写数字,并且把数字保存成图片
  2. Label:用来显示模型的识别结果
  3. Button:我们需要两个,一个用来清理PictureBox的手写结果,另一个用来把当前的结果传递给模型进行识别

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

一些注意事项

  1. 这些组件都可以通过右键->查看属性,在属性里修改它们的设置
  2. 为了方便把PictureBox里的图片转化成Mnist能识别的格式,PictureBox的最好是一个正方形
  3. label默认是不能调整大小的,可以在属性->布局里修改AutoSizeFalse,并且在外观中修改TextAlignMiddleCenter,在Text里修改默认的"label1"" "
  4. 最好把这些组件的名字都修改好,当然也可以不改。。

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

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

还是在属性窗口,我们选择一个组件,右键->查看属性,点击闪电符号,给组键绑定对应的事件:

组件类型 事件
pictureBox1 鼠标下的MouseDown中输入picturebox1_MouseDown,点击回车
鼠标下的MouseUp中输入picturebox1_MouseUp,点击回车
鼠标下的MouseMove中输入picturebox1_MouseMove,点击回车
button1(用于清除) Click下输入clean_click,点击回车
button2(用于识别) Click下输入recognize_click,点击回车
Form1 行为load下输入Form1_load,点击回车

note:在开发的过程中请记得时不时Ctrl+S一下

添加完之后我们发现,VS给我们自动生成了几个空函数

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

然后我们开始补全对应的函数~废话少说上代码!

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 MnistForm; //引用MnistForm推理类库


namespace DrawDigit
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private Bitmap digitImage;//用来保存手写数字
        private int pbWidth;//手写数字图片的宽
        private int pbHeight;//手写数字图片的高
        private bool isPainting;//当前是否处于绘制状态,当鼠标左键被按下时,处于绘制状态
        private int currentX;//用于绘制线段,作为线段的初始端点x坐标
        private int currentY;//用于绘制线段,作为线段的初始端点y坐标
        private Mnist model;//用于识别手写数字
        private const int MnistImageSize = 28;//Mnist模型所需的输入图片大小
 
        private void Form1_Load(object sender, EventArgs e)
        {
            //当窗口加载时,绘制一个白色方框
            model = new Mnist();
            pbWidth = pictureBox1.Width;
            pbHeight = pictureBox1.Height;
            digitImage = new Bitmap(pbWidth, pbHeight);
            Graphics g = Graphics.FromImage(digitImage);
            g.Clear(Color.White);
            pictureBox1.Image = digitImage;
        }

        private void recognize_click(object sender, EventArgs e)
        {
            label1.Text = "";//清除label1上次显示的值
            Bitmap digitTmp = digitImage;
            //调整图片大小为Mnist模型可接收的大小:28*28
            using (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();
        }

        private void clean_click(object sender, EventArgs e)
        {
            //当点击清除时,重新绘制一个白色方框,同时清除label1显示的文本
            digitImage = new Bitmap(pbWidth, pbHeight);
            Graphics g = Graphics.FromImage(digitImage);
            g.Clear(Color.White);
            pictureBox1.Image = digitImage;
            label1.Text = "";
        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            //当鼠标左键被按下时,设置isPainting为true,并记录下需要绘制的线段的起始坐标
            if (e.Button == MouseButtons.Left)
            {
                isPainting = true;
                currentX = e.X;
                currentY = e.Y;
            }
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            //当鼠标在移动,且当前处于绘制状态时,根据鼠标的实时位置与记录的起始坐标绘制线段,同时更新需要绘制的线段的起始坐标
            if (isPainting == true)
            {
                Graphics g = Graphics.FromImage(digitImage);
                Pen myPen = new Pen(Color.Black, 40);
                myPen.StartCap = System.Drawing.Drawing2D.LineCap.Round;
                myPen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
                g.DrawLine(myPen, currentX, currentY, e.X, e.Y);
                pictureBox1.Image = digitImage;
                g.Dispose();
                currentX = e.X;
                currentY = e.Y;
            }
        }

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
        {
            //当鼠标左键没有被按下时,设置isPainting为false,同时归零起始坐标
            if (e.Button == MouseButtons.Left)
            {
                isPainting = false;
                currentX = 0;
                currentY = 0;
            }
        }
    }
}

步骤三:连接两个部分

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

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

这部分的代码请看上述代码中recognize_click函数中的代码。

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

三、可能出现的问题

BadImageFormatException

就像这样:

这时应该在DrawDigit项目上点击右键,选择属性,在生成一栏将平台目标中的首选32位复选框取消即可。

三、效果展示

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

赶紧试试效果怎么样~

猜你喜欢

转载自www.cnblogs.com/ms-uap/p/9182530.html