【第7回】HeartCore RoboでOCRを使ってみよう - 応用編

# はじめに
[前回](https://qiita.com/Go_Miyake/items/12143cc8b7daa8942e17) は、HeartCore RoboにおけるOCRの基本動作と、画面上のOCR対象領域を絶対値で指定するOCRの方法について記載しました。今回は画像認識（イメージ検索）を組み合わせて、OCR対象領域を相対的に指定してOCRを実行する記載します。

画像認識を用いる事で、「OCR対象範囲の座標決め打ち指定」を避ける事が出来ます。また取引先毎に異なるフォーマットの請求書・見積書なども対応が可能になります。例えば、取引先の書類レイアウトから会社A、会社Bと判別してから、それぞれ会社A、B用のOCR読み込みロボットの実行が可能になります。

（留意点）
今回は変数操作を多用するため、ある程度のプログラム知識がある方を対象としております。

# 画像認識を用いたOCRとは
OCR対象となる紙・イメージに必ず表示される箇所（例：納品書における「御中」や相手先企業のロゴ等のいわゆるテンプレート箇所）をOCR対象領域の「アンカー」としてHeartCore Roboのイメージテンプレートに登録しておきます。

OCR実行にあたり、イメージが表示されている箇所のアンカー座標（XY値）を取得、アンカー座標を元にOCR起点座標（OCR対象領域の左上）を決定させます。幅と高さは固定値を指定してOCR対象範囲の絞り込みを行います。

例１：アンカーとOCR対象起点が別の場合

「御中」を宛先取得用のアンカーイメージとして使用する。OCR対象の宛先は「御中」より左にあるので別途OCR起点座標を取得する必要がある。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/254242/d0f76aa0-40cc-4f12-ac1c-4426884643f8.png)

例２：アンカーとOCR対象起点が同じ場合

「納品書番号」を番号取得用のアンカーイメージとして使用する。OCR対象は右にあるのでアンカー座標とOCR起点座標は同じに出来る。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254242/7d5b75e6-88a3-2080-7e98-18acb1d36a6b.png)

# 画像認識を用いたOCRを実行してみよう

## 事前準備
OCR対象のファイル（PDF）を用意の上画面に開く（倍率は100％）。前回使用したサンプルを今回も使用します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254242/34307225-1d81-38e9-1e40-23ac27c82e4d.png)

今回OCR対象は以下の赤枠で囲ったエリアです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/5a096119-cb66-751e-050e-bfcf0dda207a.png)

[前回](https://qiita.com/Go_Miyake/items/12143cc8b7daa8942e17)を参考にして上記赤枠をエリア指定してOCR実行、所定の結果が出ることを確認します。留意点は下部線に引っかからない・他のエリアに被らないように範囲指定を行います。

サンプルコード:

```javascript
//宛名をOCR
Compareto method="tocr" cmparea="x:84,y:356,w:384,h:29" language="jpn"
Log {_TOCR_TEXT}

//納品書番号をOCR
Compareto method="tocr" cmparea="x:727,y:280,w:142,h:18"
Log {_TOCR_TEXT}

Report "results.xml"
```

# 画像認識を利用してアンカー座標を取得する - 上記例1のパターン

アンカー座標を取得するにもまずはCompartoコマンドでイメージ指定を行います。
[イメージ指定する際のComparetoコマンドの基本的な動作](https://qiita.com/Go_Miyake/items/40db30fedd1c9ffb7db1#compareto%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)

1. OCR実行行の直前に空白行を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/a796ce56-a97b-635a-2034-5c48e6ed52e2.png)

2. 右クリック > コマンドを作成 > Broswer -File > Comparetoを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/0f6fe400-d9d0-59be-8329-93dd8923129a.png)

3. 緑十字ボタン（新規テンプレートイメージを作成または・・）をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/14b9bc3f-6fd4-0242-77c6-a2cc6dc720dc.png)

4. ファイル名にアンカーイメージ名を指定（例：御中.png） > 開く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/69287e18-8f4b-5db7-c0a2-050766123983.png)

5. 「御中」イメージを範囲指定 > クリックポイント（赤十字）を指定範囲左上に指定 > 決定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/5d7058fe-2d5b-4d6c-ae3d-dafdb5d08de7.png)
**ポイント1：イメージ左上に指定したクリックポイントをアンカー座標として使用します**

6. イメージを取得 > 比較をクリック > 「検索一致あり」と表示される事を確認 > 一致を表示をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/1d9fa24d-0e40-33d4-fa12-723ce48b41f8.png)

