Python, OCR рукописных цифр на основе OpenCV SVM
В предыдущем разделе было представлено рукописное цифровое распознавание символов на основе KNN + распознавание букв., в этом разделе будет представлено распознавание рукописных цифр на основе метода опорных векторов SVM.
1. Рендеринг
Диаграмма тренировочного эффекта простой линейной векторной машины выглядит следующим образом:На рисунке 4 точки, 3, как правило, белые точки, и одна серо-черная точка, видно, что граница решения разделительной линии очевидна.
Рендеринг данных обучения нелинейной векторной машины выглядит следующим образом:
На рисунке ниже зеленые и синие точки смешаны вместе, а граница решения в середине нелинейна, но ее можно аппроксимировать как линейную. Точка с серым кружком на границеопорный вектор, опираясь на эти небольшие объемы данных, можно найтиграница решения.
2. SVM и принцип
Машины опорных векторов SVM (поддерживаемые векторные машины)
Чтобы понять SVM, вам сначала нужно понять линейно разделимые данные и линейно неразделимые данные, Короче говоря, есть куча точек на плоскости или многомерных для классификации, могут ли они быть разделены линией для классификации друг друга.
-
Линейно разделимые данные
KNN необходимо рассчитать расстояние от тестовых данных до всех точек.Когда объем данных относительно велик, для хранения требуется большой объем памяти.Можно поступить иначе: найти линию f(x)=ax_1+bx_2+c, которая делит данные на две области. Когда вы получите новый test_data X, просто замените его на f(x). Если f(X) > 0, он принадлежит к синей группе, в противном случае — к красной.
позвонить по этой линииГраница решения, которая очень проста и эффективна с точки зрения памяти.Такие данные, которые можно разделить пополам прямой линией (или гиперплоскостью более высокого измерения), называютсяЛинейно разделимые данные.
-
Нелинейно разделимые данные в низкоразмерном пространстве с большей вероятностью станут линейно разделимыми в многомерном пространстве.
На изображении выше можно увидеть много таких линий.Какой взять? Вполне интуитивно можно сказать, что линия должна быть как можно дальше от всех точек.
Переход на самую дальнюю линию обеспечит большую помехозащищенность. Итак, что делает SVM, так это находит линию (или гиперплоскость) с наибольшим минимальным расстоянием до обучающей выборки.
- Чтобы найти эту границу решения, вам нужны не все данные, а только те, которые близки к противоположной группе.
На этом изображении они представляют собой сплошной синий круг и два сплошных красных квадрата. Мы можем назвать их опорными векторами, а прямую, проходящую через них,Опорная плоскость. их достаточно, чтобы найтиграница решения.
- Вектор весов определяет ориентацию границы решения, а точка смещения определяет ее местоположение.
2. Исходный код
2.1 Распознавание рукописных цифр SVM
# 使用SVM进行手写数据OCR
# 在KNN中直接使用像素强度作为特征向量。
# 在SVM中使用方向梯度直方图(HOG Histogram of Oriented Gradients)作为特征向量。
# 在这里,使用二阶矩对图像进行反扭曲。
import cv2
import numpy as np
SZ = 20
bin_n = 16 # Number of bins
svm_params = dict(kernel_type=cv2.ml.SVM_LINEAR,
svm_type=cv2.ml.SVM_C_SVC,
C=2.67, gamma=5.383)
affine_flags = cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR
# 左图像是原始图像,右图像是倾斜图像。
def deskew(img):
m = cv2.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 = cv2.warpAffine(img, M, (SZ, SZ), flags=affine_flags)
return img
# (HOG Histogram of Oriented Gradients)方向梯度直方图
def hog(img):
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
mag, ang = cv2.cartToPolar(gx, gy)
# 量化 (0...16)的binvalues
bins = np.int32(bin_n * ang / (2 * np.pi))
# 分成四个子块
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)
return hist
img = cv2.imread('images/digits.png', 0)
print(img.shape) # (1000,2000)
cells = [np.hsplit(row, 100) for row in np.vsplit(img, 50)]
print(len(cells)) # 50*100
# 一半数据用于训练,一半用于测试(前50列,后50列)
train_cells = [i[:50] for i in cells]
test_cells = [i[50:] for i in cells]
# cv2.imshow("img", train_cells[0][0])
# cv2.imshow("deskew", deskew(train_cells[0][0]))
# cv2.waitKey(0)
# 训练数据
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]
print('trainData: ', type(trainData), len(trainData))
print('responses: ', type(responses), responses.shape, len(responses))
print(responses[0])
svm = cv2.ml.SVM_create()
svm.setGamma(svm_params['gamma'])
svm.setC(svm_params['C'])
svm.setKernel(cv2.ml.SVM_LINEAR)
svm.setType(cv2.ml.SVM_C_SVC)
svm.train(trainData, cv2.ml.ROW_SAMPLE, responses)
# 把训练的数据及模型保存下来
svm.save('images/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]
print('result: ', type(result))
print('responses: ', type(responses))
# 检查准确度
mask = result == responses
correct = np.count_nonzero(mask)
print('correct: ', correct)
# SVM得到93.8%的准确度,比KNN的91.76%要高一些
print(correct * 100.0 / len(list(result)))
2.2 Нелинейный SVM
from __future__ import print_function
import random as rng
import cv2 as cv
import numpy as np
NTRAINING_SAMPLES = 100 # Number of training samples per class
FRAC_LINEAR_SEP = 0.9 # Fraction of samples which compose the linear separable part
# 可视化窗口大小
WIDTH = 512
HEIGHT = 512
I = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8)
# 随机生成训练数据
trainData = np.empty((2 * NTRAINING_SAMPLES, 2), dtype=np.float32)
labels = np.empty((2 * NTRAINING_SAMPLES, 1), dtype=np.int32)
rng.seed(100) # 随机生成分类标签
# 为训练数据设置线性分离区
# Set up the linearly separable part of the training data
nLinearSamples = int(FRAC_LINEAR_SEP * NTRAINING_SAMPLES)
# 为分类1生成随机点
trainClass = trainData[0:nLinearSamples, :]
# x在[0,0.4]
c = trainClass[:, 0:1]
c[:] = np.random.uniform(0.0, 0.4 * WIDTH, c.shape)
# y在[0, 1)
c = trainClass[:, 1:2]
c[:] = np.random.uniform(0.0, HEIGHT, c.shape)
# 为分类2生成随机点
trainClass = trainData[2 * NTRAINING_SAMPLES - nLinearSamples:2 * NTRAINING_SAMPLES, :]
# x在 [0.6, 1]
c = trainClass[:, 0:1]
c[:] = np.random.uniform(0.6 * WIDTH, WIDTH, c.shape)
# y在 [0, 1)
c = trainClass[:, 1:2]
c[:] = np.random.uniform(0.0, HEIGHT, c.shape)
# 为测试数据集的分类1,2分别生成随机点
trainClass = trainData[nLinearSamples:2 * NTRAINING_SAMPLES - nLinearSamples, :]
# x在[0.4,0.6]
c = trainClass[:, 0:1]
c[:] = np.random.uniform(0.4 * WIDTH, 0.6 * WIDTH, c.shape)
# y在[0,1]
c = trainClass[:, 1:2]
c[:] = np.random.uniform(0.0, HEIGHT, c.shape)
# 设置分类标签1及2
labels[0:NTRAINING_SAMPLES, :] = 1 # 分类1
labels[NTRAINING_SAMPLES:2 * NTRAINING_SAMPLES, :] = 2 # 分类2
# 开始训练,首先设置支持向量机SVM参数
print('Starting training process')
# 初始化
svm = cv.ml.SVM_create()
svm.setType(cv.ml.SVM_C_SVC)
svm.setC(0.1)
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setTermCriteria((cv.TERM_CRITERIA_MAX_ITER, int(1e7), 1e-6))
# 训练SVM
svm.train(trainData, cv.ml.ROW_SAMPLE, labels)
# 结束训练
print('Finished training process')
# 展示决策区域(绘制蓝色,绿色) 分类1为绿色,分类2为蓝色
green = (0, 100, 0)
blue = (100, 0, 0)
for i in range(I.shape[0]):
for j in range(I.shape[1]):
sampleMat = np.matrix([[j, i]], dtype=np.float32)
response = svm.predict(sampleMat)[1]
if response == 1:
I[i, j] = green
elif response == 2:
I[i, j] = blue
# 展示测试数据
thick = -1
# 分类1 绿色
for i in range(NTRAINING_SAMPLES):
px = trainData[i, 0]
py = trainData[i, 1]
cv.circle(I, (px, py), 3, (0, 255, 0), thick)
# 分类2 蓝色
for i in range(NTRAINING_SAMPLES, 2 * NTRAINING_SAMPLES):
px = trainData[i, 0]
py = trainData[i, 1]
cv.circle(I, (px, py), 3, (255, 0, 0), thick)
# 展示支持向量
thick = 2
sv = svm.getUncompressedSupportVectors()
for i in range(sv.shape[0]):
cv.circle(I, (sv[i, 0], sv[i, 1]), 6, (128, 128, 128), thick)
cv.imwrite('non_linear_svms_result.png', I) # 保存图片
cv.imshow('SVM for Non-Linear Training Data', I) # 展示图片结果
cv.waitKey()