MicrosoftOCRというのがあることを知りました。
Microsoft OCR をデスクトップのWFPアプリで動かす方法
え~WinRTのライブラリを使うの?Win32だとどうやるの?やり方知らない~
ということで調査してみました。
(情報的には未完成です。)
WinRT、なにそれ
前述のリンクのC#のコードでは、OCR実行コードがずいぶんあっさり書けちゃってるのですね><
public async Task<OcrResult> detect( SoftwareBitmap bitmap)
{
var ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages();
var ocrResult = await ocrEngine.RecognizeAsync(bitmap);
return ocrResult;
}
そもそも私はWinRTのコードを書いたことがありません。
そこで、
WinRT APIを使ってみる
を書き写してお勉強しながら、発展させていくことにします。
インクルードファイル
今回必要なのはこれくらい?
OCRは必須として、WRL、画像関係を追加しました。
# include <windows.foundation.h>
using namespace ABI::Windows::Foundation;
# include <wrl/wrappers/corewrappers.h>
# include <wrl/event.h>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
# include <windows.media.ocr.h>
using namespace ABI::Windows::Media::Ocr;
using namespace ABI::Windows::Storage::Streams;
# include <windows.graphics.imaging.interop.h>
using namespace ABI::Windows::Graphics::Imaging;
# pragma comment(lib, "runtimeobject.lib")
SoftBitmap
WinRTで使用する画像フォーマットらしいのですが、Windows関係でもフォーマットが色々あり過ぎです><
WICから引っ張ってこれるようなので、こんな感じに書いてみました。
ここまではついていけるレベルw
ISoftwareBitmapNativeFactory *pSwBMPNFactory;
CoCreateInstance(CLSID_SoftwareBitmapNativeFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSwBMPNFactory));
ISoftwareBitmap *pSwBMP;
pSwBMPNFactory->CreateFromWICBitmap(reinterpret_cast<IWICBitmap *>(pWIC_BS), TRUE, IID_PPV_ARGS(&pSwBMP));
いよいよWinRTでOCRを叩いてみる
といってもCOM的な書き方を強要してくるので、あまり新鮮味はないです。
IOcrEngineStatics *ocrEngineStatics;
RoGetActivationFactory(HStringReference(RuntimeClass_Windows_Media_Ocr_OcrEngine).Get(), IID_PPV_ARGS(&ocrEngineStatics));
IOcrEngine *ocrEngine;
ocrEngineStatics->TryCreateFromUserProfileLanguages(&ocrEngine);
なんで非同期の関数しか用意してないの?
WinRTのAPIの多くは非同期で実行する(名称は~Async)しか選択肢がないようです。
OCRを実行して結果を待つわけですが、上述のC#では
var ocrResult = await ocrEngine.RecognizeAsync(bitmap);
と、awaitを使っていました。
これをWin32/C++で書くとこうなるようです。
エラーチェックも端折ってかなり整理したつもりですが、ちょっと難解ですよね><
struct OCR_RESULT{
HRESULT hr;
HANDLE hEvent;
IOcrResult *OcrResult;
};
OCR_RESULT res={S_FALSE, CreateEvent(NULL, TRUE, FALSE, NULL), NULL}, *pres;
pres=&res;
typedef IAsyncOperation<ABI::Windows::Media::Ocr::OcrResult*> Operation;
Operation *async;
ocrEngine->RecognizeAsync(pSwBMP, &async);
typedef IAsyncOperationCompletedHandler<ABI::Windows::Media::Ocr::OcrResult*> OperationHandler;
auto callback=Microsoft::WRL::Callback<OperationHandler>([pres](Operation *async, AsyncStatus status)->HRESULT
{
HRESULT hr=S_FALSE;
switch (status)
{
case Started:
break;
case Completed:
case Error:
async->GetResults(&(pres->OcrResult));
break;
case Canceled:
break;
}
pres->hr=hr;
SetEvent(pres->hEvent);
return hr;
});
ComPtr<OperationHandler> handler(callback);
async->put_Completed(handler.Get());
WaitForSingleObject(pres->hEvent, INFINITE);
で、これ、実は正常に動作しません><
追記:) このコードをUIスレッドとは別のスレッドで実行したらさっくり動きました。
ステータス取得のループ自体が動いてない感じで、いつまでもイベントシグナルを待ち続けます。だめじゃん><
イベントのシグナル待ちをやめて、Sleepを1秒はさんで待ってから見に行くと、ちゃんと画像から文字認識して結果を取得できているんですけどね~
仕方ないので、OCR自体の動作実験は
while(async->GetResults(&pres->OcrResult)!=S_OK){
Sleep(1);
}
な感じで、結果取得までSleepのループで待つように対処して遊んでました。
OCR自体の精度はあまりよくないかな。
1枚1枚から漏らさず文字を検出するよりも、大量取得した画像の検出結果を統計的処理にかけるってのが有用な使い方でしょうか?