10.4. 基于支持向量机的手写数据OCR

10.4.1. 目标

在本章中

  • 我们将重温手写数据OCR,但是,用支持向量机代替kNN。

10.4.2. 手写数字的OCR

在kNN中,我们直接使用像素强度作为特征向量。这次我们将使用 Histogram of Oriented Gradients (HOG)作为特征向量。

在这里,在找到HOG之前,我们使用图像的二阶矩对其进行反扭曲。所以我们首先定义一个函数 反作图() 它获取一个数字图像并对其进行反投影。函数的作用如下:

>>> 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方向找到每个细胞的Sobel导数。然后找出它们在每个像素处的梯度大小和方向。这个梯度被量化为16个整数值。将此图像分割为四个子正方形。对于每一个子正方形,计算方向直方图(16个格)及其大小的加权。所以每一个子正方形给你一个包含16个值的向量。四个这样的向量(四个子正方形)一起给了我们一个包含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个数据保留用于测试。完整代码如下:

>>> 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('/cvdata/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)
93.8

这种特殊的技术使我的准确率接近94%。你可以尝试不同的支持向量机参数值,以检查是否有可能更高的精度。或者你可以阅读这方面的技术论文并尝试实现它们。

10.4.4. 练习

  1. OpenCV示例包含 digits.py 对上述方法稍加改进,得到了改进的结果。它还包含引用。检查并理解它。