Transform Handwriting to Text with the Azure Ink Recognizer – Part 1

Spread the love

The Ink Recognizer is one of the new Cognitive Services announced and as mentioned in my previous post, Azure Ink Recognizer is an AI service in the Vision Category that recognizes digital ink content, such as handwriting, shapes, and ink document layout. In this three-part series, we are going to see how to use the Azure Ink Recognizer to transform handwriting to text. In this tutorial, we are going to familiarize ourselves with Azure Ink Recognizer and how to generate Azure Ink stroke data.

Prerequisites

To run the sample code you must have Visual Studio 2017 and above installed.

How it works

The Ink Recognizer API does not use Optical Character Recognition(OCR). OCR services process the pixel data from images to provide handwriting and text recognition, while the Ink Recognizer API uses digital ink stroke data to capture the complete the recognition. he Azure Ink Recognition Service API needs the linear coordinates (X, Y) when an ink stroke is drawn. Below you can see an example of stroke data.

{
  "language": "en-US",
  "strokes": [
   {
    "id": 43,
    "points": 
        "5.1365, 12.3845,
        4.9534, 12.1301,
        ...
        ..."
   },
    ...
  ]
}

You can see a ready sample from Microsoft’s Github here, or you can generate your own by building a small console application.

Create the Data Stroke Generator application

Create a new Windows Console Application (.NET Framework) in C#.

Add system references for the PresentationCore, the PresentationFramework, and WindowsBase.

Install NewtonSoft.Json from NuGet Package Manager

Insert the the following code to Program.cs

using Microsoft.Win32;
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Media;
using System.Windows.Media.Imaging;


namespace CodeStoriesStokeDataGenerator
{
    class Program : Application
    {
        static Window myWindow;
        static InkCanvas myInkCanvas;

        [STAThread]
        static void Main(string[] args)
        {
            new Program().Run();
            GenerateStrokes();
        }
        protected override void OnStartup(StartupEventArgs args)
        {
            base.OnStartup(args);

            myWindow = new Window();
            myInkCanvas = new InkCanvas();
            myWindow.Content = myInkCanvas;
            myWindow.Show();
        }

        public static bool GenerateStrokes()
        {
            SaveFileDialog mySaveFileDialog = new SaveFileDialog();
            mySaveFileDialog.Filter = "isf files (*.isf)|*.isf";

            if (mySaveFileDialog.ShowDialog() == true)
            {
                // Save the ink to a .bmp picture
                SaveAsBmp(mySaveFileDialog.FileName);
                // Save the strokes to a json file
                SaveAsJsonFile(mySaveFileDialog.FileName); 
            }
                       
            return true;
        }

        static void SaveAsBmp(string FilePathAndName)
        {
            int width = (int)myInkCanvas.ActualWidth;
            int height = (int)myInkCanvas.ActualHeight;
            RenderTargetBitmap myRenderBmp = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Default);
            myRenderBmp.Render(myInkCanvas);
            BmpBitmapEncoder myEncoder = new BmpBitmapEncoder();
            myEncoder.Frames.Add(BitmapFrame.Create(myRenderBmp));
            string bmpFileName = FilePathAndName.Replace(".isf", "_Ink.bmp");
            using (FileStream bmpFileStream = new FileStream(bmpFileName, FileMode.Create))
            {
                myEncoder.Save(bmpFileStream);
            }
        }

        static void SaveAsJsonFile(string FilePathAndName)
        {
            int intCounter = 1;
            string myStrokesJson = string.Empty;
            myStrokesJson = "{" +
                                "\"version\": 1, " +
                                "\"language\": \"en-US\", " +
                                "\"unit\": \"mm\", " +
                                "\"strokes\": [";
            foreach (Stroke oneStroke in myInkCanvas.Strokes)
            {
                string myPoints = string.Empty;
                foreach (Point onePoint in oneStroke.StylusPoints)
                {
                    myPoints += onePoint.X + "," + onePoint.Y + ",";
                }
                myPoints = myPoints.Remove(myPoints.Length - 1); 

                myStrokesJson += "{" +
                                    "\"id\": " + intCounter + "," +
                                    "\"points\": \"" +
                                    myPoints +
                                    "\"},";
                intCounter++;
            }
            myStrokesJson = myStrokesJson.Remove(myStrokesJson.Length - 1); 
            myStrokesJson += "]}";

            string jsonFileName = FilePathAndName.Replace(".isf", "_Ink.json");
            using (TextWriter writer = new StreamWriter(jsonFileName, true))
            {
                writer.Write(myStrokesJson);
            }
        }
    }
}

