HeartCore Roboで請求書をOCR [GoogleVisionAPI]

RPAブームかつOCRブームですね。

CMSで有名なHeartCoreさんのRPA、Roboシリーズを触る機会があったので、OCRしてみます。(Desktop版です)

デベロッパーの記事を全然見かけないので、参考にしたのは公式とリファレンス。

公式のQiitaはこちら。
qiita.com

リファレンスはこちら。
www.heartcore.co.jp

ところでどうでもいい話。
リファレンスではコマンドの次に指定するメソッドにダブルクォーテーション「""」が付いてないし、実際に付けなくても動くんだけど、右クリックのコマンド挿入GUIで挿入すると「""」が付くんだよね…。
リファレンスを読み込むと、日本(HeartCore)じゃないどこかの海外のエンジン?を使っているのだと思われる*1Excelの「セル」が「細胞」と訳されていたりするから)が、なんとなく気持ち悪いのでした…。



さて、目次です。

1、OCRの設定をする

まずはOCRの設定をします。
OCRエンジンは3種類から選択可能。

  • GoogleVision:クレジットカードを登録すれば利用可能(無料分を使い切ると課金される)
  • ABBYY:専用ライセンスが必要
  • Tesseract:OSSなので完全無料

Tesseractも試したのですが、かなり精度が低かったので今回はGoogeVisionで書いてきます。

トップメニュー[OCR] > 優先OCRエンジンを選択
f:id:hiro-29:20191206115927p:plain

優先OCRエンジンで「GoogleVision」を選択 > APIキーで利用するので、APIキーを追加して接続テスト
GoogleVisionのAPI Key取得については前回記事「GoogleVisionAPIで画像タグを取得してみた(チュートリアル実行) - Dive into Numbers」を参照。
f:id:hiro-29:20191206120353p:plain

簡単!!!
これでOCRできちゃいます。

2、画像ファイルを読み込む

OCR元となるファイルを読み込んで行きます。
ここは公式Qiita記事通りですが、請求書の複数項目を読み込むので、実際にAPIを呼び出す部分(Compareto method="tocr")はプロシージャにしました。
なので画像ファイルを読み込んでプロシージャを呼び出す部分はこんな感じ。

//OCR対象のPDFファイルパス
Var PDFFilePath="C:\Users\xxxx\Desktop\OCR"

//ファイルの一覧を取得
Exec "cmd.exe /c dir /b {PDFFilePath}"
Log {_EXEC_OUTPUT}

//ファイル名だけを分割して「FileName」に格納
String "split" "{_EXEC_OUTPUT}" var="FileName" pattern="\n"

//OCR出力結果用テキストファイル作成
File "create" file="C:\Users\xxxx\Desktop\OCR結果.txt"


//一つずつ上から読み込みながら処理を実行
for (i=1; {i}<{_STRING_COUNT}+1; i={i}+1) {
    
    if ("{FileName{i}}" startswith "除外_") {
    }else{
        //ファイルを開く
        Press "Windows+R"
        Paste "{PDFFilePath}\{FileName{i}}"
        Press "ENTER" wait="1s"
        
        //Acrobat Readerが開くのを待つ ー 倍率指定が表示されるのを待つ(最長30秒)
        Waitfor "match" passrate="50" method="search2" template="起動判定.png" timeout="30s" ontimeout="Exit 1"

        //起動したら100パー表示にする
        Press "Ctrl+1"
        
        //処理ファイル名を記載
        File "append" text="{FileName{i}}\n"
        
        //プロシージャを呼び出す
        "ocr_txt_get"   
    }
}

悩んだのは表示倍率ですね。
OCRする箇所を、画像一致(Compareto)で指定するので、確実に比較画像と一致する画像が画面に表示されなければいけません。
そもそも精度的に「100%」でよいのか、そしてファイルの下の方を読み込みたいならそれをどうやって画面に収めるか、というのが問題になってくるかなと思います。
今回は「150%」とかでも試したのですが「100%」の方が精度が良かったのと、運良くファイルの上部しか読み込まない資料で試したので、素直に100%表示としています。

で、今回は100%表示を「Ctrl+1」で対応できるアプリだったので良いのですが、これを「120%」などショートカットが無い場合などは、表示倍率を指定する箇所を画像一致させて「Press」させないといけないので、これはけっこう難しかった。
(アプリ側の問題で表示倍率のGUIがウィンドウサイズによって変わったりするので、これはAcrobatとかOCRに限らず、画像一致させる場合はかなり気を使う必要がありそうだなあと思います。)

3、OCRを実行してテキストを出力する

さて、いよいよOCRを実行するプロシージャを作ります。
公式には「次ページのチェック」のループが入っていますが、今回は1ページ目のものしか読み込まなかったのでループは作ってないです。
こんな感じ。

