認識結果を使う
認識結果はOcrResultクラス(Windows.Media.Ocr名前空間)のオブジェクトとして返ってきます(次の図)。
OCRエンジンが認識した最小の単位が、緑色で示したOcrWordオブジェクト(Windows.Media.Ocr名前空間)です。英語では1単語、日本語では1文字になります。OcrWordオブジェクトは、認識した文字列を示すTextプロパティ(string型)と認識した領域を示すBoundingRectプロパティ(Windows.Foundation.Rect型)を持っています。なお、BoundingRectプロパティの示す座標は、OcrResultオブジェクトのTextAngleプロパティが示す角度だけ回転させて解釈しなければなりません(後述)。
このOcrWordオブジェクトを1行分だけ集めたコレクションが、紫色で示したOcrLineオブジェクト(Windows.Media.Ocr名前空間)のWordsプロパティ(IReadOnlyList<OcrWord>型)に格納されています。1行分のデータを表しているから「OcrLine」クラスというわけですね。OcrLineオブジェクトには、1行分の文字列を連結したTextプロパティ(string型)もあります。半角スペースを挟んで連結されています。
そして、OcrLineオブジェクトを集めたコレクションが、青色で示したOcrResultオブジェクトのLinesプロパティ(IReadOnlyList<OcrLine>型)に格納されています。OcrResultオブジェクトは、半角スペースを挟んですべての行を連結したTextプロパティ(string型)と、認識した領域の回転角度を示すTextAngleプロパティ(double?型)も持っています。TextAngleプロパティが示しているのは、認識した画像の中心を原点として、認識したすべての領域(BoundingRectプロパティのRectオブジェクト)を回転させる角度です(⇒MainPage.Ocr.csのDisplayOcrResultメソッド参照)。
OcrWordオブジェクトのTextプロパティを連結するときに半角スペースが挿入されるという仕様には、注意が必要です。アルファベット圏ではそれで良いのでしょうけれど、日本語ではカナ・漢字の間にすべて半角スペースが入ってしまいます。例として、「OCRエンジンの使い方(UWP編)」という文字列をOCRエンジンに認識させた場合を、次の画像に示します。
クリックして拡大すると、「OCR」と「エ」も別の領域として認識しているのが分かります
「OCR」と「(UWP」が単語として認識されています。残りはすべて1文字ずつの「単語」として、「工」・「ン」・「ジ」・「ン」・「の」・「使」・「い」・「方」・「編」・「)」の間には半角スペースが挿入されています。実際にOCRエンジンを利用するときには、OcrWordのTextを自前で結合するコードを書く必要がありそうです。例えば、前のOcrWordの末尾と後のOcrWordの先頭がともに英数字のときだけ半角空白を挟む、といったように。なお、この例ではカタカナの「エ」を漢字の「工」として認識してしまっています。
また、OcrLineの順序が入れ替わって認識される場合があります。各OcrLineの先頭のBoundingRectの位置関係を調べれば正しいOcrLineの順序関係を推定できると思いますが、これも実際に使うにあたっては注意が必要な点です。
OCRエンジンの使い方(WPF編)
WPFなどの.NET FrameworkアプリからOCRエンジンを使う場合も、基本的にはここまで説明してきたUWPアプリの場合と同じです。大きく異なる点として、UWPのAPIを使えるようにすることと、画像データをSoftwareBitmap型に変換しなければならないことがあります。
UWPのAPIを使えるようにする
OCRエンジンはWindows 10の最初(ビルド10240)から提供されているAPIなので、.NET Frameworkアプリ(WPF/Windows Forms/コンソールプログラムなど)のプロジェクトを作ったら、UwpDesktopを導入するだけです。
詳しくは、連載第2回「超簡単! WPFなどの.NETのアプリからUWPのAPIを使う ~日本語の読み仮名を取得するAPIを題材に」をご覧ください。
画像データをSoftwareBitmap型に変換する
OCRエンジンは画像データとしてUWPのSoftwareBitmap型しか受け付けません。そこで、.NETのアプリで扱っている画像データをどうやってSoftwareBitmap型に変換するかが問題になります。ここでは、WPFの場合を考えてみます。
WPFで画像データというとまずBitmapImageクラスが思い浮かぶかもしれませんが、画像フォーマットの変換を考えるとBitmapFrameクラスを中心にしたほうが便利そうです。BitmapFrameは、画像のエンコーダー/デコーダーが扱える型です。なお、BitmapFrameオブジェクトも、BitmapImageオブジェクトと同様に、ImageコントロールのSourceプロパティに直接セットして画像を表示できます。
WPFのBitmapFrameオブジェクトをUWPのSoftwareBitmapオブジェクトに変換するには、次のコードのようにします(⇒UwpSoftwareBitmapHelper.cs)。まずWPFの世界で、BitmapFrameオブジェクトをバイト配列に変換します。次に、UWPの世界に移って、バイト配列をUWPのストリームに変換した後、UWPのデコーダーを使ってSoftwareBitmapオブジェクトに変換します。
using System;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
// UWPのSoftwareBitmap関連のエイリアス(UWP APIの使用箇所を明確にするため)
using UwpSoftwareBitmap = Windows.Graphics.Imaging.SoftwareBitmap;
using UwpBitmapDecoder = Windows.Graphics.Imaging.BitmapDecoder;
using UwpBitmapPixelFormat = Windows.Graphics.Imaging.BitmapPixelFormat;
using UwpBitmapAlphaMode = Windows.Graphics.Imaging.BitmapAlphaMode;
using UwpInMemoryRandomAccessStream = Windows.Storage.Streams.InMemoryRandomAccessStream;
using UwpDataWriter = Windows.Storage.Streams.DataWriter;
namespace WpfApp
{
public static class UwpSoftwareBitmapHelper
{
public static async Task<UwpSoftwareBitmap> ConvertFrom(BitmapFrame sourceBitmap)
{
// BitmapFrameをBMP形式のバイト配列に変換
byte[] bitmapBytes;
var encoder = new BmpBitmapEncoder(); // ここは.NET用のエンコーダーを使う
encoder.Frames.Add(sourceBitmap);
using (var memoryStream = new MemoryStream())
{
encoder.Save(memoryStream);
bitmapBytes = memoryStream.ToArray();
}
// バイト配列をUWPのIRandomAccessStreamに変換
using (var randomAccessStream = new UwpInMemoryRandomAccessStream())
{
using (var outputStream = randomAccessStream.GetOutputStreamAt(0))
using (var writer = new UwpDataWriter(outputStream))
{
writer.WriteBytes(bitmapBytes);
await writer.StoreAsync();
await outputStream.FlushAsync();
}
// IRandomAccessStreamをSoftwareBitmapに変換
// (ここはUWP APIのデコーダーを使う)
var decoder = await UwpBitmapDecoder.CreateAsync(randomAccessStream);
var softwareBitmap
= await decoder.GetSoftwareBitmapAsync(UwpBitmapPixelFormat.Bgra8,
UwpBitmapAlphaMode.Premultiplied);
return softwareBitmap;
}
}
}
}
このようにしてSoftwareBitmapオブジェクトが手に入ったら、前述したように、あとはOCRエンジンのインスタンスを作ってRecognizeAsyncメソッドを呼び出すだけです(⇒MainWindow.Ocr.csのRecognizeBitmapAsyncメソッド)。
まとめ
Windows 10に標準で搭載されているOCRエンジンは、日本語もそこそこ認識してくれます。入力の補助としてOCRを使うアプリがいろいろ考えられると思います。
OCRエンジンはUWPのAPIとして提供されているので、もちろんUWPアプリから使えます。 WPFなどの.NETアプリからはSoftwareBitmap型の扱いがちょっと面倒になりますが、そこさえクリアできれば.NETアプリからもOCRエンジンは簡単に使えます。
このOCRエンジンのように、アプリから利用できるのにあまり知られていないWindows 10の機能がたくさんあります。「Windows 10のこの機能ってプログラムから使えるのかな?」と気になったら、UWP APIの一覧を眺めてみてください。連載第2回で説明したように、UI以外の多くのUWP APIは.NET Frameworkのアプリからも使えます。ちなみに、このUWP APIの一覧には開発中のAPIも載っています。名前空間に「Preview」と付いているのがそれで、本稿執筆時点ではWindows.AI.MachineLearning.Preview名前空間(マシンラーニングを利用するためのAPIですね!)やWindows.Devices.Input.Preview名前空間(キーボードやマウスなどの入力を扱う名前空間ですがPreviewでは視線入力を開発中)などが公開されています。
UWPアプリに興味が湧いてきた人へ
Windowsデスクトップ用アプリの開発はWindows Formsなら経験があるけど、ちょっとUWPアプリ開発も気になってきたという方へ。ぴったりの電子書籍を書きました!
『UWP アプリ開発 101 第2版: Windows Forms 開発者のための C# / XAML による UWP アプリ開発入門』 (BluewaterSoft 2017/7/1)
・Kindle版とUWPアプリ版があります。
・どちらも無料で最初の方を読めるので、とりあえず試し読みをしてみてください。
また、Microsoft公式のチュートリアルやたくさんのサンプルコードなどもあります。UWPアプリの開発環境を整えてUWPアプリのプロジェクトを作れるようになったら、これらのドキュメントやサンプルも参考にしてみてください。
