【Win 10 應用開發】手寫識別


記得前面(忘了是哪天寫的,反正是前些天,請用力點擊這里觀看)老周講了一個14393新增的控件,可以很輕松地結合InkCanvas來完成塗鴉。其實,InkCanvas除了塗鴉外,另一個大用途是墨跡識別,就是手寫識別。

識別功能早在Win 8 App的API中就有了,到了UWP,同樣使用,這叫傳承,一路學過來,都是一個體系的,我不明白為什么某些人一遇到升級就說SDK變化太大,適應不了。我是不明白了,有什么適應不了的,該不會是你笨吧,或者學習方法不對。反正老周在以前的博客中都說過了,學習要學活,不要把知識學死了,把東西往死里學,就是古人所說的書呆子。

好了,不談論書呆子的事了,因為“書呆子”在民間有太多的誤解,咱們還是說正題。

處理數字墨跡有兩種方式:

1、一種是脫離InkCanvas控件的方法,處理過程是面向筆觸(Stroke)的,這就需要你手動去管理好你的墨跡數據了;

2、要是上一種方法太麻煩,與InkCanvas關聯的做法較好,這樣不用自己去搞UI部分的內容。

 

本着易用、久用、耐用、實用、妙用等偉大原則,我們實現手寫識別還是不要脫離InkCanvas控件,這樣的話實現起來會輕松很多,除非你要搞很高級的應用場景。

不講過多的理論,免得大家看的頭暈,老周簡單說一個原理,大家懂了原理后,直接干活,這是學編程的萬能招數。

先看看大致的步驟:

1、大家知道,InkCanvas有個關聯的InkPresenter屬性,引用的是InkPresenter實例,這個你得知道,不然后面的步驟就無法玩了。

2、InkPresenter類有個StrokeContainer屬性,類型為InkStrokeContainer,它表示墨跡筆觸的集合,被收集到的輸入數據就存放到這個集合中。一個筆觸通常是指你用筆/手指/鼠標按下時開始,直到你釋放筆/手指/鼠標這一階段中,所繪制出來的一段墨跡(從下筆到提筆)。一花一世界,一落一起一筆觸。

3、實例化InkRecognizerContainer類,調用RecognizeAsync方法執行識別,上面為啥要提到InkStrokeContainer呢?因為執行識別需要它,你想啊,沒有用戶輸入的墨跡(筆觸)數據,一片空白,你識別個球。

4、識別后返回一個InkRecognitionResult列表,對於中文,通常只有一個InkRecognitionResult對象,但對於英文單詞,可能會多個,一個InkRecognitionResult表示一個單詞。對於一個InkRecognitionResult來說,訪問GetTextCandidates方法返回一個字符串列表,即候選項,匹配度高的字符串排在前面。

5、也可以訪問InkRecognizerContainer.GetRecognizers方法獲取當前系統中已安裝的語言識別引擎,中文系統至少會有一個簡體中文的識別引擎。你可以到系統設置里面安裝其他語言的引擎。

 

OK,基本思路有了,下面就可以做事情了。

首先,布置一下UI,XAML代碼如下:

        <Grid Margin="15">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="300"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <ComboBox Name="cmbRecons" Header="選一個:" DisplayMemberPath="Name"/>
            <Border Background="LightGray" Grid.Row="1" Margin="2,6">
                <InkCanvas Name="inkcv" />
            </Border>
            <TextBlock Grid.Row="2" Name="tbresult" TextWrapping="Wrap" Foreground="Red" FontSize="24"/>
        </Grid>

 

ComboBox控件用來顯示當前系統中安裝的手寫識別引擎,TextBlock用來顯示識別結果。

 

現在,切換到代碼視圖,首先在頁面類級別聲明一個InkRecognizerContainer變量,並且實例化。

        InkRecognizerContainer inkRecognContainer = new InkRecognizerContainer();

 