7. HeartCore Roboが「御中」の箇所を認識(赤線で表示)している事を確認 > 右上のxボタンで確認画面をクローズ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/2fd3b2de-9807-496c-17b2-3ecd3a356e0d.png)

8. プロパティ画面のOKをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/cf80e9de-8889-a428-134c-0df93d9a17bc.png)

9. Compareto... method="search2"コマンドが追加されたのを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/691202bc-a2f6-b0f0-6b71-66990b419007.png)
*Compareto method="search2"と指定された場合はイメージ検索になります*
*Compareto method="tocr"と指定された場合はOCRが実行されます*

10. 追加されたCompareto ..method="search2"コマンド1行のみをドラッグしてハイライト > メニュー部分の「選択」から選択したコードを実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/5dfa6a63-6ac0-c9f3-7fa0-2288b022953e.png)
*イメージ検索とアンカー座標取得のみ行われてOCRは実行されません*

11. 「実行ログ」とあるタブの２つ右横の変数タブ（カーソルを合わせると「現在のスクリプトのコンパイルまたは実行によって作成された変数を表示」と表示されるタブ）をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/55e5744e-8681-940f-7cc7-51d18d2f343f.png)

12. 実行変数タブをクリック > 現在表示されている画面内のアンカーイメージと座標（クリックポイント）が変数(*"\_COMPARETO_CLICK_X"*と*"\_COMPARETO_CLICK_Y"*)に取得されているのを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/1f5cd688-6934-a8b9-77d9-c8d3727e4ecd.png)

13. 直下のCompareto method="tocr"のOCR行 > OCR範囲を絞り込むcmparea=引数内のx:で指定されている座標値を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/4684447c-41f3-9cf9-89fd-e64421a4eaa6.png)

14. 選択したx:座標値を削除 > 変数名*"\_COMPARETO_CLICK_X"*を選択 > 右クリック > 変数名をアクティブエディタへコピーを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/2f3e3b43-23be-3915-6606-ceee79d48a04.png)
*選択したx:座標値を削除して暫くするとCompareto method="tocr"行に赤線が表示されますが、これは本来あるべきx座標が指定されていないシンタックスエラーを動的に検知してアラート表示される機能です*

15. 変数名がエディタ（スクリプト部分）のx:座標値部分へコピーされたのを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/09ba6176-769e-3fb5-b47d-231a2c913f1c.png)

16. 同様にcmparea=引数内のy:へ変数名*"\_COMPARETO_CLICK_Y"*をコピー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/3067e32b-9269-8b47-8a18-e4b971cc3bef.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/1d5617c6-1ecf-f4f0-78a8-1ac31683c96d.png)

17. OCR起点座標調整のために*"\_COMPARETO_CLICK_X"*で格納されているx座標値から対象の幅(w:で指定されている値)を引く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/7643ccd6-3921-0ba6-b3ec-495d5cb7b696.png)

18. 宛名部分OCRの3行をハイライト > 選択実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/f67bdd96-d440-00d3-abe0-5cf0d5458e7e.png)

19. 実行したOCR結果（読み取り結果）が文字列として格納されるシステム変数*"_TOCR_TEXT"*に結果が格納されているのを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/a791eb14-cce5-0566-4012-9eae07f152e1.png)