Run the program and a screen will popup. Draw anything you like with the mouse and close it. Choose a Save Location and the code will generate a bitmap of what you drew, along with the JSON file. This below is my result:

Image:

Stroke Data:

{
  "version": 1,
  "language": "en-US",
  "unit": "mm",
  "strokes": [{
    "id": 1,
    "points": "317,236,318,238,319,240,320,244,320,246,321,250,321,253,322,256,323,260,323,263,324,266,325,271,325,275,325,279,325,282,326,286,326,289,327,294,327,299,327,303,327,308,327,314,328,319,328,326,328,331,329,337,329,342,329,349,329,353,329,359,329,364,329,369,329,374,329,379,328,384,328,387,327,391,327,395,326,398,324,403,323,406,322,409,321,412,319,417,317,421,314,426,312,430,309,435,305,439,302,442,294,447,290,449,284,452,278,455,273,458,269,460,263,463,261,464,259,464,258,464,257,464,257,463,255,461,254,459,254,457,253,456,253,455"
  }, {
    "id": 2,
    "points": "399,241,399,245,399,249,399,253,400,258,400,263,400,269,400,274,400,280,400,285,400,290,400,296,400,303,400,308,400,315,401,322,402,328,403,335,404,341,405,345,405,351,406,355,407,361,408,366,409,371,409,374,409,377,410,380,411,382,412,385,412,388,414,391,415,394,416,397,416,399,416,401,417,402,418,404,418,406,418,407,418,409,418,411,419,412,419,415,419,417,420,419,420,421,420,423,420,424,420,425,420,426,420,427"
  }, {
    "id": 3,
    "points": "333,333,334,333,337,333,340,333,344,333,348,333,352,333,356,333,361,333,367,333,371,333,376,333,380,333,384,333,387,333,390,333,392,333,394,333,396,333,398,333,400,333,402,333,403,333,404,333,404,332,405,332,406,332,408,331,411,331,413,331,415,330,416,330,416,329"
  }, {
    "id": 4,
    "points": "474,396,476,396,478,396,481,396,484,396,489,396,494,396,501,395,508,394,515,392,520,390,527,387,536,384,543,382,550,379,556,376,559,374,561,373,564,370,565,369,567,366,568,363,568,360,569,359,569,356,569,354,569,352,569,350,569,348,568,346,567,345,566,343,564,342,564,341,563,341,562,341,561,341,560,341,559,341,557,341,554,341,551,341,545,342,539,345,533,347,530,348,527,349,525,350,523,351,521,352,520,354,517,356,515,357,514,361,512,363,509,367,508,371,506,375,504,378,503,383,503,386,503,390,503,393,503,396,503,398,503,401,504,404,505,407,506,410,508,413,510,416,511,418,512,419,513,421,516,422,518,424,521,424,524,425,528,426,535,427,541,427,546,427,554,427,562,427,569,427,577,427,584,427,588,427,593,427,597,427,599,427,601,427,601,426,602,425"
  }, {
    "id": 5,
    "points": "693,249,693,257,693,270,695,285,695,303,695,325,696,344,699,365,701,380,702,394,704,406,705,416,708,427,710,436,711,444,713,451,713,455,714,461,715,463,715,464,715,465"
  }, {
    "id": 6,
    "points": "761,297,761,304,763,310,764,317,766,324,767,332,768,341,769,352,769,364,771,377,772,394,773,411,774,426,776,441,779,451,780,461,780,465,781,471,782,473,782,474,782,475,782,476,782,477"
  }, {
    "id": 7,
    "points": "976,383,975,383,974,382,971,381,970,381,967,381,964,381,962,380,957,380,953,380,946,380,938,382,933,384,927,387,920,391,914,395,908,399,903,404,899,409,896,413,894,418,892,422,891,427,891,432,891,436,891,443,893,448,895,452,897,456,900,460,902,463,905,465,908,469,913,470,919,472,926,474,930,475,936,476,942,476,948,476,955,476,959,474,964,472,967,468,971,462,976,455,978,448,979,439,980,431,980,426,980,419,980,414,979,410,977,406,975,403,974,400,973,399,972,398,970,396,969,395,968,393,967,392,966,391,963,390,962,388,960,388,958,386,957,386,956,385,956,384,955,384,954,383,953,382,952,381,952,380,951,380,950,379,950,378,949,376"
  }, {
    "id": 8,
    "points": "958,3"
  }]
}

You can find the complete code for this little project in this Github repository.

What to expect in the next part of the series?

The next steps are to create the Azure Ink Recognizer resource and then we are going to see how to use the service and get the results.

Find the part two of the series here.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *