[WPF] 實作手寫辨識

  • 649
  • 0
  • 2021-10-14

這一篇介紹 WPF 上的手寫辨識實作,為了符合大家開發的習慣,範例會採用 MVVM 的方式完成。

準備工作

這個實作會用辨識引擎的部分,通常會在 \Program Files\Common Files\Microsoft Shared\ink\ Microsoft.Ink.,請記得將這個組件加入參考。

畫面安排

範例的畫面將 Grid 切割成三個 rows,最頂端放置辨識與清除按鈕、中間安排 ItemsControl 來展示候選字、下方則是使用 InkCanvas 當作手寫板。

 <Grid>      
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="72"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" Grid.Row="0" >
        <StackPanel.Resources >
            <Style TargetType="Button" >
                <Setter Property="Margin" Value="6,12"/>
            </Style>              
        </StackPanel.Resources>
        <Button Content="Recognize" Command="{Binding Recognize}" />
        <Button Content="Clear" Command="{Binding Clear}" />
    </StackPanel>
    <ItemsControl Grid.Row="1" ItemsSource="{Binding Candidates}" Background="YellowGreen" >
        <ItemsControl.ItemsPanel >
            <ItemsPanelTemplate >
                <StackPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate >
                <Button Margin="6" Padding="4" Content="{Binding}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    <InkCanvas Grid.Row="2" 
               Background="White" 
               Strokes="{Binding Strokes}" 
               UseCustomCursor="True" Cursor="Pen"
               DefaultDrawingAttributes="{Binding DrawingAttributes}">        
    </InkCanvas>
</Grid>
資料繫結

我們會需要繫結到幾個資料,一個是 InkCanvas 的 Strokes (筆觸)、一個是 InkCanvas 的 DefaultDrawingAttributes、最後則是 ItemsControl 所要展示個候選字集合。

 private StrokeCollection _strokes;

 public StrokeCollection Strokes
 {
     get => _strokes;
     set => SetProperty(ref _strokes, value);
 }

 private ObservableCollection<string> _candidates;

 public ObservableCollection<string> Candidates
 {
     get => _candidates;
     set => SetProperty(ref _candidates, value);
 }

 private System.Windows.Ink.DrawingAttributes _drawingAttributes;
 public System.Windows.Ink.DrawingAttributes DrawingAttributes
 {
     get => _drawingAttributes;
     set => SetProperty(ref _drawingAttributes, value);
 }

在 View Model 的建構式先初始化這三個屬性的相關欄位資料。

 public MainViewModel()
 {
     _candidates = new ObservableCollection<string>();
     _strokes = new StrokeCollection();
     _drawingAttributes = new System.Windows.Ink.DrawingAttributes
     {
         Color = Colors.DarkBlue,
         FitToCurve = true,
         IgnorePressure = false,
         IsHighlighter = false,
         StylusTip = StylusTip.Ellipse,
         Width = 18,
         Height = 18,
     };
 }
命令繫結

分為兩個部分,一個是辨識,另一個是清除。清除的程式碼很簡單就是把筆觸和候選字集合清除而已。

 public ICommand Clear
 {
     get
     {
         return new RelayCommand((x) =>
        {
            Strokes.Clear();
            Candidates.Clear();
        });
     }
 }

辨識的部分就會用到開頭所說明的函式庫,透過 memory stream 將 InkCanvas 的筆觸轉換為 Microsoft.Ink 的筆觸,接著使用 RecognizerContext.Recognize 方法辨識筆觸的內容,最後利用 RecognitionResult.GetAlternatesFromSelection 方法取得候選字就完成了。

 public ICommand Recognize
 {
     get
     {
         return new RelayCommand((x) =>
         {
             Candidates.Clear();
             if (Strokes.Count == 0) return;
             using (var stream = new MemoryStream())
             {
                 Strokes.Save(stream);
                 var ink = new Ink();
                 ink.Load(stream.ToArray());
                 using (var context = new RecognizerContext())
                 {
                     context.Strokes = ink.Strokes;
                     var result = context.Recognize(out RecognitionStatus status);
                     if (status == RecognitionStatus.NoError)
                     {
                         foreach (var candidate in result.GetAlternatesFromSelection())
                         {
                             Candidates.Add(candidate.ToString());
                         }
                     }
                 }
             }
         });
     }
 }

非常容易就能完成手寫辨識的功能,範例程式在此