procedure "ocr_txt_get" {
    //請求番号
    Compareto "請求番号.png" passrate="50" method="search2"
    Compareto method="tocr" cmparea="x:{_SEARCH_X}+{_COMPARETO_TEMPLATE_WIDTH},y:{_SEARCH_Y},w:70,h:22"
//    Screenshot "Desktop\work\YYMMDD_OCR読み取り\請求番号-test.jpeg" area="x:{_SEARCH_X}+{_COMPARETO_TEMPLATE_WIDTH},y:{_SEARCH_Y},w:70,h:22"
    String "trim" "{_TOCR_TEXT}" var="請求番号"
    Log {請求番号}
    //OCR出力結果用テキストファイルへ結果書込
    File "append" text="請求番号:{請求番号}\n"

    //発注数
    Compareto "発注数.png" method="search2"
    Compareto method="tocr" cmparea="x:{_SEARCH_X}+{_COMPARETO_TEMPLATE_WIDTH},y:{_SEARCH_Y},w:130,h:{_COMPARETO_TEMPLATE_HEIGHT}"
//    Screenshot "Desktop\work\YYMMDD_OCR読み取り検証\発注数-test.jpeg" area="x:{_SEARCH_X}+{_COMPARETO_TEMPLATE_WIDTH},y:{_SEARCH_Y},w:130,h:{_COMPARETO_TEMPLATE_HEIGHT}"
    String "trim" "{_TOCR_TEXT}" var="発注数"
    Log {発注数}
    File "append" text="発注数:{発注数}\n"
}


ちょっと見にくいので各行を分かりやすく。

OCRする位置を特定するため、画像検索

ここは読み込みたい情報のタイトル(項目名)。

項目名などが付いていなかったとしても、「この幅のこの並びの下に入る数字」をOCRしたいなら、そこを特定できる画像ですね。
今回はレートは基本「50」が最も安定していたので、全部50で実行しました。メソッドも「search2」。
このへんは状況によると思いますので、環境が特定されているならば余計に試しまくったほうが良いでしょう。

Compareto "請求番号.png" passrate="50" method="search2"
OCRを実行する

ここは定義済み変数を活用しています。
①で「検索画像の左上」の位置情報({_SEARCH_X}{_SEARCH_Y})をGETしたので、そこに、検索で使った画像自体の幅や高さなどを追加して、「OCRしたい場所の左上」の位置情報を指定します。

Compareto method="tocr" cmparea="x:{_SEARCH_X}+{_COMPARETO_TEMPLATE_WIDTH},y:{_SEARCH_Y},w:70,h:22"

できるだけ固定値を使いたくなかったのですが、最後の「w:70,h:22」はここでしか使わない固定値なので直書きしています。
もしも読取り対象のセルサイズとかが一律なら、これは変数で持っておいたほうがよいでしょうね。

デバッグ用のキャプチャをとる

これはコメントアウトしていますが、「実際にどこをOCRしたのか」が、これを使わないと分からないので、ソースとしては問答無用で突っ込んでおくのが良いと思います。
というか、スクリプト作成中はこれがないとデバッグできないので必須です。

//    Screenshot "Desktop\work\YYMMDD_OCR読み取り\請求番号-test.jpeg" area="x:{_SEARCH_X}+{_COMPARETO_TEMPLATE_WIDTH},y:{_SEARCH_Y},w:70,h:22"
④ 出力する

ここはもうそのままです。
{_TOCR_TEXT} というのがOCR結果ですので、これをファイルに突っ込みます。
Logも無くてもいいんだけど、ほぼデバッグ用。

    String "trim" "{_TOCR_TEXT}" var="発注数"
    Log {発注数}
    File "append" text="発注数:{発注数}\n"

実行速度

気になる実行速度。
条件は以下の通り。
・対象PDF:5ファイル(計5ページ)
・読取り箇所:1ページにつき7箇所
・PC:メモリ4GのWindows10ノートPC(非力)

これで確か5秒~10秒ほどだったと思います。
実務で一ヶ月分を数十枚回す、ということになると数分かかるんじゃないでしょうかね。

さいごに(気になったこと)

OCRの精度が云々というより「OCR場所の指定」を画像一致で行う、というのがけっこう面倒くさい。
これ、スクロールしないと画面に入らない場合ってどうするのかなぁ。「見つからなかったら画面スクロールする」みたいな泥臭いコード突っ込むしかないんだろうけど。

プログラマの端くれとしては、できるかぎり環境を固定しないでも動く物を作りたいので、画像一致はやっぱり大変。
HeartCoreは「環境は固定してくれ(できれば専用のPCを用意してくれ)」と最初っから言っていたので分かってはいましたが。

そして、ノンプログラマが画像一致をデバッグするのって相当イライラするんじゃないか…とおせっかいなことを思ったのでした。

*1:2019/12/09追記:T-Plan社さんのOEMなんですね。