使用 Windows Ink 中内置的识别功能将墨迹笔划转换为文本和形状。
重要 API:InkCanvas、Windows.UI.Input.Inking
使用墨迹分析进行自由形态识别
在这里,我们演示如何使用 Windows 墨迹分析引擎(Windows.UI.Input.Inking.Analysis) 对 InkCanvas 上的一组自由格式笔划进行分类、分析和识别, 为文本或形状。 (除了文本和形状识别之外,墨迹分析还可用于识别文档结构、项目符号列表和通用绘图。
注释
有关表单输入等基本的、单行的纯文本场景,请参阅本主题后面的 约束手写识别部分。
在此示例中,当用户单击一个按钮以指示已完成绘图时,将启动识别。
从 墨迹分析示例(基本) 下载此示例
首先,我们设置 UI(MainPage.xaml)。
UI 包括“识别”按钮、InkCanvas,以及标准 Canvas。 按下“识别”按钮时,将分析墨迹画布上的所有墨迹笔划,并在标准画布上绘制相应的形状和文本(如果识别)。 然后,从墨迹画布中删除原始墨迹笔划。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0"> <TextBlock x:Name="Header" Text="Basic ink analysis sample" Style="{ThemeResource HeaderTextBlockStyle}" Margin="10,0,0,0" /> <Button x:Name="recognize" Content="Recognize" Margin="50,0,10,0"/> </StackPanel> <Grid x:Name="drawingCanvas" Grid.Row="1"> <!-- The canvas where we render the replacement text and shapes. --> <Canvas x:Name="recognitionCanvas" /> <!-- The canvas for ink input. --> <InkCanvas x:Name="inkCanvas" /> </Grid> </Grid>
在 UI 代码隐藏文件 (MainPage.xaml.cs) 中,添加我们墨迹和墨迹分析功能所需的命名空间类型引用。
然后,指定全局变量:
InkAnalyzer inkAnalyzer = new InkAnalyzer(); IReadOnlyList<InkStroke> inkStrokes = null; InkAnalysisResult inkAnalysisResults = null;
接下来,我们将设置一些基本的墨迹输入行为:
- InkPresenter 配置为将来自笔、鼠标和触控的输入数据解释为墨迹笔划(InputDeviceTypes)。
- 使用指定的 InkDrawingAttributes的墨迹笔划呈现在 InkCanvas 上。
- 还要声明“识别”按钮上的单击事件监听器。
/// <summary> /// Initialize the UI page. /// </summary> public MainPage() { this.InitializeComponent(); // Set supported inking device types. inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen | Windows.UI.Core.CoreInputDeviceTypes.Touch; // Set initial ink stroke attributes. InkDrawingAttributes drawingAttributes = new InkDrawingAttributes(); drawingAttributes.Color = Windows.UI.Colors.Black; drawingAttributes.IgnorePressure = false; drawingAttributes.FitToCurve = true; inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes); // Listen for button click to initiate recognition. recognize.Click += RecognizeStrokes_Click; }
在本示例中,我们在“识别”按钮的单击事件处理程序中执行墨迹分析。
- 首先,在
以获取所有当前墨迹笔划的集合。InkCanvas.InkPresenter 的 StrokeContainer 上调用 GetStrokes - 如果存在墨迹笔划,请通过调用 InkAnalyzer 的 中的 AddDataForStrokes 方法来传递这些笔划。
- 我们尝试识别绘图和文本,但你可以使用 SetStrokeDataKind 方法指定你是否只对文本(包括文档结构和项目符号列表)或绘图(包括形状识别)感兴趣。
- 调用 AnalyzeAsync 以启动墨迹分析并获取 InkAnalysisResult。
- 如果 状态 返回的状态为 已更新,请调用 FindNodes 来查找 InkAnalysisNodeKind.InkWord 和 InkAnalysisNodeKind.InkDrawing。
- 遍历两个集合的节点类型,并在识别画布上绘制相应的文本或形状(位于墨迹画布的下方)。
- 最后,从 InkAnalyzer 中删除已识别的节点,并从墨迹画布中删除相应的墨迹笔划。
/// <summary> /// The "Analyze" button click handler. /// Ink recognition is performed here. /// </summary> /// <param name="sender">Source of the click event</param> /// <param name="e">Event args for the button click routed event</param> private async void RecognizeStrokes_Click(object sender, RoutedEventArgs e) { inkStrokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes(); // Ensure an ink stroke is present. if (inkStrokes.Count > 0) { inkAnalyzer.AddDataForStrokes(inkStrokes); // In this example, we try to recognizing both // writing and drawing, so the platform default // of "InkAnalysisStrokeKind.Auto" is used. // If you're only interested in a specific type of recognition, // such as writing or drawing, you can constrain recognition // using the SetStrokDataKind method as follows: // foreach (var stroke in strokesText) // { // analyzerText.SetStrokeDataKind( // stroke.Id, InkAnalysisStrokeKind.Writing); // } // This can improve both efficiency and recognition results. inkAnalysisResults = await inkAnalyzer.AnalyzeAsync(); // Have ink strokes on the canvas changed? if (inkAnalysisResults.Status == InkAnalysisStatus.Updated) { // Find all strokes that are recognized as handwriting and // create a corresponding ink analysis InkWord node. var inkwordNodes = inkAnalyzer.AnalysisRoot.FindNodes( InkAnalysisNodeKind.InkWord); // Iterate through each InkWord node. // Draw primary recognized text on recognitionCanvas // (for this example, we ignore alternatives), and delete // ink analysis data and recognized strokes. foreach (InkAnalysisInkWord node in inkwordNodes) { // Draw a TextBlock object on the recognitionCanvas. DrawText(node.RecognizedText, node.BoundingRect); foreach (var strokeId in node.GetStrokeIds()) { var stroke = inkCanvas.InkPresenter.StrokeContainer.GetStrokeById(strokeId); stroke.Selected = true; } inkAnalyzer.RemoveDataForStrokes(node.GetStrokeIds()); } inkCanvas.InkPresenter.StrokeContainer.DeleteSelected(); // Find all strokes that are recognized as a drawing and // create a corresponding ink analysis InkDrawing node. var inkdrawingNodes = inkAnalyzer.AnalysisRoot.FindNodes( InkAnalysisNodeKind.InkDrawing); // Iterate through each InkDrawing node. // Draw recognized shapes on recognitionCanvas and // delete ink analysis data and recognized strokes. foreach (InkAnalysisInkDrawing node in inkdrawingNodes) { if (node.DrawingKind == InkAnalysisDrawingKind.Drawing) { // Catch and process unsupported shapes (lines and so on) here. } // Process generalized shapes here (ellipses and polygons). else { // Draw an Ellipse object on the recognitionCanvas (circle is a specialized ellipse). if (node.DrawingKind == InkAnalysisDrawingKind.Circle || node.DrawingKind == InkAnalysisDrawingKind.Ellipse) { DrawEllipse(node); } // Draw a Polygon object on the recognitionCanvas. else { DrawPolygon(node); } foreach (var strokeId in node.GetStrokeIds()) { var stroke = inkCanvas.InkPresenter.StrokeContainer.GetStrokeById(strokeId); stroke.Selected = true; } } inkAnalyzer.RemoveDataForStrokes(node.GetStrokeIds()); } inkCanvas.InkPresenter.StrokeContainer.DeleteSelected(); } } }
- 首先,在
下面是在我们的识别画布上绘制 TextBlock 的函数。 我们使用墨迹画布上关联墨迹笔划的边界矩形来设置 TextBlock 的位置和字号。
/// <summary> /// Draw ink recognition text string on the recognitionCanvas. /// </summary> /// <param name="recognizedText">The string returned by text recognition.</param> /// <param name="boundingRect">The bounding rect of the original ink writing.</param> private void DrawText(string recognizedText, Rect boundingRect) { TextBlock text = new TextBlock(); Canvas.SetTop(text, boundingRect.Top); Canvas.SetLeft(text, boundingRect.Left); text.Text = recognizedText; text.FontSize = boundingRect.Height; recognitionCanvas.Children.Add(text); }
下面是用于在我们识别画布上绘制椭圆和多边形的函数。 我们在墨迹画布上使用相关墨迹笔划的边界矩形来设置形状的位置和字号。
// Draw an ellipse on the recognitionCanvas. private void DrawEllipse(InkAnalysisInkDrawing shape) { var points = shape.Points; Ellipse ellipse = new Ellipse(); ellipse.Width = shape.BoundingRect.Width; ellipse.Height = shape.BoundingRect.Height; Canvas.SetTop(ellipse, shape.BoundingRect.Top); Canvas.SetLeft(ellipse, shape.BoundingRect.Left); var brush = new SolidColorBrush(Windows.UI.ColorHelper.FromArgb(255, 0, 0, 255)); ellipse.Stroke = brush; ellipse.StrokeThickness = 2; recognitionCanvas.Children.Add(ellipse); } // Draw a polygon on the recognitionCanvas. private void DrawPolygon(InkAnalysisInkDrawing shape) { List<Point> points = new List<Point>(shape.Points); Polygon polygon = new Polygon(); foreach (Point point in points) { polygon.Points.Add(point); } var brush = new SolidColorBrush(Windows.UI.ColorHelper.FromArgb(255, 0, 0, 255)); polygon.Stroke = brush; polygon.StrokeThickness = 2; recognitionCanvas.Children.Add(polygon); }
下面是运行中的此示例:
在分析之前 | 经过分析之后 |
---|---|
![]() |
分析后 ![]() |
约束手写识别
在前一部分(使用墨迹分析进行自由格式识别)中,我们演示了如何使用 墨迹分析 API 分析和识别 InkCanvas 区域内的任意墨迹笔划。
在本部分中,我们将演示如何使用 Windows Ink 手写识别引擎(而不是墨迹分析)将一组笔划转换为文本 InkCanvas(基于已安装的默认语言包)。
注释
本节中显示的基本手写识别最适合单行文本输入方案,如表单输入。 有关更丰富的识别场景,包括对文档结构、列表项、形状和绘图的分析与解释(除了文本识别之外),请参阅上一部分:使用墨迹分析进行自由格式识别。
在此示例中,当用户单击一个按钮以指示已完成写入时,将启动识别。
首先,我们设置了 UI。
UI 包括“识别”按钮、InkCanvas以及用于显示识别结果的区域。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0"> <TextBlock x:Name="Header" Text="Basic ink recognition sample" Style="{ThemeResource HeaderTextBlockStyle}" Margin="10,0,0,0" /> <Button x:Name="recognize" Content="Recognize" Margin="50,0,10,0"/> </StackPanel> <Grid Grid.Row="1"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <InkCanvas x:Name="inkCanvas" Grid.Row="0"/> <TextBlock x:Name="recognitionResult" Grid.Row="1" Margin="50,0,10,0"/> </Grid> </Grid>
对于此示例,首先需要添加墨迹功能所需的命名空间类型引用:
然后,我们设置一些基本的墨迹输入行为。
InkPresenter 配置为将笔和鼠标的输入数据解释为墨迹笔划(InputDeviceTypes)。 使用指定的 InkDrawingAttributes的墨迹笔划呈现在 InkCanvas 上。 还要声明“识别”按钮上的单击事件监听器。
public MainPage() { this.InitializeComponent(); // Set supported inking device types. inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen; // Set initial ink stroke attributes. InkDrawingAttributes drawingAttributes = new InkDrawingAttributes(); drawingAttributes.Color = Windows.UI.Colors.Black; drawingAttributes.IgnorePressure = false; drawingAttributes.FitToCurve = true; inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes); // Listen for button click to initiate recognition. recognize.Click += Recognize_Click; }
最后,我们执行基本的手写识别。 在本示例中,我们使用“识别”按钮的单击事件处理程序来执行手写识别。
- InkPresenter 将所有墨迹笔划存储在 InkStrokeContainer 对象中。 笔划通过 InkPresenter 的 StrokeContainer 属性公开,并使用 GetStrokes 方法进行检索。
// Get all strokes on the InkCanvas. IReadOnlyList<InkStroke> currentStrokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
- 创建 InkRecognizerContainer 来管理手写识别过程。
// Create a manager for the InkRecognizer object // used in handwriting recognition. InkRecognizerContainer inkRecognizerContainer = new InkRecognizerContainer();
- 调用 的 RecognizeAsync 方法用于检索一组 InkRecognitionResult 对象。 InkRecognizer为每个检测到的单词生成识别结果。
// Recognize all ink strokes on the ink canvas. IReadOnlyList<InkRecognitionResult> recognitionResults = await inkRecognizerContainer.RecognizeAsync( inkCanvas.InkPresenter.StrokeContainer, InkRecognitionTarget.All);
每个 InkRecognitionResult 对象都包含一组文本候选项。 该列表中的顶部项被识别引擎视为最佳匹配,然后是剩余候选项,按照置信度递减顺序排列。
我们遍历每个 InkRecognitionResult 并汇总候选项列表。 然后显示候选项,并清除 InkStrokeContainer(这也清除了 InkCanvas)。
string str = "Recognition result\n"; // Iterate through the recognition results. foreach (var result in recognitionResults) { // Get all recognition candidates from each recognition result. IReadOnlyList<string> candidates = result.GetTextCandidates(); str += "Candidates: " + candidates.Count.ToString() + "\n"; foreach (string candidate in candidates) { str += candidate + " "; } } // Display the recognition candidates. recognitionResult.Text = str; // Clear the ink canvas once recognition is complete. inkCanvas.InkPresenter.StrokeContainer.Clear();
- 下面是完全单击处理程序示例。
// Handle button click to initiate recognition. private async void Recognize_Click(object sender, RoutedEventArgs e) { // Get all strokes on the InkCanvas. IReadOnlyList<InkStroke> currentStrokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes(); // Ensure an ink stroke is present. if (currentStrokes.Count > 0) { // Create a manager for the InkRecognizer object // used in handwriting recognition. InkRecognizerContainer inkRecognizerContainer = new InkRecognizerContainer(); // inkRecognizerContainer is null if a recognition engine is not available. if (!(inkRecognizerContainer == null)) { // Recognize all ink strokes on the ink canvas. IReadOnlyList<InkRecognitionResult> recognitionResults = await inkRecognizerContainer.RecognizeAsync( inkCanvas.InkPresenter.StrokeContainer, InkRecognitionTarget.All); // Process and display the recognition results. if (recognitionResults.Count > 0) { string str = "Recognition result\n"; // Iterate through the recognition results. foreach (var result in recognitionResults) { // Get all recognition candidates from each recognition result. IReadOnlyList<string> candidates = result.GetTextCandidates(); str += "Candidates: " + candidates.Count.ToString() + "\n"; foreach (string candidate in candidates) { str += candidate + " "; } } // Display the recognition candidates. recognitionResult.Text = str; // Clear the ink canvas once recognition is complete. inkCanvas.InkPresenter.StrokeContainer.Clear(); } else { recognitionResult.Text = "No recognition results."; } } else { Windows.UI.Popups.MessageDialog messageDialog = new Windows.UI.Popups.MessageDialog("You must install handwriting recognition engine."); await messageDialog.ShowAsync(); } } else { recognitionResult.Text = "No ink strokes to recognize."; } }
国际认可
内置于 Windows 墨迹平台的手写识别功能包括 Windows 支持的大量区域设置和语言子集。
有关 InkRecognizer.Name 属性主题的详细信息,请参阅 InkRecognizer 支持的语言列表。
你的应用可以查询已安装的手写识别引擎集并使用其中一个引擎,或者让用户选择其首选语言。
注意 用户可以通过转到 “设置 -> 时间和语言”来查看已安装语言的列表。 已安装的语言列在 语言下。
若要安装新的语言包并启用该语言的手写识别,
- 转到“设置>时间”和“语言区域”和“语言>”。
- 选择“添加语言。
- 从列表中选择语言,然后选择区域版本。 语言现在列在 “区域和语言 ”页上。
- 单击语言并选择 选项。
- 在 语言选项 页上,下载 手写识别引擎(还可以在此处下载完整的语言包、语音识别引擎和键盘布局)。
在这里,我们演示了如何使用手写识别引擎根据所选识别器在 InkCanvas 上解释一组笔划。
完成写入后,用户单击按钮即可启动识别。
首先,我们设置了 UI。
UI 包括“识别”按钮、列出所有已安装手写识别器的组合框、InkCanvas以及显示识别结果的区域。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0"> <TextBlock x:Name="Header" Text="Advanced international ink recognition sample" Style="{ThemeResource HeaderTextBlockStyle}" Margin="10,0,0,0" /> <ComboBox x:Name="comboInstalledRecognizers" Margin="50,0,10,0"> <ComboBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Name}" /> </StackPanel> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> <Button x:Name="buttonRecognize" Content="Recognize" IsEnabled="False" Margin="50,0,10,0"/> </StackPanel> <Grid Grid.Row="1"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <InkCanvas x:Name="inkCanvas" Grid.Row="0"/> <TextBlock x:Name="recognitionResult" Grid.Row="1" Margin="50,0,10,0"/> </Grid> </Grid>
然后,我们设置一些基本的墨迹输入行为。
InkPresenter 配置为将笔和鼠标的输入数据解释为墨迹笔划(InputDeviceTypes)。 使用指定的 InkDrawingAttributes的墨迹笔划呈现在 InkCanvas 上。
我们调用
InitializeRecognizerList
函数来填充识别器组合框,其中包含已安装的手写识别器列表。我们还为“识别”按钮的单击事件添加侦听器,并为识别器组合框的选择更改事件添加侦听器。
public MainPage() { this.InitializeComponent(); // Set supported inking device types. inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen; // Set initial ink stroke attributes. InkDrawingAttributes drawingAttributes = new InkDrawingAttributes(); drawingAttributes.Color = Windows.UI.Colors.Black; drawingAttributes.IgnorePressure = false; drawingAttributes.FitToCurve = true; inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes); // Populate the recognizer combo box with installed recognizers. InitializeRecognizerList(); // Listen for combo box selection. comboInstalledRecognizers.SelectionChanged += comboInstalledRecognizers_SelectionChanged; // Listen for button click to initiate recognition. buttonRecognize.Click += Recognize_Click; }
我们使用已安装手写识别器的列表填充识别器组合框。
创建 InkRecognizerContainer 来管理手写识别过程。 使用此对象调用 GetRecognizers 并检索已安装的识别器列表以填充识别器组合框。
// Populate the recognizer combo box with installed recognizers. private void InitializeRecognizerList() { // Create a manager for the handwriting recognition process. inkRecognizerContainer = new InkRecognizerContainer(); // Retrieve the collection of installed handwriting recognizers. IReadOnlyList<InkRecognizer> installedRecognizers = inkRecognizerContainer.GetRecognizers(); // inkRecognizerContainer is null if a recognition engine is not available. if (!(inkRecognizerContainer == null)) { comboInstalledRecognizers.ItemsSource = installedRecognizers; buttonRecognize.IsEnabled = true; } }
如果识别器组合框选择发生更改,请更新手写识别器。
使用 InkRecognizerContainer 调用 SetDefaultRecognizer,根据识别器组合框中选定的识别器。
// Handle recognizer change. private void comboInstalledRecognizers_SelectionChanged( object sender, SelectionChangedEventArgs e) { inkRecognizerContainer.SetDefaultRecognizer( (InkRecognizer)comboInstalledRecognizers.SelectedItem); }
最后,我们根据所选的手写识别器进行手写识别。 在本示例中,我们使用“识别”按钮的单击事件处理程序来执行手写识别。
- InkPresenter 将所有墨迹笔划存储在 InkStrokeContainer 对象中。 笔划通过 InkPresenter 的 StrokeContainer 属性公开,并使用 GetStrokes 方法进行检索。
// Get all strokes on the InkCanvas. IReadOnlyList<InkStroke> currentStrokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
调用 的 RecognizeAsync 方法用于检索一组 InkRecognitionResult 对象。
InkRecognizer为每个检测到的单词生成识别结果。
// Recognize all ink strokes on the ink canvas. IReadOnlyList<InkRecognitionResult> recognitionResults = await inkRecognizerContainer.RecognizeAsync( inkCanvas.InkPresenter.StrokeContainer, InkRecognitionTarget.All);
每个 InkRecognitionResult 对象都包含一组文本候选项。 该列表中的顶部项被识别引擎视为最佳匹配,然后是剩余候选项,按照置信度递减顺序排列。
我们遍历每个 InkRecognitionResult 并汇总候选项列表。 然后显示候选项,并清除 InkStrokeContainer(这也清除了 InkCanvas)。
string str = "Recognition result\n"; // Iterate through the recognition results. foreach (InkRecognitionResult result in recognitionResults) { // Get all recognition candidates from each recognition result. IReadOnlyList<string> candidates = result.GetTextCandidates(); str += "Candidates: " + candidates.Count.ToString() + "\n"; foreach (string candidate in candidates) { str += candidate + " "; } } // Display the recognition candidates. recognitionResult.Text = str; // Clear the ink canvas once recognition is complete. inkCanvas.InkPresenter.StrokeContainer.Clear();
- 下面是完全单击处理程序示例。
// Handle button click to initiate recognition. private async void Recognize_Click(object sender, RoutedEventArgs e) { // Get all strokes on the InkCanvas. IReadOnlyList<InkStroke> currentStrokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes(); // Ensure an ink stroke is present. if (currentStrokes.Count > 0) { // inkRecognizerContainer is null if a recognition engine is not available. if (!(inkRecognizerContainer == null)) { // Recognize all ink strokes on the ink canvas. IReadOnlyList<InkRecognitionResult> recognitionResults = await inkRecognizerContainer.RecognizeAsync( inkCanvas.InkPresenter.StrokeContainer, InkRecognitionTarget.All); // Process and display the recognition results. if (recognitionResults.Count > 0) { string str = "Recognition result\n"; // Iterate through the recognition results. foreach (InkRecognitionResult result in recognitionResults) { // Get all recognition candidates from each recognition result. IReadOnlyList<string> candidates = result.GetTextCandidates(); str += "Candidates: " + candidates.Count.ToString() + "\n"; foreach (string candidate in candidates) { str += candidate + " "; } } // Display the recognition candidates. recognitionResult.Text = str; // Clear the ink canvas once recognition is complete. inkCanvas.InkPresenter.StrokeContainer.Clear(); } else { recognitionResult.Text = "No recognition results."; } } else { Windows.UI.Popups.MessageDialog messageDialog = new Windows.UI.Popups.MessageDialog( "You must install handwriting recognition engine."); await messageDialog.ShowAsync(); } } else { recognitionResult.Text = "No ink strokes to recognize."; } }
动态识别
虽然前面的两个示例要求用户按按钮开始识别,但也可以使用与基本计时函数配对的笔划输入执行动态识别。
对于此示例,我们将使用与上一个国际识别示例相同的 UI 和笔划设置。
这些全局对象(InkAnalyzer、InkStroke、InkAnalysisResult、DispatcherTimer)在整个应用中使用。
// Stroke recognition globals. InkAnalyzer inkAnalyzer; DispatcherTimer recoTimer;
我们没有使用按钮来启动识别,而是为两个 InkPresenter 笔划事件(StrokesCollected 和 StrokeStarted)添加了侦听器,并设置一个一秒 Tick 间隔的基本计时器(DispatcherTimer)。
public MainPage() { this.InitializeComponent(); // Set supported inking device types. inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen; // Listen for stroke events on the InkPresenter to // enable dynamic recognition. // StrokesCollected is fired when the user stops inking by // lifting their pen or finger, or releasing the mouse button. inkCanvas.InkPresenter.StrokesCollected += inkCanvas_StrokesCollected; // StrokeStarted is fired when ink input is first detected. inkCanvas.InkPresenter.StrokeInput.StrokeStarted += inkCanvas_StrokeStarted; inkAnalyzer = new InkAnalyzer(); // Timer to manage dynamic recognition. recoTimer = new DispatcherTimer(); recoTimer.Interval = TimeSpan.FromSeconds(1); recoTimer.Tick += recoTimer_TickAsync; }
然后,定义在第一步中声明的 InkPresenter 事件的处理程序(我们还将替代 OnNavigatingFrom 页面事件以管理计时器)。
收集的笔画
将墨迹笔划(AddDataForStrokes)添加到 InkAnalyzer,并在用户停止墨迹书写时启动识别计时器(通过抬起笔或手指或松开鼠标按钮)。 无墨迹输入一秒后,将启动识别。使用 SetStrokeDataKind 方法指定你是否只对文本(包括文档结构和项目符号列表)感兴趣,还是只对绘图(包括形状识别)感兴趣。
笔画开始
如果新的笔划在下一个计时器时钟周期事件之前开始,请停止计时器,因为新笔划可能是单个手写条目的延续。
// Handler for the InkPresenter StrokeStarted event. // Don't perform analysis while a stroke is in progress. // If a new stroke starts before the next timer tick event, // stop the timer as the new stroke is likely the continuation // of a single handwriting entry. private void inkCanvas_StrokeStarted(InkStrokeInput sender, PointerEventArgs args) { recoTimer.Stop(); } // Handler for the InkPresenter StrokesCollected event. // Stop the timer and add the collected strokes to the InkAnalyzer. // Start the recognition timer when the user stops inking (by // lifting their pen or finger, or releasing the mouse button). // If ink input is not detected after one second, initiate recognition. private void inkCanvas_StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args) { recoTimer.Stop(); // If you're only interested in a specific type of recognition, // such as writing or drawing, you can constrain recognition // using the SetStrokDataKind method, which can improve both // efficiency and recognition results. // In this example, "InkAnalysisStrokeKind.Writing" is used. foreach (var stroke in args.Strokes) { inkAnalyzer.AddDataForStroke(stroke); inkAnalyzer.SetStrokeDataKind(stroke.Id, InkAnalysisStrokeKind.Writing); } recoTimer.Start(); } // Override the Page OnNavigatingFrom event handler to // stop our timer if user leaves page. protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) { recoTimer.Stop(); }
最后,我们执行手写识别。 在本示例中,我们使用 DispatcherTimer 的 Tick 事件处理程序来启动手写识别。
- 调用 AnalyzeAsync 以启动墨迹分析并获取 InkAnalysisResult。
- 如果 状态 返回 更新的状态,则调用 FindNodes,以获取 InkAnalysisNodeKind.InkWord的节点类型。
- 循环遍历节点并展示已识别的文本。
- 最后,从 InkAnalyzer 中删除已识别的节点,并从墨迹画布中删除相应的墨迹笔划。
private async void recoTimer_TickAsync(object sender, object e) { recoTimer.Stop(); if (!inkAnalyzer.IsAnalyzing) { InkAnalysisResult result = await inkAnalyzer.AnalyzeAsync(); // Have ink strokes on the canvas changed? if (result.Status == InkAnalysisStatus.Updated) { // Find all strokes that are recognized as handwriting and // create a corresponding ink analysis InkWord node. var inkwordNodes = inkAnalyzer.AnalysisRoot.FindNodes( InkAnalysisNodeKind.InkWord); // Iterate through each InkWord node. // Display the primary recognized text (for this example, // we ignore alternatives), and then delete the // ink analysis data and recognized strokes. foreach (InkAnalysisInkWord node in inkwordNodes) { string recognizedText = node.RecognizedText; // Display the recognition candidates. recognitionResult.Text = recognizedText; foreach (var strokeId in node.GetStrokeIds()) { var stroke = inkCanvas.InkPresenter.StrokeContainer.GetStrokeById(strokeId); stroke.Selected = true; } inkAnalyzer.RemoveDataForStrokes(node.GetStrokeIds()); } inkCanvas.InkPresenter.StrokeContainer.DeleteSelected(); } } else { // Ink analyzer is busy. Wait a while and try again. recoTimer.Start(); } }