【Win 10 应用开发】手写识别

作者: 小麦

更新时间:2022-03-26 13:13:22

2655 阅读

记得前面(忘了是哪天写的,反正是前些天,请用力点击这里观看)老周讲了一个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会变回空白。

 

运行一下程序,然后手写一些字,看看识别效果。

 

示例源代码下载

 

版权声明:本文著作权归作者【小麦 】所有,不代表本网站立场。

侵权请联系:root_email@163.com