読者です 読者をやめる 読者になる 読者になる

danglingfarpointer's memoization

仕事周りでの気付き、メモ、愚痴などを書いていきます。

Tesseract OCRで数字の認識

オープンソースで最も認識精度が高いと言われている文字認識エンジン、Tesseractで数字を認識してみる。名刺の中の電話番号を認識する、という想定。OSはMac OS X.

画像の準備

名刺っぽいプリントアウトをスマホのカメラで撮影。 画像サイズは2592x1944. 人名や住所などは全くデタラメw

f:id:danglingfarpointer:20150130191230j:plain

tesseractのインストール

tesseractをMacPortsでインストール。

sudo port install tesseract

ソースコード

処理の流れとして、まず二値化を行い、次に文字認識を適用する。文字認識結果の出力とは別に、二値化の結果画像もtifファイルとして保存する。

文字認識では、数字といくつかの記号をホワイトリストとしている。なので、後に見るように、漢字やアルファベットなども無理やり数字として解釈してしまう。

認識結果の一文字ごとに確信度(confidence)が割り当てられる。認識結果を出力する際は、確信度が高いものと低いものを区別して出力する。低いものにはインデントを付けて出力する。

#include <iostream>
#include <cstdlib>
#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>

using namespace std;
using namespace tesseract;

int main(int argc, char **argv)
{
    if(argc != 3){
        cerr << "USAGE: " << argv[0]
             << " /path/to/image /path/to/bi-output-image" << endl;
        exit(1);
    }

    char* src = argv[1], *bi = argv[2];

    //
    // 入力画像を二値化する
    //
    PIX *pixsrc = pixRead(src);
    PIX *pixgray = pixConvertRGBToLuminance(pixsrc); // グレイスケールに一度変換
    PIX *pixbi = pixCreate(pixgray->w, pixgray->h, 2);
    // 二値化
    pixOtsuAdaptiveThreshold(pixgray, pixgray->w, pixgray->h, 0, 0, 0.1, NULL, &pixbi);
    pixWrite(bi, pixbi, IFF_TIFF);
    // 要らないものをfinalize
    pixDestroy(&pixsrc);
    pixDestroy(&pixgray);

    //
    // 文字認識実行
    //
    TessBaseAPI tess;
    if(tess.Init(NULL, "eng")){
        cerr << "could not initialize tesseract" << endl;
        exit(1);
    }
    tess.SetVariable("save_blob_choices", "T");
    // ホワイトリスト設定
    tess.SetVariable("tessedit_char_whitelist", "0123456789 -:()");
    tess.SetImage(pixbi);
    tess.Recognize(NULL);

    ResultIterator* ri = tess.GetIterator();
    if(ri != 0){
        // 一文字ずつ結果を見ていく
        do {
            const char* symbol = ri->GetUTF8Text(RIL_SYMBOL);
            if(symbol != 0){
                float conf = ri->Confidence(RIL_SYMBOL);
                // 認識結果と確信度を出力
                if(conf < 80) // 確信度が低ければインデントを付与
                    cout << "    \'" << symbol << "\'" 
                         << " confidence: " << conf << endl;
                else
                    cout << "\'" << symbol << "\'" 
                         << " confidence: " << conf << endl;

                // ChoiceIterator ci(*ri);
                // do {
                //     const char* choice = ci.GetUTF8Text();
                //     cout << "\t\t\t" << "\'" << choice << "\': " 
                //          << ci.Confidence() << endl;
                // } while(ci.Next());
            }
            delete[] symbol;
        } while((ri->Next(RIL_SYMBOL)));
    }

    tess.Clear();
    tess.End();
    pixDestroy(&pixbi);

    return 0;
}

ビルド

g++ -o tess tess.cpp -O2 -I/opt/local/include -L/opt/local/lib -llept -ltesseract

実行

第一引数に入力画像を、第二引数に保存する二値化画像のファイル名を与える。結果は以下のとおり。

$ ./tess cap.jpg cap.tif
    '3' confidence: 55.7878
    '5' confidence: 48.2515
    '1' confidence: 74.4947
    '2' confidence: 74.8571
    '9' confidence: 65.7379
    '4' confidence: 50.08
    '0' confidence: 13.8199
    '3' confidence: 66.5101
    '0' confidence: 22.5098
    '0' confidence: 6.99005
'0' confidence: 91.9207
'8' confidence: 93.2891
'5' confidence: 91.6527
'-' confidence: 94.7043
'0' confidence: 90.4132
'0' confidence: 91.5067
'3' confidence: 95.2874
'2' confidence: 92.9869
    '1' confidence: 64.2396
    '0' confidence: 49.9588
    '5' confidence: 69.2291
    '0' confidence: 16.5115
    '5' confidence: 54.0336
    '1' confidence: 63.1991
    '5' confidence: 52.3447
    '3' confidence: 56.4374
    '1' confidence: 74.0782
    '0' confidence: 44.8059
    '0' confidence: 51.2802
    '5' confidence: 56.8292
    '5' confidence: 50.2101
    '0' confidence: 58.1238
    '0' confidence: 23.41
    '0' confidence: 51.4877
    '5' confidence: 67.2946
    '0' confidence: 29.2511
    '0' confidence: 41.8625
    '0' confidence: 45.2179
    '1' confidence: 73.9682
'7' confidence: 93.7799
'-' confidence: 96.4489
'1' confidence: 88.6481
'4' confidence: 91.9315
'-' confidence: 93.9458
'9' confidence: 90.9804
    '0' confidence: 20.1172
    '1' confidence: 69.0272
':' confidence: 95.3482
'0' confidence: 88.9497
'1' confidence: 94.7421
'5' confidence: 91.4299
'4' confidence: 93.6259
'-' confidence: 96.483
'9' confidence: 90.8426
'7' confidence: 94.9449
'-' confidence: 90.6047
'4' confidence: 91.5667
'8' confidence: 90.4304
'9' confidence: 88.8301
'8' confidence: 94.6282
    '0' confidence: 69.7327
    '0' confidence: 64.6164
    '1' confidence: 70.6974
    '0' confidence: 67.1356
    '0' confidence: 56.4117
':' confidence: 92.6204
    '1' confidence: 70.3545
    '(' confidence: 79.365
    '3' confidence: 73.291
    '0' confidence: 28.9734
    '5' confidence: 70.0992
    '0' confidence: 57.6477
    '0' confidence: 30.6046
    '0' confidence: 74.5287
'5' confidence: 82.144
    '0' confidence: 74.3088
    '0' confidence: 72.9004
    '0' confidence: 50.3754
    '6' confidence: 51.6748
    '0' confidence: 76.4664
    '0' confidence: 16.0721
    '0' confidence: 76.0345
    '6' confidence: 68.1494
    '0' confidence: 19.3069
    '1' confidence: 64.2986
    '0' confidence: 1.06659
    '0' confidence: 75.0809

インデントされているのは確信度が80よりも小さい文字。名刺の中の漢字やアルファベットは、ほとんどが80より小さくなっている。逆に、数字の認識はいずれも確信度が高く、実際にすべて成功している。

従って、確信度の高いチャンクを切り出し、電話番号のフォーマットに合致しているものを取り出せば、今回は電話番号を特定できたことになる。

ちなみに以下が二値化された画像。良好に二値化されている。

f:id:danglingfarpointer:20150130193110p:plain