(2017年12月) PythonとOpenCVをこれからやってみる - 5 - 学習(1)

#はじめに
PythonもOpenCVもこれまでまったく触れてこなかったけど、ちょっとやってみたいと思ってはじめたので、色々と試しているという状況です。
前回『[(2017年12月) PythonとOpenCVをこれからやってみる - 4 - 座標設定 - Qiita](https://qiita.com/rrryutaro/items/7f75470586cf210af6d5)』は読み取り座標を指定するツールを用意しました。
今回はいよいよ、読込み精度を上げるための根本的な部分として、言語データを作成する部分を行なっていきます。

#言語データについて
最初にダウンロードした`jpn.traineddata`は基本的なものということもあり、精度はそれ程高くないようです。
本格的に使っていくのであれば、カスタマイズして行く必要があるでしょう。
Tesseractのコンフィグファイルを調整する事でも、精度を上げられるようですが、今回は先に言語データを調整する事にします。
作るデータは、OCRの確認で利用した画像の中の文字の"Python"が"pwhon"となっていたので、試しに"Python"だけの言語データを作ってみます。

#言語データを作成する
ざっくり調べていると何だか難しそうですが、簡単に出来るツールもあるようです。
その中でも今回は、jTessBoxEditorというのを使うことにしました。
[jTessBoxEditor - Tesseract box editor & trainer](http://vietocr.sourceforge.net/training.html)
ダウンロード先に行くと、最新はベータ版のようですが「jTessBoxEditorFX-2.0-Beta.zip」をダウンロードして解凍して実行します。（javaが無ければダウンロードしてパスを通すなりしておきます）

```:コマンドプロンプト
>java -Xms128m -Xmx1024m -jar jTessBoxEditorFx.jar
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/b2dda978-2d48-865f-cf05-e9766f65a3de.png)

[TIFF/Box Generator]のタブをクリックし、画面も広げます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/ceb6c533-d037-cb32-0e3f-17602edc83fb.png)
・[text2image]チェックボタン
　まだよくわかっていません。今回は使いません。
・[Input]ボタン
　作成するデータの文字列を設定したテキストファイルを取り込むことが出来るようです。
　今回は直接入力してしまいますので使用しません。
・[Output]テキストボックス、[...]ボタン
　ファイルの生成先の場所を指定します。
・[...]の次のテキストボックス
　"jpn"など、言語データの最初の3文字の部分を指定する。
　その後にフォント名を指定するので、被っていても大丈夫だとは思いますが、今回は"jp2"としてみます。
・その後のテキストボックスとボタン
　フォント名の指定と、フォントの指定。
　ボタンの方を押せば、フォントの選択のダイアログが表示されます。
　フォント名もそれに合わせて変わりますが、書きかえても大丈夫です。
　（最後の拡張子部分の".tif"はそのままに）
・anti-aliasing(アンチエイリアス)
　境界を滑らかにする指定と思われますが、とりあえず今回は使用しません。
・その後の色々
　よくわかっていません。今回はさわりません。
・[Generate]ボタン
　必要な入力が終わったらボタンを押す事で、tifファイルとboxファイルが生成されます。
　tifファイルは文字のイメージ、boxは各文字の座標と対応付ける文字を定義したファイルのようです。
・[Clear]ボタン
　入力内容などをクリアします。

では、次のように設定します。
フォントはChromeのDevToolsで確認したところ"-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Hiragino Kaku Gothic ProN,"メイリオ",meiryo,sans-serif;"となっていました。この内実施環境では"Segoe UI"があり、表示してみたところそれっぽいのでこのフォントを指定します。サイズは14pxです。
"Python"と入力します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/fc7e4eca-3ffd-a0e5-d952-bee98b3d1c77.png)

画面下部の[Box Layout]をクリックすると次のように表示されています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/60ed7289-255a-d946-b770-712ec6b396e2.png)

問題なさそうなので、[Generate]ボタンをクリックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/8e7781b1-c3b0-b8b0-35cd-92710b710b57.png)

指定した出力先に次のファイルが出力されました。
jp2.py.box
jp2.py.tif

それぞれ普通にファイルを見てみると次のようになっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/d318cfac-87f0-63e9-5dcc-2ae8e817b111.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/874da0f5-b003-de56-bbfd-f87a62aefcb3.png)

jTessBoxEditorFXにて[Box Editor]をクリックし、[Open]をクリックして、"jp2.py.box"を開いてみると次のように表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/be8c1701-de0a-114f-5b5f-b6e69e8602ef.png)

続いては、「font_properties」「word list」「frequent word list」というファイルを用意しておく必要があるようです。

・font_propertiesファイル
　{言語名}.font_properties
　今回の場合、"jp2.font_properties"
　中身は次のようにするみたいです。

```:jp2.font_properties
py 0 0 0 0 0
```

pyの部分がフォント名です。

・word listファイル
　{言語名}.words_list
 今回の場合、"jp2.words_list"
　中身は空でいいようです。

・frequent word listファイル
　{言語名}.frequent_words_list
　今回の場合、"jp2.frequent_words_list"
　こちらも中身は空でいいようです。

以下のようになりました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/1ac81812-79d9-ef7b-576b-e1e3b311378e.png)

続いて作成したファイルを元に学習を行なわせます。
jTessBoxEditorFXの[Trainer]タブをクリックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/4d088302-6aab-4e4f-db54-09f81ccf9eb4.png)
・[Tesseract Executables]
　tesseract.exeのパスの指定。デフォルトではjTessBoxEditorFXにあるtesseract.exeを見ているようなので、必要に応じて変更。
・Training Data
　作成したファイルの場所を指定。
・Language
　言語名を指定。今回の場合"jp2"
・その他
　あまりよくわかっていません。

[- - Training Mode - -]とあるコンボボックスをクリックして、[Train with Existing Box]を選択して、[Run]ボタンクリックで実行します。

次のように最終的に"** Training Completed **"が表示されれば問題ないでしょう。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/474bc967-1319-23ef-2e87-e2467bda13d5.png)

今回は中身が少ないので一瞬で終わりました。
次のようにファイルが作られています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/399d1ee8-2dc6-937b-bf51-16aa945e317e.png)

最後に、次のようにしてファイルをひとまとめにします。

```:コマンドプロンプト
c:\test>combine_tessdata jp2.
Combining tessdata files
Output jp2.traineddata created successfully.
Version string:4.0.0-alpha.20170804
1:unicharset:size=492, offset=192
3:inttemp:size=124990, offset=684
4:pffmtable:size=82, offset=125674
5:normproto:size=902, offset=125756
13:shapetable:size=112, offset=126658
23:version:size=20, offset=126770
```

これで、"jp2.traineddata"ファイルが作成されました。
よくわかっていないのですが、フォント名が入っていないので、一応ファイル名を"jp2.py.traineddata"としておきます。
そして、"Tesseract-OCR\tessdata"にファイルを配置します。

#言語データを指定して読み取りを行なうようにする。
まずはExcelに出力して、言語設定の欄を追加します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/eec4b4d6-b3c7-d53b-2ae3-0120f8f361f5.png)

前回作成したExcel側のJSONファイル出力の際に、文字形式を出力する部分をヘッダーが"Output"だったらとしていたので、セルの書式が文字型かで判断するように修正します。

```vbnet:OutputOMRJsonFileの判定部分のみ抜粋
If UsedRange(i, j).NumberFormat = "@" Then
```

```:出力結果
[{"X":19,"Y":10,"Width":785,"Height":94,"Language":"jpn"},{"X":19,"Y":127,"Width":57,"Height":25,"Language":"jp2.py"},{"X":89,"Y":127,"Width":65,"Height":25,"Language":"jpn"},{"X":19,"Y":228,"Width":117,"Height":31,"Language":"jpn"},{"X":19,"Y":297,"Width":747,"Height":79,"Language":"jpn"}]
```
`ocr.py`の言語指定部分をjsonで設定した値を指定するように修正します。

```py3:ocr.py
from PIL import Image
import sys
import pyocr
import pyocr.builders
import cv2
import numpy as np
import json 

tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)

tool = tools[0]

args = sys.argv
img = cv2.imread(args[1], cv2.IMREAD_GRAYSCALE)

#座標の指定は x, y, width, Height
file = open(args[2], "r", encoding="utf-8_sig")
boxs = json.load(file)

for box in boxs:
    #イメージは OpenCV -> PIL に変換する
    txt = tool.image_to_string(Image.fromarray(img[box["Y"]:box["Y"]+box["Height"], box["X"]:box["X"]+box["Width"]]),
    						   lang=box["Language"],
    						   builder=pyocr.builders.TextBuilder(tesseract_layout=6))
    print(txt)
```

```:実行結果
>ocr.py ocr.png ocr.json
(②0①⑦ 年 ⑫ 月 ) Python と OpenCV を こ れ か ら
や っ て み る - ① - は じ め の 一 歩
Pytnon
ocpancy
は じ め に
Python0 バ イ ソン ) を 中 々 使 う 機 会 が 無 く 、OpenC/ オ ー プ ン シ ー プ
 イ ) も 気 に は な り つ つ 使 う 橋 会 が 無 く 。
