Tesseract OCR 4.x(LSTM)で言語データのトレーニング前に実施するべき品質の改善方法について

# 概要

[前回の記事](https://qiita.com/henjiganai/items/7a5e871f652b32b41a18)ではTesseract OCRの使い方と実行時のオプションについて記載しました。
今回はTesseract OCR4.x(LSTM)版について[言語データをトレーニング](https://github.com/tesseract-ocr/tesseract/wiki/TrainingTesseract-4.00)する際の手法一覧と、トレーニング前に行うべき[品質改善](https://github.com/tesseract-ocr/tesseract/wiki/ImproveQuality)の方法について記載したいと思います。

# Tesseract OCR 4.xについて

それまでのTesseract OCR 3.5までと違い、Tesseract OCR 4.0から文字認識のエンジンにニューラルネットワークの一つであるLSTMを追加で採用しました。
その結果、計算時間が増加した代わりに3.5までのTesseractエンジンより**遥かに認識精度が向上**しました。
※しかし複雑な言語では、Tesseractエンジンより実行結果は早くなる場合がほとんどです。

ニューラルネットワークでのトレーニングは今までのTesseractエンジンより多くの時間がかかり、場合によっては数日から数週間かかる場合があります。
もし言語データを再トレーニングしたとしても特定のデータには不十分な場合が発生することがあります。
そのため、特定の言語や特定の状況に応じて再トレーニングする必要があります。

# トレーニングの種類

トレーニングには次の3種類がある。

## 1. 微調整(Fine tune)

既存のトレーニングされた言語データに対して特定の追加データを与えてトレーニングを行う。

## 2. ネットワークから最上位または任意のレイヤー(層)を切り離してトレーニング

([原文](https://github.com/tesseract-ocr/tesseract/wiki/TrainingTesseract-4.00#introduction)ではCut off the top layer (or some arbitrary number of layers) from the network and retrain a new top layer using the new data)

ニューラルネットワークでは`層`という概念がある。そのうち最上位または任意のレイヤーのみを切り離し、切り離した部分に対して特定の追加データを与えてトレーニングを行う方法である。
「1. 微調整」でうまくいかない場合はこちらの方法でうまく改善できる可能性がある。

## 3. 初めから再トレーニング(Retrain from scratch)

全てのデータを自分で準備をしてトレーニングを行います。こちらの場合、十分なトレーニングのセットがない限り非常に困難になる。
その場合、トレーニングデータのみでうまく機能する言語データになる場合がほとんどになる。

# トレーニングを行う前に

Tesseract OCR 4.xのトレーニング・使用自体にニューラルネットワークの知識は必要がない。
言語のトレーニングには多大な時間がかかるためその前に必ず[ImproveQuality](https://github.com/tesseract-ocr/tesseract/wiki/ImproveQuality)を読み、先に品質の改善を行った方が良い。

# 品質の改善について

Tesseract OCRは実際の文字認識を実行する前に複数の画像処理操作をLeptonicaライブラリを介して行う。
通常はLeptonicaライブラリが実行する処理のみでも問題がないクオリティで文字認識が行われるが、処理を行う画像によっては事前の画像処理が十分ではないこともあり得るため、精度が大きく低下する場合がある。

精度に問題があると感じた場合はTesseract OCRの実行時に下記のどちらかを設定することでTesseract OCRが画像を処理した方法を確認できる。

* `configfiles`に`tessedit_write_images`を`true`として設定し、該当のconfigfilesを実行時に指定
* Tesseract OCR実行時に[コントロールパラメータ（ControlParams）](https://github.com/tesseract-ocr/tesseract/wiki/ControlParams)の`出力形式`に`get.images`を指定

上記処理によって出力されたtessinput.tifを確認して画像処理に問題があると感じた場合、Tesseract OCRの実行前に下記の画像処理のいくつかを実行することによって認識精度が向上する場合がある。

## 画像処理

### 1. 白黒反転の画像を使用しない(4.0以上)

Tesseract OCR 4.0以上のLSTMベースのOCRエンジンを使用する場合は白背景に黒字を使うようにする。
なお、3.05までのエンジンの場合は白黒反転の画像にも対応しているため黒背景に白字の場合でも問題なく処理が可能である。

![inversion_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340824/d62a482b-0a1c-befa-a2dd-4edc31d59d08.png)

### 2. 適切なスケーリング

画像サイズが以下に当てはまるように再設定する。

* 300dpi以上
* 10pt以上
* `x-height`が少なくとも10ピクセル以上(10ptかつ300dpiの場合、20ピクセルが望ましい)

10ptかつ300dpi未満の場合に精度が低下していき、更に8ptかつ300dpi未満の場合、著しく精度が低下する。

画像に含まれる文字のptがわからない場合の簡単な確認方法は`x-height`を確認して画像の再スケーリングを行うことである。
10ptかつ300dpiの場合は`x-height`が一般的なフォントの場合約20ピクセルとなる。
(フォントによっては大幅に異なる可能性がある)
`x-height`が10ピクセル未満の場合、正しい認識結果が得られる可能性がほとんどなく、8ピクセル未満の場合は文字のほとんどが**ノイズ除去の対象**として認識されてしまう。

### 3. 二値化

Tesseract OCRでは文字認識の実行時、内部で**大津の二値化**を実行しているが、認識させたい画像の背景の色が不均一な場合に適切な二値化が行われない可能性がある。
その場合はTesseract OCRの実行前にその画像にとって最適なアルゴリズムの二値化を実行して文字と背景を適切に分離する必要がある。

![binarisation_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340824/19db3191-a129-ab55-5746-4c1fca2ee74e.png)

原文のアルゴリズムの例としては以下のものが挙げられている

* ImageJの[Auto Threshold](https://imagej.net/Auto_Threshold)
* OpenCVの[画像の閾値処理](https://docs.opencv.org/master/d7/d4d/tutorial_py_thresholding.html)
* scikit-imageの[Thresholding](https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_thresholding.html)

もしTesseract OCRで最適な二値化が難しい画像を扱う場合は、自分の実行するライブラリや言語に合わせて適切な二値化を行う。

### 4. ノイズ除去

認識させる画像に対してごま塩ノイズ(インパルスノイズ)などが発生している場合、Tesseract OCRによる二値化でノイズが除去しきれない場合がある。
いくつかのノイズはTesseract OCRでは除去しきれないためノイズが含まれたままになってしまい、精度が低下する可能性がある。

一般的に簡単に実施できるノイズ除去処理は収縮・膨張処理をn回繰り返すことであるが、画像のノイズの入り方によってはこの手法では除去できない場合もあるため、画像ごとに適切なノイズ除去の方法を選択する必要がある。

![noise_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340824/ae9bafa8-e930-3a08-ea16-1a3705446901.png)

### 5. 回転/回転補正

画像をスキャナなどで読み込む際、場合によっては文字列が水平にスキャンできない場合がある。この時に文字列が水平になっていない画像を読み込む場合、行ごとの分割が正しく行われない可能性が非常に高くなり、文字認識の精度が大幅に低下する。

水平にスキャンされていない画像に対してOCRを実行する場合は必ず文字列が水平になるように画像の角度を回転させて調整を行うこと。

![rotation_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340824/c931e82d-defa-b8e7-ed97-89643b955c63.png)

### 6. 境界線

#### 6.1. 不要な境界線の除去

スキャナなどで本をスキャンした場合にページの周囲に境界線が表示されることがある。
境界線にグラデーションがかかっていたり、複数の形状の境界線がページに含まれている場合、OCRを実行した場合に誤認識を行う可能性がある。
上記のような境界線が画像に含まれている場合は余分な境界線を予め削除しておくこと。

![borders_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340824/5897257d-64fd-40b1-b911-d951c80b9b28.png)

#### 6.2. 文字と画像との境界線を追加

画像に対して境界がほとんどない文字を認識させようとする場合、問題が発生する場合がある。
その場合は文字に対して10ptほどでよいので白い境界線を追加すれば認識精度が向上する場合がある。
(原文では[ImageMagick](https://imagemagick.org/index.php)を使用すれば簡単に境界線を追加できると書かれているが、多くの脆弱性が発見されているため他のツールを使用した方が良いと思われる)
※参考URL
[Why do I get such poor results from Tesseract for simple single character recognizing?](https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/tesseract-ocr/v26a-RYPSOE/2Sppq61GBwAJ)
[Error in boxClipToRectangle: box outside rectangle #427](https://github.com/tesseract-ocr/tesseract/issues/427)

### 7. 透明度及びアルファチャネルの除去

PNGなどの画像形式は透過度の情報を扱うためのアルファチャンネルが含まれている。
しかしTesseract OCRではアルファチャンネルを持つ画像にOCRをかける場合、状況に応じてOCR実行前にアルファチャンネルの削除を行う必要がある。

**Tesseract OCR 4.0以上の場合**
4.0以上のエンジンの場合はleptonica関数の[pixRemoveAlpha()](https://github.com/DanBloomberg/leptonica/blob/648a3be52b6a004df14671de7004416f9a3ce489/src/pixconv.c#L133)を使用してアルファチャンネルの削除が行われる。
上記の手法では背景を白としてブレンドを行いアルファチャンネルの削除が行われるため、特定の条件(例えば、[映画の字幕](https://github.com/tesseract-ocr/tesseract/issues/2048#issuecomment-438015376)に対してそのままOCRを実行する場合)では問題が発生する可能性がある。
そのような場合にはOCR実行前に前処理としてアルファチャンネルを削除するか、画像を反転させておく必要がある。

**Tesseract OCR 3.05までの場合**
3.05までのエンジンを使用する場合はTesseract OCRではアルファチャンネル削除を行わないため、OCR実行前にユーザが透過度及びアルファチャンネルを削除しておく必要がある

### 8. ツール・ライブラリの紹介

* [Leptonica](http://leptonica.com/)
* [OpenCV](http://opencv.org/)
* [ScanTailor Advanced](https://github.com/4lex4/scantailor-advanced#-scantailor-advanced)
* [ImageMagick](http://www.imagemagick.org/)
* [unpaper](https://www.flameeyes.eu/projects/unpaper)
* [ImageJ](http://rsb.info.nih.gov/ij/)
* [Gimp](http://www.gimp.org/)
* [PRLib](https://github.com/leha-bot/PRLib)　※OCR品質を改善するための事前認識ライブラリ

### 9. 画像改善のプログラム例

* [OpenCV のRotationによるスキュー角の除去](http://felix.abecassis.me/2011/10/opencv-rotation-deskewing/)　C++でスキャン時の角度の歪みの除去の例
* [Fred's ImageMagick ScriptsのTEXTCLEANER](http://www.fmwconcepts.com/imagemagick/textcleaner/index.php)　テキスト画像の背景を処理するための各種前処理を行うbashスクリプト(ノイズ除去、歪み除去などが可能)
* [rotation_spacing.py](https://gist.github.com/endolith/334196bac1cac45a4893#)　ラドン変換を利用してテキスト画像の回転と行間隔を自動検出するためのPythonスクリプト
* [crop_morphology.py](https://github.com/danvk/oldnyc/blob/master/ocr/tess/crop_morphology.py)　[OpenCV、numpyを使用して画像内のテキストブロックを検索する](http://www.danvk.org/2015/01/07/finding-blocks-of-text-in-an-image-using-python-opencv-and-numpy.html)ためのPythonスクリプト
* [OpenCVおよびPythonを使用したクレジットカードOCR](https://www.pyimagesearch.com/2017/07/17/credit-card-ocr-with-opencv-and-python)　OpenCVを利用してクレジットカードの文字を読み取るためのPythonスクリプト
* [noteshrink](https://github.com/mzucker/noteshrink)　手書き文字の画像をPDFにクリーンアップするPythonのスクリプト（詳細は[手書きメモの圧縮と強化](https://mzucker.github.io/2016/09/20/noteshrink.html)に記載）
* [uproject text](https://github.com/mzucker/unproject_text)　テキスト画像に施された遠近感を解除するためのPythonスクリプトの例（詳細は[テキストに楕円を当てはめて投影変換を解除](https://mzucker.github.io/2016/10/11/unprojecting-text-with-ellipses.html)に記載）
* [page_dewarp](https://github.com/mzucker/page_dewarp)　「キュービックシート」モデルを使用してスキャン時に付与されたページカールを修正するPythonスクリプトの例（詳細は[ページの歪み](https://mzucker.github.io/2016/08/15/page-dewarping.html)に記載）
* [OpenCVを使用してスキャンした画像から影を削除する方法](https://stackoverflow.com/questions/44752240/how-to-remove-shadow-from-scanned-images-using-opencv)　OpenCVを使用して画像から影を削除するPythonスクリプトの例

### 10. アウトライン化の除去(原文では存在しないが、[Issue #2048: Accuracy problem](https://github.com/tesseract-ocr/tesseract/issues/2048)から追記)

文字列がアウトライン化されている場合、Tesseract OCRの認識精度は大幅に低下する。
上記のような文字列が含まれている場合、アウトライン部分を除去する必要がある。

## ページセグメンテーション

通常何もオプションを指定しない場合、Tesseract OCRでは複数行が含まれる1枚の原稿のような画像を期待する。小さい領域を文字認識したい場合は[ページセグメンテーションモード(=psm)](https://github.com/tesseract-ocr/tesseract/blob/master/doc/tesseract.1.asc)を使用して別のページセグメンテーションモードを試すことをすすめる。
トリミングされすぎていて画像に対して境界線がほとんどない場合、テキスト画像の周囲に白い境界線を追加することでも文字認識の改善が行われる場合もある。
（[Issue #398: without output result](https://web.archive.org/web/20151209085049/https://code.google.com/p/tesseract-ocr/issues/detail?id=398)を参照）

サポートされているページセグメンテーションモードを確認するには`tesseract -h`を実行すること。
（ページセグメンテーションの各モードについては[以下のページ](https://qiita.com/henjiganai/items/7a5e871f652b32b41a18#3-%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%BB%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%A2%E3%83%BC%E3%83%89psm%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)で翻訳している）

## 辞書、単語リスト、および文字パターン

通常何もオプションを指定しない場合、Tesseract OCRでは各単語を認識するように最適化されている。領収書、価格表、コードなど一般的な単語とはことなる文を認識しようとしている場合は適切なページセグメンテーションモードが選択されていること以外にもいくつかの方法で精度を向上させることができる。

Tesseractに対して辞書を無効にするオプションを指定した場合、テキスト画像のほとんどが辞書に含まれるような単語でない場合に文字認識の精度が向上する。
辞書を無効にするためには[コントロールパラメータ（ControlParams）](https://github.com/tesseract-ocr/tesseract/wiki/ControlParams)の`load_system_dawg`と`load_freq_dawg`の二つに対して`false`を設定する。

Tesseractによる文字認識を支援するために単語リストに単語を追加したり、一般的な文字パターンを追加することも可能である。
これについては、[Tesseractマニュアル](https://github.com/tesseract-ocr/tesseract/blob/master/doc/tesseract.1.asc#config-files-and-augmenting-with-user-data)で詳細が記載されている。

数字を認識したい場合といった、一部の文字のみのパターンしかあり得ないことがわかっている場合は`tessedit_char_whitelist`で認識する文字列を制限することができる。例については[FAQ](https://github.com/tesseract-ocr/tesseract/wiki/FAQ-Old#how-do-i-recognize-only-digits)を参照すること。
（`tessedit_char_whitelist`については[以下のページ](https://qiita.com/henjiganai/items/7a5e871f652b32b41a18#2-%E8%AA%8D%E8%AD%98%E3%81%99%E3%82%8B%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E5%88%B6%E9%99%90%E3%81%99%E3%82%8B35%E3%81%BE%E3%81%A7%E3%82%82%E3%81%97%E3%81%8F%E3%81%AF41%E4%BB%A5%E4%B8%8A)で翻訳・解説している）

## 上記を試しても問題がある場合

画像処理を試してもまだ読み取り精度が低い場合は[フォーラム](https://groups.google.com/forum/?fromgroups#!forum/tesseract-ocr)でヘルプを求めることが推奨される。
可能な限りサンプル画像を投稿すること。

# 総括

今回はOCR前に行った方が良い品質改善方法について書きました。
一般的な横書きに近いテキストの場合、おそらくこちらに記載した内容を実施するだけで認識精度が向上します。トレーニング不要になる場合も多いと思われます。
しかし、特殊なフォントもしくは文字の場合、こちらだけでは精度不足になる場合があると思います。その場合は言語データに対してトレーニングが必要になります。
次回の記事では言語データのトレーニングについて書きたいと思います。
~~PythonでTesseract OCRの使用方法について書くと言っていましたがまだ書いていないので早めに書きたい~~