C#.net(WPF)で画面を文字認識してみる

# はじめに
※この記事は過去書いた記事を加筆・修正してまとめたものです。

Windows10でOCRをする方法は以下の方法があります。

1. 自分で実装する
1. クラウドサービスを使う(AWS,GCP,Azureなど)
1. Tesseractを使う
1. Windows10搭載のMicrosoftOCRを使う


今回は主に④のMicrosoftOCRを使いながら、
C#.NETで画面の文字を認識を試みてみます。

# 使用するもの
* Microsoft VisualStudio 2019
* Microsoft Windows10 Software Development Kit


# MicrosoftOCRについて
MicrosoftOCRは、デスクトップ版Windows10に実装されている
Optical Character Recognition(OCR)・文字認識用APIです。

様々なOCRの中でも結構精度が高いらしいです。
https://rpa.bigtreetc.com/column/microsoftocr/

使い方についてですが、
プロジェクトをUWP形式かWPF形式で作るかによって使い方が違います。

・UWPの場合
using Windows.Media.Ocr;を書くことでそのままで使うことが出来ます。

・UWP以外の場合(WPFやFormsなど)
NuGetでMicrosoft.Windows.SDK.Contractsプラグインを導入する必要があります。
また、それに伴った複数の設定が必要です。

以下、WPF形式でプラグインを導入して使う方法を説明していきます。
 
# Windos.SDK.Contractsプラグインの導入
Microsoft.Windows.SDK.Contractsは、
UniversalWindowsのAPIをWPFで使うことが出来るようにするプラグインです。

導入するためには、VisualStudio上で以下の手順を行ってください。

```
1. [ツール]>[NuGetパッケージマネージャー]>[パッケージマネージャー設定]を開きます。

2. [パッケージの管理]にある[既定のパッケージ管理形式]を
「PackageReference」に変更します(※1)

3. プロジェクトを作ります(※2)

4. [NuGetパッケージの管理] を開きます。

5. 検索ボックスの右側にある [プレリリースを含める] にチェックを入れます。

6. 検索ボックスに「Microsoft.Windows.SDK.Contracts」と入力して検索します。

7. 「Microsoft.Windows.SDK.Contracts」を選択し、インストールします(※3)。

8. 下記のURLよりContractsのバージョンに合うWindows10SDKをインストールします。
 過去バージョンのSDK
 　https://developer.microsoft.com/ja-jp/windows/downloads/sdk-archive/
 最新バージョンのSDK
 　https://developer.microsoft.com/ja-jp/windows/downloads/windows-10-sdk/

13. [参照の追加] を開く。

14. [参照(B)] ボタンを選択し、ファイル選択ダイアログを表示する。

15. 下記のファイルをそれぞれ設定(sdk-versionは任意のバージョン)(※4)
 ・System.Runtime.WindowsRuntime
 　C:\Windows\Microsoft.NET\Framework\v4.0.30319
 ・System.Runtime.WindowsRuntime.UI.Xaml
 　C:\Windows\Microsoft.NET\Framework\v4.0.30319
 ・System.Runtime.InteropServices.WindowsRuntime
 　C:\Windows\Microsoft.NET\Framework\v4.0.30319
 ・windows.winmd
 　C:\Program Files (x86)\Windows Kits\10\UnionMetadata\<sdk version>\Facade
 ・Windows.Foundation.UniversalApiContract.winmd
 　C:\Program Files (x86)\Windows Kits\10\References\<sdk version>\Windows.Foundation.UniversalApiContract
 ・Windows.Foundation.FoundationContract.winmd
 　C:\Program Files (x86)\Windows Kits\10\References\<sdk version>\Windows.Foundation.FoundationContract
```

※1.パッケージ管理システムがPackage.configだと導入時にエラーが出る場合があります。

※2.既存のプロジェクトを開く場合、パッケージ管理がPackage.configの場合があるので、
　　ソリューションエクスプローラー内の参照ツリーを右クリックして、
　［Package.configをPackageReferenceに移行する］があればそれを押します。
　　その後、プラグインなどの確認ウインドウが出るので問題がなければOKを押します。