*注： 2019年8月時点での最新Tesseractは4.xであり、結果の末尾には"↑"が出力されるようになっています。切り取るには[String trim](https://www.heartcorerobo.com/hcrobodesktop/docs/scripting/commref.html#string)を使用します*

以下結果を"宛名"変数に格納するサンプルスクリプト

```javascript
//宛名をOCR
Compareto "御中.png" method="search2"
Compareto method="tocr" cmparea="x:{_COMPARETO_CLICK_X}-384,y:{_COMPARETO_CLICK_Y},w:384,h:29" language="jpn"
String "trim" "{_TOCR_TEXT}" var="宛名"
Log {宛名}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/cfa31e6f-f1d8-5957-395f-ee82e2e935fe.png)

# 画像認識を利用してアンカー座標を取得する - 上記例2のパターン

1. Compareto method="search2"を追加 > 「納品書番号」の右上をアンカー座標として指定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/7acc1d40-6c01-2d84-780e-a1148bff8a8f.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/33afa2b7-c8bb-1f6b-27df-ac7ee571c77d.png)

2. 直下のCompareto method="tocr"のOCR行 > x:とy:座標値を上記例１と同様に"*\_COMPARETO_CLICK_X"*と*"\_COMPARETO_CLICK_Y"*に置き換え
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/2956b475-385b-30bb-27f2-480cf136dd56.png)
*今回のアンカー座標はOCR範囲の左側なのでX:座標の調整は必要ありません*

3. 納品書番号部分OCRの3行をハイライト > 選択実行 > 結果確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/686b3010-7224-2bb5-a240-f04a9c3872e5.png)

```javascript
//納品書番号をOCR
Compareto "納品書番号.png" method="search2"
Compareto method="tocr" cmparea="x:{_COMPARETO_CLICK_X},y:{_COMPARETO_CLICK_Y},w:142,h:18"
String "trim" "{_TOCR_TEXT}" var="納品書番号"
Log {納品書番号}
```

# アンカーイメージ使用した場合のOCR結果が思わしくない場合
アンカーイメージを起点としたOCR範囲指定を調整する必要があります。例えば対象範囲の文字が見切れる状態でOCRを掛けた場合には結果が大きく異なります。

例：
「見積書番号」に対してOCR実行したが、"180328001"ではなく"1RN29RaNN1"と結果が帰ってきた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/1e9bb163-df0e-1cc0-274c-4ebf1577c30f.png)

原因：
Compareto method="tocr"で指定しているOCR範囲の高さ（h:の値)が小さいため。
アンカーイメージ(納品書.png)の高さが18にも関わらず、OCR範囲の高さが14であるのが原因です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/53a11717-e9f5-b83d-bdfb-876983c5bd24.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/bfcf6c7e-7206-8af4-3934-1863c1539e9e.png)

対応：
Compareto method="tocr"で指定しているOCR範囲の高さ（h:の値)をアンカーイメージと同じ程度の値に調整します。

h:14→h:18へ変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/21e808aa-9f8e-2257-4315-441ed74e552b.png)
↓
想定通りの結果になります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/b4fc4d3c-6034-532f-2722-8435cabc07c8.png)

また、HeartCore Roboにある[Screenshot](https://www.heartcorerobo.com/hcrobodesktop/docs/scripting/commref.html#screenshot)コマンドを使用してデバッグ的にOCRの範囲を確認する事が可能です。Screenshotコマンドは範囲指定によるピンポイントなスクリーンショット取得が可能なので、OCR範囲指定と同じ方法で範囲指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/b6d51303-2399-3658-f916-a4b31d3ecffe.png)

```java

//納品書番号をOCR
Compareto "納品書番号.png" method="search2"
//OCR範囲のスクリーンショット取得
Screenshot "納品書番号OCR範囲.jpeg" area="x:{_COMPARETO_CLICK_X},y:{_COMPARETO_CLICK_Y},w:145,h:18"
Compareto method="tocr" cmparea="x:{_COMPARETO_CLICK_X},y:{_COMPARETO_CLICK_Y},w:142,h:18"
String "trim" "{_TOCR_TEXT}" var="納品書番号"
Log {納品書番号}
```

Screenshotコマンドで取得したスクリーンショット画像は、Projectフォルダ直下のReportsフォルダ内、実行時毎に自動で作成されるフォルダから確認出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/d260a9cc-e84f-84df-b211-2b3b8d9243be.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/58f0002d-8def-65d4-9359-777446ce1c51.png)

上記のOCR結果が思わしくない場合(h:14と指定した場合)にはOCRの対象が見切れているのが判明します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/0c4a85eb-6cdf-570c-aacf-a86215ffe410.png)


*Reportsフォルダの場所は、変数タブ > コンパイル変数から”\_PROJECT_REPORT_DIR”変数の値を確認します。*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/7b26f719-8c9c-142b-33e0-9ea47ee43d6f.png)

# 実践版OCRロボットスクリプト
PDFを開いて書類の表示倍率を100％に調整、PDF内にあるページ分OCR処理を行うサンプルスクリプトです。
![OCRサンプル.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/baad4929-8550-79b9-9cf8-1bdb3301287c.gif)

結果：
2ページ分の「宛先」「納品書番号」OCR結果がテキストファイルに出力されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/73f621d5-a7b3-7902-152a-cf4af759d2ef.png)

事前準備として、Acrobat Readerの倍率指定の箇所のイメージ(表示パーセンテージ.png)と次ページへ遷移させるアイコンイメージ(次ページ.png)を取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/fc7c8a90-058b-500d-9ce5-5d564e2d92c8.png)

以下スクリプト
各コマンドの詳細については[HeartCore Roboオンラインマニュアル](https://www.heartcorerobo.com/hcrobodesktop/docs/scripting/commref.html)を参照ください。

```java

//OCR対象のPDFファイルパス
Var PDFFilePath="C:\HCRTEMP\納品書.pdf"

//「ファイル名を指定して実行」（Windows+R）を使用してAcrobat Readerを起動
//PDFファイルパスを引数として指定する
Press "Windows+R" wait="500"
Paste "acrord32 {PDFFilePath}" wait="500"
Press "ENTER"

//Acrobat Readerが開くのを待つ ｰ　倍率指定が表示されるのを待つ（最長30秒）
Waitfor "match" method="search2" template="表示パーセンテージ.png" timeout="30s" ontimeout="Exit 1"
//倍率指定が表示されたらクリックポイントをクリックして倍率表示を変更させる
Mouse click to=x:{_COMPARETO_CLICK_X},y:{_COMPARETO_CLICK_Y}
//100とタイプしてEnterを押す - 表示倍率を常に100%とする
Typeline "100" wait="1s"

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

//PDFファイル内のページ分ループ
for (;0==0;) {
   
    //宛名をOCR
    Compareto "御中.png" method="search2"
    Compareto method="tocr" cmparea="x:{_COMPARETO_CLICK_X}-384,y:{_COMPARETO_CLICK_Y},w:384,h:29" language="jpn"
    String "trim" "{_TOCR_TEXT}" var="宛名"
    Log {宛名}
    //OCR出力結果用テキストファイルへ結果書込
    File "append" text="{宛名}\n"

    //納品書番号をOCR
    Compareto "納品書番号.png" method="search2"
    Compareto method="tocr" cmparea="x:{_COMPARETO_CLICK_X},y:{_COMPARETO_CLICK_Y},w:142,h:18"
    String "trim" "{_TOCR_TEXT}" var="納品書番号"
    Log {納品書番号}
    //OCR出力結果用テキストファイルへ結果書込
    File "append" text="{納品書番号}\n"
    
    //次ページがあるかのチェック
    Compareto "次ページ.png" passrate="85" method="search2"
    if ({_EXIT_CODE} == 0) {
        //次ページがある場合にはアイコンをクリックして次ページ表示
        Mouse "click" wait="1s" to="x:{_COMPARETO_CLICK_X},y:{_COMPARETO_CLICK_Y}"
    } else {
        //次ページがない場合（アイコンがグレーアウト）にはループを抜ける
        Break
    }
}

//OCR出力結果用テキストファイル保存
File "close" save="true"

Report "results.xml"
```

# OCR活用のベストプラクティス
OCRの結果はOCRエンジン（例：Tesseract, ABBYY, Google Vision等）に大きく依存します。インストール型OCRの場合、辞書ファイルをチューニングすることで精度の向上は出来ますが、100％の精度を求めるのは難しいのが現実です。ただある程度のOCRの「クセ」を把握出来れば後続の[String replace](https://www.heartcorerobo.com/hcrobodesktop/docs/scripting/commref.html#string)でOCR結果を調整することが可能になります。

例１：
数字のみの文字列（商品コード等）対象のOCR結果で"1"がアルファベットLの小文字"l"と認識される場合や、”0"がアルファベットの"O"と認識される場合

> *String "replace" "{_TOCR_TEXT}" string="l" var="var" replacement="1"*
> *String "replace" "{_TOCR_TEXT}" string="o" var="var" replacement="0"*


例２：
平仮名”の"が"@"と認識される場合

> *String "replace" "{_TOCR_TEXT}" string="@" var="var" replacement="の"*


HeartCore Roboの強みである、画像認識を活用しながらノイズ(余計な箇所)が入らないようOCRの範囲をピンポイント指定した上で、その箇所毎に一番精度が出るようOCRオプションを変えてみるのも一つの方法です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254242/5246da3d-3a68-8e3d-84c7-03a17ec5118d.png)

また、表示倍率を上げると潰れたような字や画数が多い文字の認識精度が若干上がる傾向にあります。


# OCRについての個人的な考察
OCRの精度を追求すると「ハマる」事が多々あります。ある程度の精度が出た時点で割り切って使用、日々の使用時における誤認識パターンを把握しながら、スクリプトでの修正を追加する継続的なチューニング運用が望ましいと考えています。