kNNで文字認識
kNNとは
k近傍法(k-nearest neighbor)。かなり単純なクラスタリングのアルゴリズム。
- 教師データ(ラベリング済)を特徴ベクトル化して学習
- クラスタリング対象のデータも特徴ベクトル化
- 特徴ベクトルの距離を比較し、対象データと近い順にk個の教師データを選ぶ
- 選ばれた教師データに付与されているラベルから対象データのクラスを決定
直感的に理解できる簡素なアルゴリズムである。 OpenCVで実装されているため簡単に試せる。
IkalogではkNNで文字認識を行なっているらしい。
「スプラトゥーン」リアルタイム画像解析ツール 「IkaLog」の裏側
文字位置・角度・照明条件が可変な今回のケースではどの程度の精度が出るのか、試しにやってみた。
学習データの準備
前項までで文字位置の特定ができているので、その領域を切り出して等サイズにリサイズ→保存する。
resized_img = cv2.resize(character_img, (RESIZED_WIDTH, RESIZED_HEIGHT), interpolation=cv2.INTER_NEAREST)
PythonでkNN
学習は毎回実行時に行う。
- 学習データを1行N列のベクトルに変形
# sample = img.reshape((1, img.shape[0] * img.shape[1])) sample = img.reshape((1, -1))
reshapeの引数のサイズのうち片方に-1を指定すると,もう一方の値から適切な数値を判断して変形してくれる。 Numpyつよい。
ラベルは別配列に要素番号が同じになるように保存する。
label = ord('0') + int(character_i) labels.append(label)
samplesは[1行N列のベクトル]の配列、かつtype() == CV_32S
になるように注意して変形する。
labelsも[1行1列のベクトル(?)]の配列、かつtype() == CV_32S
になるように変形。
printするとこんな感じ。
samples [[ 0. 0. 0. ..., 0. 0. 0.] [ 0. 0. 0. ..., 0. 0. 0.] [ 0. 0. 0. ..., 0. 0. 0.] ..., [ 0. 0. 0. ..., 255. 255. 0.] [ 0. 0. 0. ..., 255. 255. 0.] [ 0. 0. 0. ..., 255. 255. 255.]] labels [[ 48.] [ 48.] [ 48.] ..., [ 57.] [ 57.] [ 57.] [ 57.]]
学習部分全体のコード。
samples = None labels = [] # for 0 - 9 for character_i in range(0, 10): img_dir = TRAIN_DATA_DIR + str(character_i) + "/" files = os.listdir(img_dir) for file in files: ftitle, fext = os.path.splitext(file) if fext != '.jpg' and fext != '.png': continue # Load image abspath = os.path.abspath(img_dir + file) img = cv2.imread(abspath, cv2.IMREAD_GRAYSCALE) if img is None: print('Failed to read image') break sample = img.reshape((1, -1)) label = ord('0') + int(character_i) if samples is None: samples = np.empty((0, img.shape[0] * img.shape[1])) samples = np.append(samples, sample, 0).astype(np.float32) labels.append(label) labels = np.array(labels, np.float32) labels = labels.reshape((labels.size, 1)).astype(np.float32) knn = cv2.ml.KNearest_create() knn.train(samples, cv2.ml.ROW_SAMPLE, labels)
文字認識
学習結果を使って画像をクラスタリングすることで文字認識を行う。
# kNN ch_string="" for ch_img in character_imgs: sample = ch_img.reshape((1, ch_img.shape[0] * ch_img.shape[1])) sample = np.array(sample, np.float32) k = 3 retval, results, neigh_resp, dists = knn.findNearest(sample, k) d = chr(int(results.ravel())) ch_string += str(d) print("result:" + ch_string) cv2.waitKey(0)
認識対象も勿論1行N列の32bitfloat配列に直してknn.findNearest()につっこむ。
結果
成功例
result:09828932
result:08463768
失敗例
result:09955509
result:09680560
13画像(104字)に対して6文字の認識ミスだったので認識精度は94.23%。 0-8-9あたりの誤認識が多かった。
考察
kNNのアルゴリズムを考慮すると、学習が足りないとかいう問題ではなくノイズの影響が大きいように思う。
- ひどい例
9 : 8 :
また、各画素値を単純に並べて特徴ベクトルにする関係上、上下端の余白の大きさや回転による影響もありそう。
- 不必要な部分まで切り取られたパターン
8 :
2値化処理を見直すかDeep Learning様の力でゴリ押すかしようと思う。