k近傍法を使った手書き文字認識

目的

この章では
  • 前章で学んだk近傍法を使い,基礎的なOCR(光学文字認識)を構築する.
  • OpenCVにある数字とアルファベットのデータを使う.

手書き数字に対するOCR

ここでの目的は手書きの数字を読みとるアプリケーションの作成である.この目的を実現するために,train_data(学習データ)と test_data(テストデータ)を必要とする.OpenCVには5,000個(0から9まで各数字が500個)の手書き数字が1枚の画像として収められている digits.png ( opencv/samples/python2/data/ フォルダ内に保存)がある.それぞれの数字は20x20のサイズの画像である.そこでまず初めに,この1枚の画像を5000個の数字の画像に分割する.次に20x20のサイズの数字データをそれぞれ,1列400画素のデータに変換する.一つ一つの手書き数字は、この400画素のデータ、つまり400個の画素値を特徴量に持つデータとなる.これは,我々が作れる最も簡単な特徴量である.0から9までのそれぞれの数字から、最初の250個のデータをtrain_data(学習用のデータ),残りの250個のデータをtest_data(テスト用のデータ)としよう.さて準備を始めよう。(コード, 数字データ)

import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('digits.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# この画像をそれぞれ20x20のサイズのセル5,000個に分割する。
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]

# Numpy配列に変換する。そのサイズは (50,100,20,20)
x = np.array(cells)

# train_data(学習データ)とtest_data(テストデータ)を用意
train = x[:,:50].reshape(-1,400).astype(np.float32) # Size = (2500,400)
test = x[:,50:100].reshape(-1,400).astype(np.float32) # Size = (2500,400)

# 学習データとテストデータのラベルを作る
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = train_labels.copy()

# kNNのインスタンスを作り、学習データで学習し、テストデータでテストする(k=1とする)
knn = cv2.KNearest()
knn.train(train,train_labels)
ret,result,neighbours,dist = knn.find_nearest(test,k=5)

# 分類の精度を調べる
# そのために、テストデータのラベルと比較し、誤りを調べる
matches = result==test_labels
correct = np.count_nonzero(matches)
accuracy = correct*100.0/result.size
print(accuracy)

これで基礎的な文字認識アプリができた.この例では91%の精度を達成した.精度を向上させる一つの方法として,学習データ,中でも誤りデータを増やすことがある.さて、アプリケーションを起動するたびにデータの学習を行うのではなく、学習結果を保存し、それを次回以降に使えるようにしよう。そうすれば、この保存した学習データを読み込んで文字認識ができるようになる。データの保存にはNumpyの関数のうち例えばnp.savetxt, np.savez, np.load等を使えばよい.詳細については各関数のドキュメントを参照すること:(コード)

# データの保存
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.4MBの容量になった.特徴量として画像の画素値(uint8型のデータ)を使っているので,データを np.uint8型に変換してから保存すると良いだろう.そうすれば,データ容量はたったの1.1 MBになる.ただしその時は、データを読み込むとき,float32型のデータに変換することを忘れないようにすること.

英字の認識(OCR)

今度は英字についても同じことを試してみよう。ここで数字と英字では,データと特徴量に多少違いがある.OpenCVでは、文字データは画像ではなく opencv/samples/cpp/フォルダ内のletter-recognition.data というデータファイルとして提供されている.

このデータファイルを開くと,一見意味不明なデータが20,000行並んでいるように見える.それぞれの行は,一列目にラベルとして用いられるアルファベット(英字)があり、それに続いて16個の数が並んでいる。この16個の数が特徴量である.これらの特徴量は UCI Machine Learning Repository(カリフォルニア大学アーバイン校(UCI)の機械学習データセンター)から得たものである.詳しくはこのページを参照すること。

20,000個のサンプルがあるので,最初の10,000個のデータを学習用,残りの10,000個のデータをテスト用としよう.英字を直接扱えないため,英字をASCIIコード(数)に変換する必要がある.(コード, e英字データ)

import cv2
import numpy as np
import matplotlib.pydata= np.loadtxt('letter-recognition.data', dtype= 'float32', delimiter = ',',
                    converters= {0: lambda ch: ord(ch)-ord('A')})

# データを2つに分け、学習用とテスト用とに10,000ずつにする
train, test = np.vsplit(data,2)

# trainDataと testDatを、特徴と応答値に分ける
responses, trainData = np.hsplit(train,[1])
labels, testData = np.hsplit(test,[1])

# kNNを始め、分類し、精度を求める.
knn = cv2.KNearest()
knn.train(trainData, responses)
ret, result, neighbours, dist = knn.find_nearest(testData, k=5)

correct = np.count_nonzero(result == labels)
accuracy = correct*100.0/10000
print accuracy

私のマシンで実行したところ93.22%の精度となった.すでに述べたように,精度を上げるには、それぞれのラベルごとに誤りデータを追加する.

課題