目標
この章では:
- kNNに関する知識を使って、基本的なOCR(光学文字認識)アプリケーションを構築する。
- OpenCVに付属する数字とアルファベットのデータでアプリケーションを試す。
手書き数字のOCR
目標は、手書きの数字を読み取れるアプリケーションを構築することである。そのためには、いくつかの訓練データとテストデータが必要である。OpenCVには、5000個の手書き数字(各数字につき500個)を含む画像digits.png(opencv/samples/data/フォルダ内)が付属している。各数字は20x20の画像である。そこで最初のステップは、この画像を5000個の異なる数字画像に分割することである。次に、各数字(20x20の画像)について、400ピクセルからなる単一の行に平坦化する。これが特徴セット、すなわちすべてのピクセルの輝度値である。これは作成できる最も単純な特徴セットである。各数字の最初の250サンプルを訓練データとして使用し、残りの250サンプルをテストデータとして使用する。それではまず、それらを準備しよう。
import numpy as np
import cv2 as cv
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]
x = np.array(cells)
train = x[:,:50].reshape(-1,400).astype(np.float32)
test = x[:,50:100].reshape(-1,400).astype(np.float32)
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = train_labels.copy()
knn = cv.ml.KNearest_create()
knn.train(train, cv.ml.ROW_SAMPLE, train_labels)
ret,result,neighbours,dist = knn.findNearest(test,k=5)
matches = result==test_labels
correct = np.count_nonzero(matches)
accuracy = correct*100.0/result.size
print( accuracy )
Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
Loads an image from a file.
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
Converts an image from one color space to another.
これで基本的なOCRアプリの準備ができた。この具体的な例では、精度91%が得られた。精度を向上させる選択肢の一つは、訓練用のデータを追加することであり、特に誤りの多かった数字についてデータを増やすとよい。
アプリケーションを起動するたびにこの訓練データを求める代わりに、それを保存しておけば、次回はこのデータをファイルから直接読み込んで分類を開始できる。これはnp.savetxt、np.savez、np.loadなどのいくつかのNumpy関数を使って行える。詳細についてはNumPyのドキュメントを参照してほしい。
np.savez('knn_data.npz',train=train, train_labels=train_labels)
with np.load('knn_data.npz') as data:
print( data.files )
train = data['train']
train_labels = data['train_labels']
私のシステムでは、約4.4 MBのメモリを消費する。特徴として輝度値(uint8データ)を使用しているため、まずデータをnp.uint8に変換してから保存した方がよい。この場合はわずか1.1 MBしか消費しない。そして読み込み時にfloat32に戻せばよい。
英語アルファベットのOCR
次に、英語アルファベットについても同じことを行うが、データと特徴セットに少し変更がある。ここでは画像の代わりに、OpenCVにデータファイルletter-recognition.dataがopencv/samples/cpp/フォルダに付属している。これを開くと、一見ゴミのように見えるかもしれない20000行が表示される。実際には、各行の最初の列はラベルである文字を表す。それに続く次の16個の数字がさまざまな特徴である。これらの特徴は UCI Machine Learning Repository から取得されたものである。これらの特徴の詳細は このページ で確認できる。
20000サンプルが利用可能であるため、最初の10000を訓練サンプルとし、残りの10000をテストサンプルとする。文字は直接扱えないため、文字をASCII文字に変換する必要がある。
import cv2 as cv
import numpy as np
data= np.loadtxt('letter-recognition.data', dtype= 'float32', delimiter = ',',
converters= {0: lambda ch: ord(ch)-ord('A')})
train, test = np.vsplit(data,2)
responses, trainData = np.hsplit(train,[1])
labels, testData = np.hsplit(test,[1])
knn = cv.ml.KNearest_create()
knn.train(trainData, cv.ml.ROW_SAMPLE, responses)
ret, result, neighbours, dist = knn.findNearest(testData, k=5)
correct = np.count_nonzero(result == labels)
accuracy = correct*100.0/10000
print( accuracy )
これは93.22%の精度を与える。繰り返すが、精度を向上させたい場合は、反復的にデータを追加していけばよい。
追加リソース
- 光学文字認識に関するWikipediaの記事
演習
- ここではk=5を使用した。kに他の値を試すとどうなるか?精度を最大化する(誤りの数を最小化する)値を見つけられるか?