そ ん な 承 、 ち ょ っ と や っ て み よ う と 思 っ た の で 、 う か ら や る
 に あ た っ て の 導 入 や は じ め の 一 歩 な ど 。
邉 境 は Windows⑦ ⑥bt で や り ま す 。
```

結果は"Pytnon"。う～ん、おしい！！
"h"と"n"の差か・・・
前回は"pwhon"で"h"と"n"は区別できていましたから、定義のどこかが悪いのかもしれないですね。
ともかくもこれで、言語データの作り方もわかりましたし、個別に指定できますので、フォントなどに応じて読み取り精度を上げられるかと思います。

次はコンフィグなどで、読み取り文字の絞込みなどをどのように行なうか確認して行こうと思います。
数値のみ("0123456789")などの指定があるでしょうから、そうした設定をどのように行なうかですね。
後は、学習機能があるということは、今回のように読み取りがうまく行なえなかった文字について、教える事でも精度を上げられると言う事でしょうから、その辺もどのように行なうのかなども調べて行きたいと思います。

以上

<font color=red>【追記】このケースの場合、背景がグレーであったのが影響していたようです。次のように2値化すれば読み取れました</font>

```py3
ret, img2 = cv2.threshold(img, 180, 255, cv2.THRESH_BINARY_INV)
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/8984a958-f010-8257-abc2-01a12c891474.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/156225/e9d3e8d7-fabd-7779-6dac-1773f03ad28b.png)

