使用 Windows Ink 中内置的识别功能将墨迹笔划转换为文本和形状。
重要 API: InkCanvas、 Windows.UI.Input.Inking
使用墨迹分析进行自由格式识别
在这里,我们演示如何使用 Windows 墨迹分析引擎(Windows.UI.Input.Inking.Analysis)将 InkCanvas 上的一组自由格式笔划分类、分析和识别为文本或形状。 (除了文本和形状识别之外,墨迹分析还可用于识别文档结构、项目符号列表和通用绘图。
注释
有关表单输入等基本单行纯文本方案,请参阅本主题后面的 约束手写识别 。
在此示例中,当用户单击一个按钮以指示已完成绘图时,将启动识别。
首先,我们设置 UI(MainPage.xaml)。
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 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。
- 如果 Status 返回更新状态,请为 InkAnalysisNodeKind.InkWord 和 InkAnalysisNodeKind.InkDrawing 调用 FindNodes。
- 遍历这两组节点类型集合,并在识别画布上绘制各自的文本或形状(位于墨迹画布下方)。
- 最后,从 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 上的一组笔划转换为文本(基于已安装的默认语言包)。
注释
本节中显示的基本手写识别最适合单行文本输入方案,如表单输入。 有关更丰富的识别方案,包括文档结构、列表项、形状和绘图的分析和解释(除了文本识别),请参阅上一部分: 使用墨迹分析进行自由格式识别。
在此示例中,当用户单击一个按钮以指示已完成写入时,将启动识别。
从 Ink 手写识别示例下载此示例
首先,我们设置了 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 支持的语言列表,请参阅 InkRecognizer.Name 属性主题。
你的应用可以查询已安装的手写识别引擎集并使用其中一个引擎,或者让用户选择其首选语言。
注意 用户可以通过转到 “设置 -> 时间和语言”来查看已安装语言的列表。 已安装的语言在 “语言”下列出。
若要安装新的语言包并启用该语言的手写识别,
- 转到
设置 “时间和语言” “区域和语言” 。 - 选择 “添加语言”。
- 从列表中选择语言,然后选择区域版本。 语言现在列在 “区域和语言 ”页上。
- 单击语言并选择“ 选项”。
- 在 “语言选项 ”页上,下载 手写识别引擎 (还可以在此处下载完整的语言包、语音识别引擎和键盘布局)。
在这里,我们演示如何使用手写识别引擎根据所选识别器解释 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)添加侦听器,并设置一个基本计时器(DispatcherTimer),其间隔为一秒Tick。
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 页面事件以管理我们的计时器)。
StrokesCollected
将墨迹笔划(AddDataForStrokes)添加到 InkAnalyzer,并在用户停止墨迹书写时启动识别计时器(通过抬起笔或手指或松开鼠标按钮)。 无墨迹输入一秒后,将启动识别。使用 SetStrokeDataKind 方法来指定您是仅对文本(包括文档结构和项目符号列表)感兴趣,还是仅对绘图(包括形状识别)感兴趣。
StrokeStarted
如果新的笔划在下一个计时器时钟周期事件之前开始,请停止计时器,因为新笔划可能是单个手写条目的延续。
// 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。
- 如果 Status 返回“已更新”状态,请为 InkAnalysisNodeKind.InkWord 的节点类型调用 FindNodes。
- 遍历节点并显示已识别的文本。
- 最后,从 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(); } }