2014/04/29

Tesseract-OCRでMNISTのデータを用いて手書き数字認識をしようとしてみた

MNISTの手書き数字データベースのデータから
Tesseract-OCR用に学習データを生成し、手書き数字をオフライン認識してみます。

先に言い訳とお断りをさせていただきますと....
今回はとりあえずそのまま作るとどうなるかという実験のつもりでしたので
私の処理が「思いっきり手抜き」であり、数字しか認識できず認識率も低いですが、
とりあえず学習データができたというレベルの自分用メモとして記しておきます。
Tesseract-OCRもMNISTのデータベースも、正しく使えば、本来は相当な認識率となるはずです
 より正しい方法をご存知の方や、学習データの情報をご提供いただける方は是非教えていただければ幸いです
 ご指摘等をお待ちしております。 (記事のタイトルの歯切れが悪いのはそういうことです(汗;))


利用した環境は Linux (Arch Linux x64)であり、
Tesseract-OCR および engの学習データがインストール済みである事が前提です。
(Arch Linuxのpacmanでは tesseract, tesseract-data-eng でインストール可能。)

尚、Tesseract-OCRでの学習に関する手順は Tesseract-OCRの学習 - はだしの元さん を参照、引用させていただきました。感謝申し上げます。


1. MNISTのデータを取得

まずは 学習モデルの素となる MNISTのデータを取得...といきたいところですが、
今回はそのままでは使いづらい(もとい、使い方がよくわからない(^^;))ので
JPEG化されたデータを使わせていただくことにします。
http://www.cs.nyu.edu/~roweis/data.html

上記URLのページにある"MNIST Handwritten Digits"の
"training pictures"を0〜9までダウンロードします。
各々はJPEGファイル(mnist_train*.jpg)であり、その数字の手書き画像が大量に結合された巨大な画像ファイルとなっています。
training picturesの"0" (抜粋)
手書きされた数字の0が大量に結合されている。
さらに前処理として...これらのJPEGファイルを二値化してノイズを取り除き、PNGファイルとして保存しておきます。
この時、tesseractのboxファイルの命名規則に応じたファイル名にすると楽です:
(3文字の言語名).(フォント名(任意)).exp(インデックス番号)
という規則になっています (引用元)。  今回は数字だけですので...
  • 言語名: "eng"
  • フォント名:  "hand" (適当)
  • インデックス番号: PNGファイルの数字にあわせて0〜9
    (例: eng.hand.exp0.png 〜 eng.hand.exp9.png)
とします。
convertコマンドを使えば、二値化とPNGファイルとしての保存が一発ですね。
$ convert -threshold 30000 mnist_train*.jpg eng.hand.exp%d.png

2. boxファイルを生成

次にtesseract コマンドを用いて、PNGファイルからboxファイルを生成します。
$ tesseract eng.hand.exp0.png eng.hand.exp0 -l eng batch.nochop makebox &&\
tesseract eng.hand.exp1.png eng.hand.exp1 -l eng batch.nochop makebox &&\
tesseract eng.hand.exp2.png eng.hand.exp2 -l eng batch.nochop makebox &&\
tesseract eng.hand.exp3.png eng.hand.exp3 -l eng batch.nochop makebox &&\
tesseract eng.hand.exp4.png eng.hand.exp4 -l eng batch.nochop makebox &&\
tesseract eng.hand.exp5.png eng.hand.exp5 -l eng batch.nochop makebox &&\
tesseract eng.hand.exp6.png eng.hand.exp6 -l eng batch.nochop makebox &&\
tesseract eng.hand.exp7.png eng.hand.exp7 -l eng batch.nochop makebox &&\
tesseract eng.hand.exp8.png eng.hand.exp8 -l eng batch.nochop makebox &&\
tesseract eng.hand.exp9.png eng.hand.exp9 -l eng batch.nochop makebox
尚、処理には1時間程度かかりました。

3. boxファイルを修正

次にテキストエディタを用いて、boxファイルに対して修正を行います。
ここではまず、"0"の分のboxファイル(eng.hand.exp0.box)を見てみます:
o 2134 2132 2150 2152 0
o 2135 2104 2149 2124 0
o 2133 2075 2153 2095 0
a 2133 2048 2153 2066 0
a 2133 2021 2153 2037 0
o 2132 1992 2152 2012 0
o 2134 1964 2152 1984 0
o 2132 1936 2152 1956 0
o 2133 1907 2153 1927 0
o 2133 1880 2151 1900 0
o 2133 1853 2153 1869 0
o 2133 1825 2153 1842 0
o 2133 1797 2153 1815 0
n 2131 1770 2151 1784 0
o 2135 1739 2150 1759 0
....
先頭は認識された文字ですが、"0"ではなく"o"や"a"、"n"になってしまっています。
これをすべて"0"に置き換えます。(正規表現置換でs/^./0/すると良いですね。)
(※本当は行頭のあとの座標データも修正しなければならないようですが、今回は見逃しておきますw
本当はデータ数減らしてでもちゃんと修正すべきだと思います。)