```
>ocr.py ocr.png ocr.json
(②0①⑦ 年 ⑫ 月 ) Python と こ OpenCV を こ れ か ら
や っ て み る - ① - は じ め の 一 歩
Python
Ge)
は じ め に
0000⑧②0 に gi ES0①①⑧utduauo cz と z⑧0②①a②②leeiis t
そ ん な 折 、 ち ょ っ と や っ て み よ う と 思 っ た の で 、 今 か ら や る
 に あ た っ て の 尊 入 ゃ は じ め の 一 歩 な ど 。
```

ただし、他の場所が今度は問題ありなので、座標情報のjsonに、画像補正を行なうかといったパラメータなどを用意する形になるかとおもいます。


#参考にした記事など
- [文字認識エンジンTesseract OCRで学習 - danglingfarpointer's memoization](http://danglingfarpointer.hatenablog.com/entry/2015/01/28/215629)

#これまでの記事
|前回|一覧|次回|
|:--|:--:|--:|
|[4 - 座標設定](https://qiita.com/rrryutaro/items/7f75470586cf210af6d5)|[一覧](https://qiita.com/rrryutaro/items/933ec2ed754a9f3a2d3e)|[6 - コンフィグ(1)](https://qiita.com/rrryutaro/items/cb2a8682f1150e33ea81)|