SVMによる手書きデータ認識
目標
この章では
- ただし、kNN の代わりに SVM を使用して、手書きデータの OCR を再度学習します。
手書き数字のOCR
kNN では、ピクセル強度を特徴ベクトルとして直接使用します。今回は、特徴ベクトルとして方向性勾配ヒストグラム(HOG)を使用します。
HOG を見つける前に、その 2 番目のモーメントを使用して画像を修正します。そこで、最初にデジタル画像を取得して修正する関数deskew()を定義します。これが deskew() 関数です。
def deskew(img):
m = cv.moments(img)
if abs(m['mu02']) < 1e-2:
return img.copy()
skew = m['mu11']/m['mu02']
M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
img = cv.warpAffine(img,M,(SZ, SZ),flags=affine_flags)
return img
以下の図は、ゼロ画像に適用された上記の補正関数を示しています。左の画像が元の画像、右の画像が補正後の画像です。
画像
次に、各セルの HOG 記述子を見つける必要があります。これを行うには、X 方向と Y 方向の各要素のソーベル導関数を見つけます。次に、各ピクセルでその大きさと勾配の方向を見つけます。勾配は 0 ~ 16 の整数に量子化されます。この画像を 4 つのサブ正方形に分割します。各サブスクエアについて、サイズを使用して重み付けされた方向のヒストグラム (16 ビン) が計算されます。したがって、各部分正方形から 16 個の値を持つベクトルが得られます。4 つのそのようなベクトル (4 つの部分正方形) を合わせると、64 の値を持つ特徴ベクトルが得られます。これは、データのトレーニングに使用した特徴ベクトルです。
def hog(img):
gx = cv.Sobel(img, cv.CV_32F, 1, 0)
gy = cv.Sobel(img, cv.CV_32F, 0, 1)
mag, ang = cv.cartToPolar(gx, gy)
bins = np.int32(bin_n*ang/(2*np.pi)) # quantizing binvalues in (0...16)
bin_cells = bins[:10,:10], bins[10:,:10], bins[:10,10:], bins[10:,10:]
mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
hist = np.hstack(hists) # hist is a 64 bit vector
return hist
最後に、前と同様に、最初に大規模なデータセットを独立したユニットに分割します。各数値について、250 ユニットがトレーニング データ用に予約され、残りの 250 ユニットはテスト用に残されます。完全なコードは次のとおりです。ここからダウンロードすることもできます。
#!/usr/bin/env python
import cv2 as cv
import numpy as np
SZ=20
bin_n = 16 # Number of bins
affine_flags = cv.WARP_INVERSE_MAP|cv.INTER_LINEAR
def deskew(img):
m = cv.moments(img)
if abs(m['mu02']) < 1e-2:
return img.copy()
skew = m['mu11']/m['mu02']
M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
img = cv.warpAffine(img,M,(SZ, SZ),flags=affine_flags)
return img
def hog(img):
gx = cv.Sobel(img, cv.CV_32F, 1, 0)
gy = cv.Sobel(img, cv.CV_32F, 0, 1)
mag, ang = cv.cartToPolar(gx, gy)
bins = np.int32(bin_n*ang/(2*np.pi)) # quantizing binvalues in (0...16)
bin_cells = bins[:10,:10], bins[10:,:10], bins[:10,10:], bins[10:,10:]
mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
hist = np.hstack(hists) # hist is a 64 bit vector
return hist
img = cv.imread('digits.png',0)
if img is None:
raise Exception("we need the digits.png image from samples/data here !")
cells = [np.hsplit(row,100) for row in np.vsplit(img,50)]
# First half is trainData, remaining is testData
train_cells = [ i[:50] for i in cells ]
test_cells = [ i[50:] for i in cells]
deskewed = [list(map(deskew,row)) for row in train_cells]
hogdata = [list(map(hog,row)) for row in deskewed]
trainData = np.float32(hogdata).reshape(-1,64)
responses = np.repeat(np.arange(10),250)[:,np.newaxis]
svm = cv.ml.SVM_create()
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setType(cv.ml.SVM_C_SVC)
svm.setC(2.67)
svm.setGamma(5.383)
svm.train(trainData, cv.ml.ROW_SAMPLE, responses)
svm.save('svm_data.dat')
deskewed = [list(map(deskew,row)) for row in test_cells]
hogdata = [list(map(hog,row)) for row in deskewed]
testData = np.float32(hogdata).reshape(-1,bin_n*4)
result = svm.predict(testData)[1]
mask = result==responses
correct = np.count_nonzero(mask)
print(correct*100.0/result.size)
この特定のテクニックにより、ほぼ 94% の精度が得られました。SVM のさまざまなパラメータに異なる値を設定して、精度が向上するかどうかを確認してください。あるいは、その分野の技術論文を読んで実装してみることもできます。
追加のリソース
練習する
- OpenCV の例には Digits.py があります。これは上記の方法をわずかに改良し、より良い結果を実現します。参考資料も掲載しています。読んで理解してください。