• Azure认知服务之使用墨迹识别功能识别手写汉字


    前面我们使用Azure Face实现了人脸识别、使用Azure表格识别器提取了表格里的数据。这次我们试试使用Azure墨迹识别API来对笔迹进行识别。

    墨迹识别

    墨迹识别器认知服务提供基于云的 REST API 用于分析和识别数字墨迹内容。 与使用光学字符识别 (OCR) 的服务不同,该 API 需要使用数字墨迹笔划数据作为输入。 数字墨迹笔划是 2D 点(X,Y 坐标,表示数字手写笔或手指的动作)的时序集。 然后,墨迹识别器会识别输入中的形状和手写内容,并返回包含所有已识别实体的 JSON 响应。

    引用自微软文档

    它不是ocr对图像进行识别,而是对墨迹数据进行识别。墨迹数据的原理主要是一些手写输入设备,比如平板,手写板等。

    创建墨迹识别资源

    跟前面的内容一样,在portal控制台找到墨迹识别功能,点击创建,取一个实例名。墨迹识别也是一个免费服务,定价选F0方案,额度为5次/分,20000事务/月。
    d8uQJI.png

    获取秘钥和终结点

    我们调用墨迹识别API需要秘钥跟终结点信息。点击菜单“密钥和终结点”查看信息。
    d8ulWt.png

    新建一个WPF项目

    我们这次同样实现一个WPF小程序。界面上放置一个InkCanvas用来手写,一个文本框用来显示识别的文本,一个按钮用来触发识别。
    d31qhj.png

    MainWindow.xaml

    修改MainWindow.xaml为如下代码:

    <Window x:Class="InkRec2.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            xmlns:local="clr-namespace:NoteTaker"
            xmlns:controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls"
            Title="MainWindow">
    
        <Grid >
            <Grid.RowDefinitions>
                <RowDefinition Height="4*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="50" />
    
            </Grid.RowDefinitions>
            <Border Grid.Row ="0" BorderBrush="Black" BorderThickness="1">
                <controls:InkCanvas x:Name="inkCanvas" Loaded="inkCanvas_Loaded"/>
            </Border>
            <Border Grid.Row ="1" BorderBrush="Black" BorderThickness="1">
                <ScrollViewer>
                    <TextBox x:Name="output" FontSize="18" TextWrapping="Wrap"/>
                </ScrollViewer>
            </Border>
            <StackPanel Grid.Row="2" Orientation="Horizontal">
                <Button Click="Button_InkRec">开始识别</Button>
            </StackPanel>
        </Grid>
    </Window>
    
    

    注意:InkCanvas控件需要使用的是Microsoft.Toolkit.Wpf.UI.Controls包下的,如果本地没有使用nuget进行安装

    采集墨迹

    inkCanvas load事件里设置输入设备的类型:

       private void inkCanvas_Loaded(object sender, RoutedEventArgs e)
            {
                inkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Touch;
            }
    
    

    先定义几个模型用来存储墨迹数据:

    
        public class InkStroke
        {
            public int id { get; set; }
    
            public string points { get; set; }
        }
    
        public class InkData
        {
            public string language { get; set; }
    
            public List<InkStroke> strokes { get; set; }
        }
    
    

    从InkCanvas获取墨迹数据组装成InkData:

            private InkData GetInkData()
            {
                var data = new InkData();
                data.language = "zh-CN";
                data.strokes = new List<InkStroke>();
    
                int id = 0;
                foreach (var stroke in this.inkCanvas.InkPresenter.StrokeContainer.GetStrokes())
                {
                    var points = stroke.GetInkPoints();
    
                    var convertPoints = ConvertPixelsToMillimeters(points);
    
                    var inkStorke = new InkStroke();
                    inkStorke.id = id++;
    
                    var sb = new StringBuilder();
                    foreach (var point in convertPoints)
                    {
                        sb.Append(point.X);
                        sb.Append(",");
                        sb.Append(point.Y);
                        sb.Append(",");
                    }
                    inkStorke.points = sb.ToString().TrimEnd(',');
    
                    data.strokes.Add(inkStorke);
                }
    
                return data;
            }
                    private List<System.Windows.Point> ConvertPixelsToMillimeters(IReadOnlyList<InkPoint> pointsInPixels)
            {
                float dpiX = 96.0f;
                float dpiY = 96.0f;
                var transformedInkPoints = new List<System.Windows.Point>();
                const float inchToMillimeterFactor = 25.4f;
    
    
                foreach (var point in pointsInPixels)
                {
                    var transformedX = (point.Position.X / dpiX) * inchToMillimeterFactor;
                    var transformedY = (point.Position.Y / dpiY) * inchToMillimeterFactor;
    
                    transformedInkPoints.Add(new System.Windows.Point(transformedX, transformedY));
                }
    
                return transformedInkPoints;
            }
    

    调用墨迹API

    这里需要前面复制好的密钥跟终结点地址。识别其实很简单,就是把墨迹数据转换成json后给服务器发生一个put请求,识别成功后就会返回一个json字符串的结果。

            private async Task<string> InkRec(InkData data)
            {
                string inkRecognitionUrl = "/inkrecognizer/v1.0-preview/recognize";
                string endPoint = "x";
                string subscriptionKey = "x";
    
                using (HttpClient client = new HttpClient { BaseAddress = new Uri(endPoint) })
                {
                    System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
                    var jsonData = JsonConvert.SerializeObject(data);
                    var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
                    var res = await client.PutAsync(inkRecognitionUrl, content);
                    if (res.IsSuccessStatusCode)
                    {
                        var result = await res.Content.ReadAsStringAsync();
    
                        return result;
                    }
                    else
                    {
                        var err = $"ErrorCode: {res.StatusCode}";
    
                        return err;
                    }
                }
            }
    

    解析识别结果

    识别成功后,结果会以json字符串的形式进行返回。结果是一个数组,里面存放了每一个笔迹的识别结果,以及最终的识别结果。
    结果示例:

    {"recognitionUnits":[{"alternates":[{"category":"inkWord","recognizedString":"乖"},{"category":"inkWord","recognizedString":"黍"},{"category":"inkWord","recognizedString":"秉"},{"category":"inkWord","recognizedString":"乗"},{"category":"inkWord","recognizedString":"埀"}],"boundingRectangle":{"height":48.159999847412109,"topX":7.190000057220459,"topY":22.010000228881836,"width":35.639999389648438},"category":"inkWord","class":"leaf","id":4,"parentId":3,"recognizedText":"乘","rotatedBoundingRectangle":[{"x":41.490001678466797,"y":21.25},{"x":43.209999084472656,"y":69.239997863769531},{"x":7.8299999237060547,"y":70.5},{"x":6.1100001335144043,"y":22.520000457763672}],"strokeIds":[0,1,2,3,4,5,6,7,8,9]},{"alternates":[{"category":"inkWord","recognizedString":"風"},{"category":"inkWord","recognizedString":"夙"},{"category":"inkWord","recognizedString":"凤"},{"category":"inkWord","recognizedString":"凡"},{"category":"inkWord","recognizedString":"㶡"}],"boundingRectangle":{"height":32."class":"leaf","id":8,"parent
    ...
    

    那么我们只要对其进行反序列化取出想要的识别结果就行了。

        public class InkRecResponse
        {
            public List<InkRecResponseUnit> recognitionUnits { get; set; }
        }
    
        public class InkRecResponseUnit
        {
            public string category { get; set; }
    
            public string recognizedText { get; set; }
        }
       private async void Button_InkRec(object sender, RoutedEventArgs e)
            {
                var inkData = GetInkData();
                var response = await InkRec(inkData);
    
                var jsonObj = JsonConvert.DeserializeObject<InkRecResponse>(response);
    
                var recognizedText = jsonObj.recognitionUnits.First(o => o.category == "line").recognizedText;
    
                this.output.Text = recognizedText;
            }
    

    运行一下

    我们的程序写好了,运行一下。在canvas上随便写上几个汉字点击识别按钮。字虽然丑了点,但是结果还是完美的。
    d31bNQ.png

    总结

    使用Azure墨迹识别可以轻松的识别手写输入设备的笔迹。墨迹识别功能并不是见到的orc识别,它可以对每一个笔画进行识别,提供候选结果。以上代码虽然多,其实主要是获取墨迹数据比较麻烦,其实真正识别墨迹只是一个http put请求而已,这是非常简单的。有了这个API我们可以实现很多创意,比如稍微改进下上面的代码就可以实现手写文字的连续识别功能,一边写一边不断的识别,封装进平板就是一款可以实时识别手写板啦。

    关注我的公众号一起玩转技术

  • 相关阅读:
    C#2.0新特性
    .NET Framework 4.5新特性
    C#4.0 中的新特性
    C#3.0中的新特性
    开始使用Mac OS X——写给Mac新人
    关于WF4 State Machine 状态机工作流的发布问题.
    C# 3.5新特性
    libssh2编译错误(configure error: cannot find OpenSSL or Libgcrypt)解决方法
    程序员转产品经理的思考
    升级PHP版本
  • 原文地址:https://www.cnblogs.com/kklldog/p/azure-inkrec.html
Copyright © 2020-2023  润新知