tesseract + pyocr使い方メモ

ひょんな理由で tesseract の使い方を知る必要に迫られたので、簡単に調査。取り急ぎ、次の情報が得られることが目標。

  1. 認識された文字(≠単語)のテキストデータ (UTF-8)
  2. それらの位置情報(左上点の座標と幅と高さ)

もちろん、プログラム的にこれらの情報を分析等に使いたいので、Python から tesseract を使う場合によく使われている pyocr もインストールして、これを使って上記を取り出してみる。

tesseract とは

tesseract は Hewlett-Packard で開発された OCR エンジンで、2005 年にオープンソース化された後、2006 年からは Google が開発を進めている。本日2019年6月9日の最新バージョンは 4.0。

github.com

なお Python から tesseract を使う場合、pyocr というパッケージが有名なもよう。こちらも今回は使ってみる。

gitlab.gnome.org

導入方法

4.0 の Windows バイナリは公式で配布されておらず、Ubuntu 18.04 では apt でインストールできるとのこと。そのため Windows 環境でのお試しならば WSL で Windows 上に Ubuntu 18.04 環境を作り、そこに apt でインストールするのが(自分的には)楽だった。

$ apt install tesseract-ocr-jpn

tesseract 自体は多言語対応しており、各言語向けの学習データも apt でインストールできる。上記コマンドは日本語用の学習データをダウンロード対象に指定しており、その結果この依存関係に設定された tesseract 本体も同時にインストールしてくれる。なお Python から tesseract を使うのに便利なパッケージ pyocr についても apt でインストールできる (python-pyocrpython3-pyocr)。OS 標準の Python 環境で使いたい場合はこちらもインストールしておくと良いだろう。Anaconda 等で非標準の Python 環境を作っている場合は普通に pip install pyocr しておく。

tesseract の動作確認(コマンドを使用)

最低限の動作確認として、tesseract コマンドで OCR 処理を実行してみる。

tesseract をコマンドラインインタフェースで使う場合、もっとも単純な方法は次のように文字認識をかけたい画像ファイルと、結果のファイル名をコマンドラインに指定する:

$ tesseract image.tiff output -l jpn  # output.txt が生成される

出力先ファイル名の拡張子は出力形式に応じて自動的に補完されるので指定不要らしい。標準形式は .txt で、認識された文字列が平文テキストで格納されただけのもの。何かの分析や他のプログラムに接続する場合、もっと情報量の多い TSV 形式などにすると良いだろう。例えば次の画像 "image.png" を認識した結果を TSV に出力する例は、次のようになる。

f:id:sgryjp:20190609170351p:plain
OCRテスト用画像 "image.png"

$ tesseract image.png output -l jpn tsv
$ column -ts $'\t' output.tsv
level  page_num  block_num  par_num  line_num  word_num  left  top  width  height  conf  text
1      1         0          0        0         0         0     0    467    140     -1
2      1         1          0        0         0         62    64   346    48      -1
3      1         1          1        0         0         62    64   346    48      -1
4      1         1          1        1         0         62    64   346    48      -1
5      1         1          1        1         1         62    77   190    32      92    tesseract
5      1         1          1        1         2         261   66   66     46      935      1         1          1        1         3         328   64   80     46      94    試す

うん、きっちり認識できている…と思いきや、画像と座標を照らし合わせると「を」と「試す」の切れ目が不自然な位置に来ているね。「言」と「式」の間に両単語の切れ目があると認識されているようだ。まあ、ともあれ。インストールした tesseract が動くことは確認できたので、良しとしよう。

なお最後の引数に指定した tsv は tesseract のインストール先に置かれた設定ファイルの名前だったりする。その中には KEY=VALUE の形で設定値が並んでおり、設定ファイルを介さずともコマンドラインオプション -c を使えば同じように設定値を指定できる。たとえば文字単位での認識結果を出力する設定ファイル makebox の内容は tessedit_create_boxfile=1 とだけ書いてあるので、次のようなコマンドラインで実行すれば同じ効果を得ることができる:

$ tesseract image.png output -l jpn -c tessedit_create_boxfile=1
$ column -ts' ' output.box
t   62   32  77   63  0
e   80   31  97   57  0
s   106  32  119  57  0
s   127  32  141  57  0
e   148  31  161  57  0
r   176  32  188  57  0
a   191  32  210  57  0
c   216  31  234  57  0
t   237  32  252  63  0261  28  326  74  0328  30  358  76  0377  30  408  73  0

続いてプログラム的な処理につなげるべく Python からの呼び出しを試してみよう。

pyocr を通した利用

何はともあれ pyocr を import する。また画像のロード用に Pillow も import しておく。

>>> import pyocr
>>> from PIL import Image

pyocr は実行環境にインストールされた OCR ツール類をスキャンしてリストアップする pyocr.get_available_tools() 関数を提供している。最初はこれで tesseract が認識されていることを確認しておくと良さそうだ。

>>> tools = pyocr.get_available_tools()
>>> len(tools)
2
>>> tools[0].get_name()
'Tesseract (sh)'
>>> tools[1].get_name()
'Tesseract (C-API)'

ちなみに get_available_tools() が返しているのは Python の module で、上記の例では一つ目の tool として tesseract コマンド実行で連携する pyocr.tesseract が、二つ目の tool として libtesseract の API 利用で連携する pyocr.libtesseract が返ってきている。これらの間には機能差があるため、場合によって使い分けが必要になると思われる。また、たとえば tesseract コマンドを必ず使うのであれば get_available_tools() を使わずに、直接 tool = pyocr.libtesseract などとしても良い。

さて、文字認識を実行するには上記のように取得したモジュールの image_to_string() を呼べばよい。この関数には、OCR を実行したい画像データを指定するほか、認識に使用する言語、領域の切り出し方式(?)を指定する。ここでは pyocr.tesseract(コマンド連携版)を使い、当初の目的通り文字単位での切り出しを試してみる。

>>> ocr = pyocr.get_available_tools()[0]  # または ocr = pyocr.tesseract
>>> im = Image.open("image.png")
>>> builder = pyocr.tesseract.CharBoxBuilder()
>>> char_boxes = ocr.image_to_string(im, lang="jpn", builder=builder)
>>> len(char_boxes)
12
>>> "|".join(box.content for box in char_boxes)
't|e|s|s|e|r|a|c|t|を|試|す'
>>> for box in char_boxes:
...     # box.position は左下を原点とした ((min-x, min-y), (max-x, max-y)) らしい。
...     # ここでは左上を原点とした x, y, width, height に変換してみる
...     x = box.position[0][0]
...     y = im.height - box.position[1][1]
...     width = box.position[1][0] - x
...     height = im.height - box.position[0][1] - y
...     print("\t".join([
...         box.content,                          # 文字
...         str(x), str(y), str(width), str(height),
...         str(box.confidence),                  # 確信度
...     ]))
...
t       62      77      15      31      0
e       80      83      17      26      0
s       106     83      13      25      0
s       127     83      14      25      0
e       148     83      13      26      0
r       176     83      12      25      0
a       191     83      19      25      0
c       216     83      18      26      0
t       237     77      15      31      0261     66      65      46      0328     64      30      46      0377     67      31      43      0
>>>

なお、単語単位であれば pyocr.builder.WordBoxBuilder など「特定の OCR エンジンに依存しない」オブジェクトが用意されているので、これらを使う方が良いだろう。文字単位での切り出しは pyocr.tesseract のみサポートされているらしく、それ用の実装クラスを上記の例では明示指定している。

座標系が変わっていたり、確信度が得られなかったり、といった点で少し注意は要るけれど、これで使えそうだ。