C#でOcrEngineを使用して画像内の文字列を取得する方法の概略を紹介します。
記事作成時の環境は以下の通りです。
・Windows11
・Visual Studio Community 2022
・.Net 8.0のWindowsForm(Windowsフォームアプリ)
・Microsoft.Windows.CsWinRT v2.0.7
Windows11で.Netの場合はどうなる?と思ったのでメモ程度の記事ですので間違いがありましたら指摘していただければと思います。
Windowsフォームアプリの場合の参考例が少ないようので、あえてWindowsフォームアプリで作成しています。
以下のサイトを参考にしています。
【C#】Windows10のOCRを使用する – かくすけのいろいろ作るブログ (hatenablog.com)
C#.net(WPF)で画面を文字認識してみる #C# – Qiita
開発メモ その267 Windows 10 の OCR を試してみる (.NET) · A certain engineer “COMPLEX” (taktak.jp)
Microsoft.Windows.CsWinRTの導入
NuGetからインストールを行います。
Visual Studioのメニューから”ツール”→”NuGetパッケージマネージャー”→”ソリューションのNuGetパッケージの管理”を起動します。
”参照”を選択し、検索窓にMicrosoft.Windows.CsWinRTを入力すると見つかりますので、選択してプロジェクトにインストールします。
.Net8.0ではMicrosoft.Windows.SDK.Contractsは使用できないようなので注意が必要です。
変更のプレビューが出ますが適用で大丈夫です。
TargetFrameworkの修正
Nugetからインストールしただけではビルドに失敗しますので、TargetFrameworkを修正します。
.csprojファイルをテキストで開き、PropertyGroupにあるTargetFrameworkを「net8.0-windows10.0.22621.0」とするだけです。この値は使用する環境に合わせて変更が必要です。
元からあるTargetFrameworkを変えてもいいですし、後ろに追加しても大丈夫です。
今回の例では後ろに追加しています。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.0.7" />
</ItemGroup>
</Project>
言語パックのインストール
OCRを使用する場合、対象の言語がインストールされている必要があります。
日本語のまま英文を読み取ることもできなくはないですが、後述の「OCRの実行と結果の確認」にあるように精度が違います。
Windowsの「設定」から「時刻と言語」を選択し、言語の追加で必要な言語をあらかじめインストールしておいてください。
OCRの実行と結果の確認
例として以前の記事で使用した画像ファイルを読み込み、変換してみます。
C#PictureBoxの使い方 | のんびりとまとめてみる (cssmemo.com)
英文なので英語を指定して実行した場合は以下の画像のようになり、大体正常に取得できていることがわかります。
ところどころ「.」が「_」とか「:」になってたりしますが十分な精度ではないでしょうか。
言語を日本語指定にしたまま読み取ると下の画像のようになります。
ざっくりと取得はできていますが、スペースがないですし、正確に読み取れていない部分が多いです。
正直使えない精度かなと思います。
では日本語の場合はどうなるのか。以下の記事の一部をキャプチャして試してみます。
マルチモニターのすすめ | のんびりとまとめてみる (cssmemo.com)
思っていたよりもしっかりと読み取れています。
Wordの「r」が「「」になってたりしますが、大体はちゃんと取得できています。
日本語は複雑なのでどうかなと思いましたが、濁点の抜けなどはあるものの精度が良いです。
言語を英語指定にしたまま読み取るとどうなるかも試しておきますが、予想通り全く使えません。
コード
今回の実験ではコードを流用しているため以下のような構成としています。
ファイルパスが表示されている部分にファイルドロップするとShowMyImage(String fileToDisplay)が呼ばれて画像が表示されます。
実行ボタンをクリックするとOCRが実行され、右側にテキストが表示されます。
private Bitmap MyImage;
/// <summary>
/// PictureBoxに画像を表示する
/// </summary>
/// <param name="fileToDisplay">描画する画像のファイルパス</param>
public void ShowMyImage(String fileToDisplay)
{
if (MyImage != null)
{
MyImage.Dispose();
}
MyImage = new Bitmap(fileToDisplay);
pictureBox1.Image = MyImage;
}
private async void buttonOCR_Click(object sender, EventArgs e)
{
// MemoryStreamにBitomapを保存し、SoftwareBitmapとする
MemoryStream ms = new MemoryStream();
MyImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
IRandomAccessStream ras = ms.AsRandomAccessStream();
BitmapDecoder bd = await BitmapDecoder.CreateAsync(ras);
SoftwareBitmap sb = await bd.GetSoftwareBitmapAsync();
// 言語の指定
Windows.Globalization.Language language = new Windows.Globalization.Language("ja");
OcrEngine ocrEngine = OcrEngine.TryCreateFromLanguage(language);
// OCR実行
OcrResult result = await ocrEngine.RecognizeAsync(sb);
textBox2.Text = result.Text;
}
改行も対応する場合(おまけ)
OCRの結果のTextをそのまま使用すると改行されないまま表示されます。
改行も対応するのであれば結果のLinesを使用することで一行ずつ表示させることもできます。
string s = "";
foreach (var item in result.Lines)
{
s += item.Text + "\r\n";
}
textBox2.Text = s;