※3.バージョンは幾つかあるので、使用したい・インストール済みのWindowsSDKのバージョンと合わせます。

※4.参照の追加によって、競合が発生する場合があります。
　　その場合はエラーに基づいて一部追加した参照を消してください。

## 画面をキャプチャする
以下のコードを使い画面全体のキャプチャを取得します。

```csharp
using System.Drawing;
using System.Windows.Forms;
private Bitmap CaptureScreen(){
    //Bitmapの作成
    Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
    Screen.PrimaryScreen.Bounds.Height);
    //Graphicsの作成
    Graphics g = Graphics.FromImage(bitmap);
    //画面全体をコピーする
    g.CopyFromScreen(new Point(0, 0), new Point(0, 0), bmp.Size);
    //解放
    g.Dispose();
    return bitmap;
}
```

## BitmapをSoftwareBitmapに変換
上記で画面の画像は取得できるが、このままではMicrosoftOCRで上では処理できません。
理由は、MicrosoftOCRのインプットはSoftwareBitmapという形式になっているからです。
そのため、取得したBitmapを以下のコードでSoftwareBitmapに変換します。

```csharp
using System.IO;
//using System.Drawing;ダブリ、単体で使う場合は有効に
using System.Drawing.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.Graphics.Imaging;
public async Task<SoftwareBitmap> GetSoftwareSnapShot(Bitmap snap)
{
    // 取得したキャプチャ画像をファイルとして保存
    var folder = Directory.GetCurrentDirectory();
    var imageName = "ScreenCapture.bmp";
    StorageFolder appFolder = await StorageFolder.GetFolderFromPathAsync(@folder);
    snap.Save(folder + "\\" + imageName, ImageFormat.Bitmap);
    SoftwareBitmap softwareBitmap;
    var bmpFile = await appFolder.GetFileAsync(imageName);

    // 保存した画像をSoftwareBitmap形式で読み込み
    using (IRandomAccessStream stream = await bmpFile.OpenAsync(FileAccessMode.Read))
    {
        BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
        softwareBitmap = await decoder.GetSoftwareBitmapAsync();
    }

    // 保存した画像ファイルの削除
    File.Delete(folder + "\\" + imageName);

    // SoftwareBitmap形式の画像を返す
    return softwareBitmap;
}
```
一旦画像を保存して、それをsoftwareBitmap形式に読み込み直すという若干力技じみた実装ではあります。
もっとスマートな方法で書くと、MemoryStreamに一時ファイルを保存する形が良いかもしれません。
以下のような感じで。

```csharp
var ms = new MemoryStream ();
bitmap.Save (ms, System.Drawing.Imaging.ImageFormat.Png);
var stream = ms.AsRandomAccessStream ();
```

## 画像を文字認識する
ここはとっても簡単、OcrEngineを作ってSoftwareBitmapを渡すだけ。

```csharp
using Windows.Media.Ocr;
//using Windows.Graphics.Imaging;ダブリ
private async Task<OcrResult> RecognizeText(SoftwareBitmap snap)
{
    OcrEngine ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages();
    // OCR実行
    var ocrResult = await ocrEngine.RecognizeAsync(snap);
    return ocrResult;
}
```
上記の出力結果ocrResult.textで認識結果(String)を得ることが出来ます。

## おわりに
今回はWindows10 OCR APIを使いましたが、
実装と検証に多くの時間が割けられるのならば、自分で1から実装することも良いと思います。
そういう場合、機械学習を用いた文字認識モデルを作る手法などを考えたり実装することになります。
Pythonではそれらの機械学習の実装が頻繁に行われているので、
それらリソースを参照しながら各言語で実装を進めるのが良いかもしれません。
  
 以下の記事が参考になりそうなので、貼っておきます。
【日本語OCRを作ったので解説してみる】
https://qiita.com/tanreinama/items/8fc1c8af6554654aae00
【文字認識アルゴリズムのFOTSを実装した】
https://qiita.com/jjjkkkjjj/items/bfa03d89eaf6ab0c0487#recognition