另外,還需要一個Timer,作用是在墨跡收集2秒鍾后進行識別。

        DispatcherTimer timer = new DispatcherTimer();

       ……


            // 准備計時器
            // 延遲2秒,應該不算慢吧
            timer.Interval = TimeSpan.FromSeconds(2d);
            timer.Tick += onTimerTick;
            // 處理ink操作事件
            inkcv.InkPresenter.StrokeInput.StrokeStarted += (k1, k2) =>
            {
                // 人家正要下筆呢,沒有在此時識別的道理
                timer.Stop();
            };
            inkcv.InkPresenter.StrokesCollected += (t1, t2) =>
            {
                // 墨跡已收集,可以進行識別
                timer.Start();
            };

 

當下筆開始書寫時,會發生StrokeStarted事件,在此時,應該停止計時,你總不能人家一邊寫你就一邊識別,沒什么意思。但InkCanvas收集到輸入筆觸后,會發生StrokesCollected事件,這時候就可以開始計時了,2秒鍾后進行識別。說白了就是在用戶停止手寫2秒鍾后識別。

 

在ComboBox控件中顯示系統已安裝的識別引擎:

            // 獲取已安裝的識別引擎列表
            var inkrecogs = inkRecognContainer.GetRecognizers();
            // 將這些列表顯示到ComboBox控件中
            cmbRecons.ItemsSource = inkrecogs;
            // 處理選項更改事件
            cmbRecons.SelectionChanged += (s1, s2) =>
            {
                // 將選中的識別引擎設為默認
                InkRecognizer currec = (InkRecognizer)cmbRecons.SelectedItem;
                inkRecognContainer.SetDefaultRecognizer(currec);
            };
            if (cmbRecons.Items.Count > 0)
                cmbRecons.SelectedIndex = 0;

 當ComboBox控件做出選擇后,引發SelectionChanged事件,在事件處理代碼中可以調用SetDefaultRecognizer方法設置默認的識別引擎。

 

還有一件事,不要忘了,讓InkCanvas支持筆、手觸、鼠標來書寫。

            // 全能書寫
            inkcv.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Touch | Windows.UI.Core.CoreInputDeviceTypes.Pen;

 

下面是核心代碼,就是上面那個Timer的Tick事件處理,在處理代碼中,執行手寫識別,並顯示識別的結果。

            // 如果InkStrokeContainer中沒有收集筆觸,那就沒有識別的必要了
            // 所以Count應大於0
            if (inkcv.InkPresenter.StrokeContainer.GetStrokes().Count > 0)
            {
                IReadOnlyList<InkRecognitionResult> results = await inkRecognContainer.RecognizeAsync(inkcv.InkPresenter.StrokeContainer, InkRecognitionTarget.All);
                // 處理結果
                if (results.Count > 0)
                {
                    StringBuilder strbd = new StringBuilder();
                    strbd.AppendLine("結果:");
                    // 每個InkRecognitionResult實例表示一個漢字/單詞的識別結果
                    // 而單個結果中又包含候選列表,最接近的識別結果優先級更高
                    for(int x = 0; x < results.Count; x++)
                    {
                        string s = string.Join("", results[x].GetTextCandidates().ToArray());
                        strbd.AppendLine(s);
                    }
                    // 顯示結果
                    tbresult.Text = strbd.ToString();
                    // 清理墨跡
                    inkcv.InkPresenter.StrokeContainer.Clear();
                }
            }

 

不是很復雜,代碼你應該看得懂的,不然,學.NET這么多年,太對不起自己了。注意的是,識別后返回多個結果,對於中文,通常只返回一個,因為多個漢字是可以一起識別,並放到字符候選列表中。

在代碼的最后面有這么一句:

 inkcv.InkPresenter.StrokeContainer.Clear();

這句代碼的作用是清除所收集的所有墨跡,清除后,InkCanvas會變回空白。

 

運行一下程序,然后手寫一些字,看看識別效果。

 

示例源代碼下載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM