OpenCVのチュートリアルで学ぶ、手書文字認識。
今年配属されてきた新人さんが、「大学時代は、文字認識をやっていました」というので、自分もその一端に触れてみようという試みです。
辞書データの入手
辞書データとして、OpenCVの配布物に含まれているdigits.pngを使います。残念ながら、Anaconda環境でconda install --channel https://conda.anaconda.org/menpo opencv3
した環境にはdigits.pngは含まれていませんでした。
これは、0から9までの数字を手書きしたものの画像データになります。実際に、日本語でOCRを作ろうとすると、そのまま実装すると膨大な辞書データを作る必要ができます。ワタナベさんとかサイトウさんとかの漢字のバリエーションもちゃんと見分けようとすると、そりゃあもう大変なことになりそうです。なんとかなるんでしょうか?
その前にKNNとは
Wikipediaには、k近傍法としてエントリーがあります。
特に図が、KNNを非常に端的に説明していて分かりやすいです。
すなわち、未知のサンプルが、ある座標の空間においてA群に属するものなのかB群に属するものなのかを判別したい場合に、「近傍にある既知の分類済データが多い方の群に属する」と判断する、というものです。
さて、概念を理解できたとして、これはpython(+OpenCV)では簡単に使えるようになっています。
辞書データを1文字ずつにバラす
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]
これは、何をしているか?というと、digits.pngという画像が1000x2000のサイズで、各数字が20x20サイズで500回ずつ書かれているのを受けて、縦を100で、横を50で割ったものを、cellsに配列として格納していることを示しています。しかし、実際のところ、理解できないというか、自分では絶対に書けません。
np.hsplit()や、np.vsplit()という配列の分割の仕方があるということが分かっただけで十分かもしれません。
trainingセットとtestセットへの分割
これを、xというnumpyの配列に格納した後、各数字の250個をtrainingセットに、残りの250個をtestセットに分割します。
x = np.array(cells)
train = x[:,:50].reshape(-1,400).astype(np.float32)
test = x[:,50:100].reshape(-1,400).astype(np.float32)
また、それぞれのデータに対して、ラベルを設定します。
np.arange(10)
すると、0から9までの10この連続した整数の配列array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
が作られるので、それを250回繰り返してラベルにします。
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = train_labels.copy()
KNN実行
いよいよKNNを実行します。k=5で実行してみます。
knn = cv2.KNearest()
knn.train(train,train_labels)
ret,result,neighbours,dist = knn.find_nearest(test,k=5)
実は、上記はエラーになります。チュートリアルが間違っているらしい。
上記の3行は、以下のように書き換えます。
knn = cv2.ml.KNearest_create()
knn.train(train,cv2.ml.ROW_SAMPLE,train_labels)
ret, results, neighbours, dist = knn.findNearest(test, 5)
判別器の成績を見てみます。
matches = results==test_labels
correct = np.count_nonzero(matches)
accuracy = correct*100.0/results.size
print(accuracy)
今回の実行結果は、91.76
ということになりました。
数字だけ10文字の判別でこの精度は、ちょっと低いかなという印象です。
今日は、ひとまずここまで。