AI应用开发实战 - 手写识别应用入门
AI應(yīng)用開發(fā)實(shí)戰(zhàn) - 手寫識(shí)別應(yīng)用入門
手寫體識(shí)別的應(yīng)用已經(jīng)非常流行了,如輸入法,圖片中的文字識(shí)別等。但對(duì)于大多數(shù)開發(fā)人員來說,如何實(shí)現(xiàn)這樣的一個(gè)應(yīng)用,還是會(huì)感覺無從下手。本文從簡(jiǎn)單的MNIST訓(xùn)練出來的模型開始,和大家一起入門手寫體識(shí)別。
在本教程結(jié)束后,會(huì)得到一個(gè)能用的AI應(yīng)用,也許是你的第一個(gè)AI應(yīng)用。雖然離實(shí)際使用還有較大的距離(具體差距在文章后面會(huì)分析),但會(huì)讓你對(duì)AI應(yīng)用有一個(gè)初步的認(rèn)識(shí),有能力逐步搭建出能夠?qū)嶋H應(yīng)用的模型。
建議和反饋,請(qǐng)發(fā)送到
https://github.com/Microsoft/vs-tools-for-ai/issues
聯(lián)系我們
OpenmindChina@microsoft.com
準(zhǔn)備工作
- 使用win10 64位操作系統(tǒng)的計(jì)算機(jī)
- 參考上一篇博客AI應(yīng)用開發(fā)實(shí)戰(zhàn) - 從零開始配置環(huán)境。在電腦上訓(xùn)練并導(dǎo)出MNIST模型。
一、 思路
通過上一篇文章搭建環(huán)境的介紹后,就能得到一個(gè)能識(shí)別單個(gè)手寫數(shù)字的模型了,并且識(shí)別的準(zhǔn)確度會(huì)在98%,甚至99%以上了。那么我們要怎么使用這個(gè)模型來搭建應(yīng)用呢?
大致的步驟如下:
是不是很簡(jiǎn)單?
二、動(dòng)手
步驟一:獲取手寫的數(shù)字
提問:那我們要怎么獲取手寫的數(shù)字呢?
回答:我們可以寫一個(gè)簡(jiǎn)單的WinForm畫圖程序,讓我們可以用鼠標(biāo)手寫數(shù)字,然后把圖片保存下來。
首先,我們打開Visual Studio,選擇文件->新建->項(xiàng)目。
在彈出的窗口里選擇Visual C#->Windows窗體應(yīng)用,項(xiàng)目名稱不妨叫做DrawDigit,解決方案名稱不妨叫做MnistForm,點(diǎn)擊確定。
此時(shí),Visual Studio也自動(dòng)彈出了一個(gè)窗口的設(shè)計(jì)圖。
在DrawDigit項(xiàng)目上點(diǎn)擊右鍵,選擇屬性,在生成一欄將平臺(tái)目標(biāo)從Any CPU改為x64。
否則,DrawDigit(首選32位)與它引用的MnistForm(64位)的編譯平臺(tái)不一致會(huì)引發(fā)System.BadImageFormatException的異常。
然后我們對(duì)這個(gè)窗口做一些簡(jiǎn)單的修改:
首先我們打開VS窗口左側(cè)的工具箱,這個(gè)窗口程序需要以下三種組件:
那經(jīng)過一些簡(jiǎn)單的選擇與拖動(dòng)還有調(diào)整大小,這個(gè)窗口現(xiàn)在是這樣的:
一些注意事項(xiàng)
經(jīng)過一些簡(jiǎn)單的調(diào)整,這個(gè)窗口現(xiàn)在是這樣的:
現(xiàn)在來讓我們愉快地給這些組件添加事件!
還是在屬性窗口,我們選擇某個(gè)組件,右鍵->查看屬性,點(diǎn)擊閃電符號(hào),給組件綁定對(duì)應(yīng)的事件。每次綁定后,會(huì)跳到代碼部分,生成一個(gè)空函數(shù)。點(diǎn)回設(shè)計(jì)視圖繼續(xù)操作即可。
| pictureBox1 | 在Mouse下雙擊MouseDown、MouseUp、MouseMove來生成對(duì)應(yīng)的響應(yīng)事件函數(shù)。 |
| button1 | 如上,在Action下雙擊Click。 |
| Form1 | 如上,在Behavior下雙擊Load。 |
然后我們開始補(bǔ)全對(duì)應(yīng)的函數(shù)體內(nèi)容。
注意,如果在上面改變了控件的名稱,下面的代碼需要做對(duì)應(yīng)的更改。
廢話少說上代碼!
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D;//用于優(yōu)化繪制的結(jié)果 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;//用來保存手寫數(shù)字private Point startPoint;//用于繪制線段,作為線段的初始端點(diǎn)坐標(biāo)private Mnist model;//用于識(shí)別手寫數(shù)字private const int MnistImageSize = 28;//Mnist模型所需的輸入圖片大小private void Form1_Load(object sender, EventArgs e){//當(dāng)窗口加載時(shí),繪制一個(gè)白色方框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){//當(dāng)點(diǎn)擊清除時(shí),重新繪制一個(gè)白色方框,同時(shí)清除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){//當(dāng)鼠標(biāo)左鍵被按下時(shí),設(shè)置isPainting為true,并記錄下需要繪制的線段的起始坐標(biāo)startPoint = (e.Button == MouseButtons.Left) ? e.Location : startPoint;}private void pictureBox1_MouseMove(object sender, MouseEventArgs e){//當(dāng)鼠標(biāo)在移動(dòng),且當(dāng)前處于繪制狀態(tài)時(shí),根據(jù)鼠標(biāo)的實(shí)時(shí)位置與記錄的起始坐標(biāo)繪制線段,同時(shí)更新需要繪制的線段的起始坐標(biāo)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){//當(dāng)鼠標(biāo)左鍵釋放時(shí)//同時(shí)開始處理圖片進(jìn)行推理//暫時(shí)不處理這里的代碼}} }步驟二:把模型包裝成一個(gè)類
將模型包裝成一個(gè)C#是整個(gè)過程中比較麻煩的一步。所幸的是,Tools for AI對(duì)此提供了很好的支持。進(jìn)一步了解,可以看這里。
首先,我們?cè)诮鉀Q方案MnistForm下點(diǎn)擊鼠標(biāo)右鍵,選擇添加->新建項(xiàng)目,在彈出的窗口里選擇AI Tools->Inference->模型推理類庫(kù),名稱不妨叫做MnistModel,點(diǎn)擊確定,于是我們又多了一個(gè)項(xiàng)目,
然后自己配置好這個(gè)項(xiàng)目的名稱、位置,點(diǎn)擊確定。
然后彈出一個(gè)模型推理類庫(kù)創(chuàng)建向?qū)?#xff0c;這個(gè)時(shí)候就需要我們選擇自己之前訓(xùn)練好的模型了~
首先在模型路徑里選擇保存的模型文件的路徑。這里我們使用在AI應(yīng)用開發(fā)實(shí)戰(zhàn) - 從零開始配置環(huán)境博客中訓(xùn)練并導(dǎo)出的模型
note:模型可在/samples-for-ai/examples/tensorflow/MNIST目錄下找到,其中output文件夾保存了檢查點(diǎn)文件,export文件夾保存了模型文件。
對(duì)于TensorFlow,我們可以選擇檢查點(diǎn)的.meta文件,或者是保存的模型的.pb文件
這里我們選擇在AI應(yīng)用開發(fā)實(shí)戰(zhàn) - 從零開始配置環(huán)境這篇博客最后生成的export目錄下的檢查點(diǎn)的SavedModel.pb文件,這時(shí)程序?qū)⒆詣?dòng)配置好配置推理接口,見下圖:
類名可以自己定義,因?yàn)槲覀冇玫氖荕NIST,那么類名就叫Mnist好了,然后點(diǎn)擊確定。
這樣,在解決方案資源管理器里,在解決方案MnistForm下,就多了一個(gè)MnistModel:
雙擊Mnist.cs,我們可以看到項(xiàng)目自動(dòng)把模型進(jìn)行了封裝,生成了一個(gè)公開的infer函數(shù)。
然后我們?cè)贛nistModel上右擊,再選擇生成,等待一會(huì),這個(gè)項(xiàng)目就可以使用了~
步驟三:連接兩個(gè)部分
這一步差不多就是這么個(gè)感覺:
I have an apple , I have a pen. AH~ , Applepen
首先,我們來給DrawDigit添加引用,讓它能使用MnistModel。在DrawDigit項(xiàng)目的引用上點(diǎn)擊鼠標(biāo)右鍵,點(diǎn)擊添加引用,在彈出的窗口中選擇MnistModel,點(diǎn)擊確定。
然后,由于MNIST的模型的輸入是一個(gè)28×28的白字黑底的灰度圖,因此我們首先要對(duì)圖片進(jìn)行一些處理。
首先將圖片轉(zhuǎn)為28×28的大小。
然后將RGB圖片轉(zhuǎn)化為灰階圖,將灰階標(biāo)準(zhǔn)化到[-0.5,0.5]區(qū)間內(nèi),轉(zhuǎn)換為黑底白字。
最后將圖片用mnist模型要求的格式包裝起來,并傳送給它進(jìn)行推理。
于是,我們?cè)趐ictureBox1_MouseUp中添加上這些代碼,并且在文件最初添加上using MnistModel;:
最后讓我們嘗試一下運(yùn)行~
三、效果展示
現(xiàn)在我們就有了一個(gè)簡(jiǎn)單的小程序,可以識(shí)別手寫的數(shù)字了。
趕緊試試效果怎么樣~
注意
擴(kuò)展
嘗試識(shí)別多個(gè)數(shù)字
我們已經(jīng)支持了單個(gè)手寫數(shù)字的識(shí)別,那能不能支持多個(gè)手寫數(shù)字的識(shí)別呢?同時(shí)寫下多個(gè)數(shù)字,正是現(xiàn)實(shí)中更為常見的情形。相比之下,如果只能一次識(shí)別一個(gè)手寫數(shù)字,應(yīng)用就會(huì)有比較大的局限性。
首先,我們可以嘗試在現(xiàn)有的應(yīng)用里一次寫下兩個(gè)數(shù)字,看看識(shí)別效果(為了更好的展示效果,將筆畫的寬度由40調(diào)整為20。這一改動(dòng)對(duì)單個(gè)數(shù)字的識(shí)別并無大的影響):
識(shí)別效果不盡人意。
右上角展示的結(jié)果準(zhǔn)確地反應(yīng)了模型對(duì)我們手寫輸入的推理結(jié)果(即result.First().First().ToString()),然而這一結(jié)果并不像我們期望的那樣是“42”。
了解MNIST數(shù)據(jù)集的讀者們可能已經(jīng)意識(shí)到了,這是“理所當(dāng)然”的。歸根結(jié)底,這一問題的癥結(jié)在于:作為我們AI應(yīng)用核心的AI模型,本身并不具備識(shí)別多個(gè)數(shù)字的能力——當(dāng)前案例中我們使用的AI模型是基于MNIST數(shù)據(jù)集訓(xùn)練的(訓(xùn)練過程請(qǐng)回顧我們之前的博客AI應(yīng)用開發(fā)實(shí)戰(zhàn) - 從零開始配置環(huán)境),而MNIST數(shù)據(jù)集只覆蓋了單個(gè)的手寫數(shù)字;并且,我們并未對(duì)筆跡圖形作額外的處理。
結(jié)果是在寫下多個(gè)數(shù)字的情況下,我們實(shí)際上在“強(qiáng)行”讓AI模型做超出其適應(yīng)性范圍的判斷。這屬于AI模型的誤用。其結(jié)果自然難以令人滿意。
那么,為了增強(qiáng)應(yīng)用的可用性,我們能不能改善它、讓它能識(shí)別多個(gè)數(shù)字呢?我們很自然地想到,既然MNIST模型已經(jīng)能很好地識(shí)別單個(gè)數(shù)字,那我們只需要把多個(gè)數(shù)字分開,一個(gè)一個(gè)地讓MNIST模型進(jìn)行識(shí)別就好了。這樣,我們就引入了一個(gè)新的子問題,即是“多個(gè)手寫數(shù)字的分割”。
子問題:分割多個(gè)手寫數(shù)字
我們注意到本文介紹的應(yīng)用有一個(gè)特點(diǎn),那就是最終用作輸入的圖形,是用戶當(dāng)場(chǎng)寫下的,而非通過圖片文件導(dǎo)入的靜態(tài)圖片,即我們擁有筆畫產(chǎn)生過程中的全部動(dòng)態(tài)信息,比如筆畫的先后順序,筆畫的重疊關(guān)系等等。考慮到這些信息,我們可以設(shè)計(jì)一種基本的分割規(guī)則:在水平面上的投影相重疊的筆畫,我們就認(rèn)為它們同屬于一個(gè)數(shù)字。
筆畫和水平方向上投影的關(guān)系示意如下圖:
因此書寫時(shí),就要求不同的數(shù)字之間盡量隔開。當(dāng)然為了盡可能處理不經(jīng)意的重疊,我們還可以為重疊部分相對(duì)每一筆畫的位置設(shè)定一個(gè)閾值,如至少進(jìn)入筆畫一端的10%以內(nèi)。
應(yīng)用這樣的規(guī)則后,我們就能比較好的把多個(gè)手寫數(shù)字分割開,并能利用Visual Studio Tools for AI提供的批量推理功能,一次性對(duì)所有分割出的圖形做推理。
多個(gè)手寫數(shù)字識(shí)別的最終效果如圖:
當(dāng)然,我們對(duì)問題的定義還是非常理想化,分割算法也比較簡(jiǎn)單。在實(shí)際應(yīng)用中,我們還經(jīng)常要考慮非二值圖形、噪點(diǎn)、非數(shù)字的判別等等。并且對(duì)手寫數(shù)字的分割可能比我們?cè)O(shè)定的規(guī)則要復(fù)雜,因?yàn)樵诂F(xiàn)實(shí)場(chǎng)景中,水平方向上的重疊可能會(huì)影響圖形的涵義。
將兩個(gè)手寫數(shù)字分割開這一問題,實(shí)際上和經(jīng)典的圖像分割問題非常類似。雖然本文示例中的圖像非常簡(jiǎn)單,但仍然可能具有相當(dāng)復(fù)雜的語(yǔ)義需要處理。為此,我們可能需要引入更多的模型,或者擴(kuò)展現(xiàn)有的模型來正確判斷多個(gè)圖形之間的關(guān)系。
進(jìn)階
那么,如果要識(shí)別多個(gè)連寫的數(shù)字,或支持字母該怎么做呢?大家多用用也會(huì)發(fā)現(xiàn),如果數(shù)字寫得很小,或者沒寫到正中,識(shí)別起來正確率也會(huì)不高。要解決這些問題,做成真正的產(chǎn)品,就不止這一個(gè)模型了。比如在多個(gè)數(shù)字識(shí)別中,可能要根據(jù)經(jīng)驗(yàn)來切分圖,或者訓(xùn)練另一個(gè)模型來檢測(cè)并分割數(shù)字。要支持字母,則需要重新訓(xùn)練一個(gè)包含手寫字母的模型,并準(zhǔn)備更多的字母的數(shù)據(jù)。要解決字太小的問題,還要檢測(cè)一下字的大小,做合適的放大等等。
我們可以看到,一個(gè)訓(xùn)練出來的模型本身到一個(gè)實(shí)際的應(yīng)用之間還有不少的功能要實(shí)現(xiàn)。希望我們這一系列的介紹,能夠幫助大家將機(jī)器學(xué)習(xí)的概念帶入到傳統(tǒng)的編程領(lǐng)域中,做出更聰明的產(chǎn)品。
轉(zhuǎn)載于:https://www.cnblogs.com/ms-uap/p/9182530.html
總結(jié)
以上是生活随笔為你收集整理的AI应用开发实战 - 手写识别应用入门的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: get_called_class与get
- 下一篇: DeepLearning.AI笔记:二、