この処理も0〜9のすべてのboxファイルに対して行います。

4. trファイルの生成

PNGファイルと修正したboxファイルから
tesseractコマンドを用いてtrファイルを生成します。
$ tesseract eng.hand.exp0.png eng.hand.exp0 nobatch box.train.stderr
これにより eng.hand.exp0.tr というファイルが生成されます。

この処理も0〜9のすべてのファイルに対して行います。

5. trファイルおよびboxファイルの結合

生成された 0〜9の分のtrファイル、boxファイルを各々結合します。
$ cat eng.hand.exp*.tr > eng.hand.exp.tr
$ cat eng.hand.exp*.box > eng.hand.exp.box
これ以降はほぼ参照させていただいたページのとおりですが、
丸投げもいけませんので、自分用メモとして掲載させていただきます m(_ _)m

6. 学習データの下準備

まず、boxファイルに対してunicharset_extractorコマンドを実行します。
$ unicharset_extractor eng.hand.exp.box
これにより、"unicharset"ファイルが生成されます。
※ この時、boxファイルの指定はこのように拡張子をつけます。

次に、テキストエディタで"font_properties"ファイルを作成します。
$ vim font_properties
hand 0 0 0 0 0
※ 先頭の文字列は最初に適当に決めたフォント名です。0 0 0 0 0はフォントの種類を表しますが、今回は特に何もないので0です。(詳しく参照ページをご覧ください。)

7. 学習データの生成

まず、先ほど作成したファイルをもとに、mftrainingコマンドを実行します。
$ mftraining -F font_properties -U unicharset eng.hand.exp.tr
これにより、"inttemp"、"shapetable"、"pffmtable"の3ファイルが生成されます。
※ この処理に約20時間要しました (Core i5 2520M @2.50Ghz, RAM8GB にて)。

さらに以下のコマンドを実行します。
$ mftraining -F font_properties -U unicharset -O eng.unicharset unicharset eng.hand.exp.tr
※ この処理にも約20時間要しました...(^^;)

続けて、cntrainingコマンドを実行します。
$ cntraining eng.hand.exp.tr
これにより、"normproto"ファイルが生成されます。
※ 以降の処理は数秒で行えます。

次に、本章で生成されたファイルのファイル名を変更し、
各々の先頭に言語名(今回は"eng")を付与します。
$ mv inttemp eng.inttemp
$ mv shapetable eng.shapetable
$ mv pffmtable eng.pffmtable
$ mv normproto eng.normproto

最後に、combine_tessdataコマンドを実行します。
$ combine_tessdata eng.
※ 最後のドットを忘れないようにしましょう。
これにより、"eng.traineddata"が生成されます。これが学習データです。

あとは...tesseractコマンドや、先日の記事のTesseract-OCRを用いたAndroidアプリケーションでこの学習データを利用できます。

8. 学習データを利用して認識してみる

生成した学習データを /usr/local/src/tesseract-ocr あたりにコピーしてもよいのですが、今回は環境変数で学習データのパスを指定してみることにします。
まず"tessdata"というディレクトリを作成し、学習データを入れておきます。
$ mkdir tessdata
$ mv eng.traineddata tessdata/
次に認識させたい画像をPNGファイルなどとして用意しておきます。(やはり2値化しておくと良いようです。)

あとは環境変数をつけて、tesseractコマンドを実行するだけです。
$ TESSDATA_PREFIX=. tesseract foo.png out.txt -l eng
これにより、out.txt に手書き数字の認識結果が得られます。

認識率は...私の手抜きの分、うーん(´・ω・`)...という感じです。
認識結果は参考までに...

  • 元画像(MNISTのデータをPNG化したもの)から適当な行を抜き出して認識させてみると、そこそこ満足できる認識率が得られました。
    (これで一応、学習データがそれらしく生成できていることは確認できました。)
  • 数字4桁を紙に手書きして、スマートフォンのカメラで撮影したものは...
    そのままでは全く認識できませんでしたが、
    数字部分を切り抜き、2値化と膨張を施すと、後ろ2文字は認識できました。さらに1文字目の数字の間隔を広げたところ、4文字すべて認識できました。

恐らくデータをもっと丁寧に前処理して、boxファイルの修正も丁寧に行えば、精度は向上すると思います...。
素晴らしいデータベースオープンソースソフトウェアであるにも関わらず、私の手抜きでこんな記事になってしまい大変失礼いたしました m(_ _;)m

もう一回、データ数を減らしてまじめに作りなおしてみようかな...とも思ったり...。


参考にさせていただいたWebページ (感謝♪) :