• 【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会变回空白。

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

    示例源代码下载

  • 相关阅读:
    mysql数据库-完整性约束
    mysql数据库-常用数据类型2
    mysql数据库--常用数据类型
    mysql数据库--表的操作
    Django学习【第18篇】:Django之缓存
    Django学习【第17篇】:Django之信号
    Django学习【第16篇】:Django之Form组件自定义验证规则
    Django学习【第15篇】:Django之Form组件归类
    Django学习【第14篇】:Django之Form组件补充
    Django学习【第13篇】:Django之Form组件
  • 原文地址:https://www.cnblogs.com/tcjiaan/p/5809895.html
Copyright © 2020-2023  润新知