支持向量机实现任意个数手写数字识别(opencv+sklearn)

引言

本文基于OpenCV库和SVM算法,将MNIST用作训练集,在白板中绘制任意个数的手写数字,并对每个包含手写数字的图像进行处理作为测试集,预测图像中的数字并顺序输出。

本文记录了手写数字图像处理和机器学习图像识别的完整流程,可供初学者参考,其中还有许多可以改进的地方,欢迎各位提出意见和建议。

结果展示

识别结果:123

 识别结果:67899876

识别结果:5201314

需要用到的库

import numpy as np
import matplotlib.pyplot as plt
import datetime as dt
import cv2
from sklearn.preprocessing import MinMaxScaler
from sklearn.svm import NuSVC
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor

画图函数

def myplot(arr):
    plt.imshow(arr, cmap='gray', vmin=0, vmax=255)
    plt.xticks(ticks=[])
    plt.yticks(ticks=[])
    plt.show()
    plt.close()

准备训练数据集

手写数字训练集选择MNIST,该数据集来自美国国家标准与技术研究所,手写数字图像的像素大小为28*28,特征数为784。Torchvision. datasets中包含了大量用于机器学习模型训练的图像数据集,通过datasets.MNIST()方法下载/导入该数据集。由于MNIST训练集和测试集的样本数均较大,这里加载测试集的10000幅图像即可。

test_data = datasets.MNIST(root='data', train=False, download=True, transform=ToTensor())
MNIST手写数字图像示例

下载后测试集数据和标签的文件名分别为“t10k-images-idx3-ubyte”、“t10k-labels-idx1-ubyte”,将test_data中的数据和标签均另存为npy文件,以我们熟悉的numpy ndarray类型进行读取。 

Xdata = np.empty(shape=(10000, 28 * 28), dtype=np.float16)
Ydata = np.empty(shape=10000, dtype=np.uint8)
for idx, vari in enumerate(test_data):
    arr = vari[0].numpy()
    Xdata[idx, :] = arr.ravel()
    Ydata[idx] = vari[1]
np.save("./Dataset/Xdata.npy", Xdata)
np.save("./Dataset/Ydata.npy", Ydata)

准备测试数据

打开windows自带的画图程序或其他绘图软件,在画板中绘制手写数字,画笔选择记号笔,笔的线条不宜太粗。此外,数字之间应保持一定的间隔,以便提取单个数字的轮廓。

图像预处理

在白板中绘制好手写数字后,需要对每个手写数字图像进行预处理,以得到结构化的模型输入数据。本文中图像处理采用OpenCV库实现,它是一个开源的计算机视觉库,被广泛应用于图像处理、图像分割、图像识别等场景。

加载图像

首先使用imread()函数读取含有手写数字的图像,cv2.IMREAD_GRAYSCALE表示以灰度形式加载图像(范围0~255),函数返回一个二维数组表示灰度值。当选择cv2.IMREAD_COLOR参数时,函数将返回3个二维数组,分别用于R、G、B3个通道。将图像的灰度值进行反转,使得图像中背景的灰度值设为0,与训练集图像保持一致。

image = cv2.imread(figpath, cv2.IMREAD_GRAYSCALE)
image = 255 - image
加载图像(背景为255)
灰度值反转(背景为0)

图像二值化

由于一幅图像中含有多个手写数字,需要将其自动裁剪为只含有单个数字的图像,才能用于分类过程中的测试集数据。findContours()方法用于提取图像中物体的轮廓/边缘,它返回的contours参数中含有物体轮廓在图像中的坐标,是实现单个数字提取的关键函数。

首先需要将图像进行二值化,threshold()函数对图像进行阈值分割并创建二值化图像,阈值处理将图像前景像素与背景像素分开。该函数的主要参数有阈值(thresh)、最大像素值(maxval)、阈值类型,函数第一个返回值为retval,第二个返回值为阈值分割后的像元数组。阈值类型选择cv2.THRESH_BINARY,该参数的阈值分割结果dst(x,y) = maxval if src(x,y)>thresh 0 otherwise,即二值化的阈值分割,小于阈值的像素都设置为零,其余为最大像素值。

ret, binary = cv2.threshold(image, 50, 255, cv2.THRESH_BINARY)
图像二值化

获取图像轮廓

使用findContours()方法识别二值图像中手写数字的轮廓,该方法需设置两个关键参数:轮廓检索模式(mode)和轮廓近似法(method),本文选择的轮廓检索模式为cv2.RETR_EXTERNAL,该选项仅检索手写数字最外层的轮廓,轮廓近似法选择cv2.CHAIN_APPROX_SIMPLE,它将压缩轮廓的水平、垂直和对角线段,只保留轮廓的端点。函数返回contours和hierarchy两个参数,contours以点集的形式记录轮廓的xy坐标位置,hierarchy记录了contours数组的索引编号,可反映轮廓之间的层次信息。绘制图像轮廓使用drawContours()方法,参考OpenCV中的轮廓 (apachecn.github.io)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

得到手写数字的轮廓后,使用boundingRect()方法计算轮廓的外接矩形,函数返回x、y、w、h,(x,y)为外接矩形的左上角坐标,w和h分别为矩形的宽和高,利用外接矩形的坐标信息裁剪原始图像,得到只含有单个数字的图像。

for contour in contours:
    x, y, w, h = cv2.boundingRect(contour)
    image_clip = image[y:y + h, x:x + w]
单个数字裁剪

扩充图像边界

此时我们已经自动裁剪出每个手写数字的图像,但仍与MNIST数据集中的原始数据存在差异,数字的外轮廓与图像边缘是存在一定距离的,因此需要用到copyMakeBorder()函数扩充图像的边界,该函数需定义上、下、左、右四个方向上扩充的宽度,以及边界类型(borderType),边界类型选择cv2.BORDER_CONSTANT,以常量填充整个边界(还需指定填充的颜色value),图像边缘四个方向上需要扩充的宽度由用户自行定义,在这里变量edge_h和edge_w分别定义了图像垂直和水平方向上需扩充的宽度。

edge_h = int(h * 0.25)
edge_w = (h + edge_h + edge_h - w) * 0.5
if edge_w < edge_h:
    edge_w = edge_h * 2
else:
    edge_w = int(edge_w * 0.9)
constant = cv2.copyMakeBorder(image_clip, edge_h, edge_h, edge_w, edge_w, cv2.BORDER_CONSTANT, value=(0, 0, 255))
扩充图像边界

形态学操作(可选)

在对图像进行重采样之前,还需对数字线条宽度进行处理。由于我们在屏幕中绘制的手写数字线条粗细是任意的,数字线条过粗或过细可能会影响模型预测的准确率,因此可对图像进行形态学操作,当线条过粗时选择腐蚀操作,当线条过细时选择膨胀操作。腐蚀操作将侵蚀前景对象的边界(前景为白色),当内核中所有像素为1时,当前像素设为1,反之被侵蚀(设为0)。膨胀操作将增加前景对象的大小,当内核中至少有一个像素为1,则当前像素设为1。腐蚀函数为erode(),膨胀函数为dilate(),二者需设置内核大小(kernel)和迭代次数(iterations)。

kernel = np.ones((10, 10))
imerode = cv2.erode(constant, kernel, iterations=1)
原始图像
膨胀操作
腐蚀操作
kernel = np.ones((10, 10))
imdilate = cv2.dilate(constant, kernel, iterations=1)

图像重采样

最后将处理好的图像进行重采样,使其具有与MNIST数据集同样的分辨率(28*28),用到的函数为resize(),指定缩放后的图像分辨率,插值方法(interpolation)选择cv2.INTER_AREA。

imresize = cv2.resize(imdilate, (28, 28), interpolation=cv2.INTER_AREA)
图像重采样

支持向量机训练和测试

本文使用scikit-learn库中的NuSVC类对手写数字进行训练和预测,scikit-learn是一个开源的机器学习库,它支持数十种监督和无监督机器学习的算法和模型,它们被称为估算器(estimators),并提供了机器学习过程中所需要的模型拟合、数据预处理、模型选择、模型评估等多种工具。

scikit-learn中的支持向量机分类器有SVC、NuSVC、LinearSVC三种,SVC和NuSVC的原理相似,区别在于NuSVC引入了新参数nu用于控制支持向量的数量,以下是NuSVC分类器的参数,含义详见sklearn.svm.NuSVC-scikit-learn中文社区

class sklearn.svm.NuSVC(*, nu=0.5, kernel='rbf', degree=3, gamma='scale', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', break_ties=False, random_state=None)

首先导入准备好的模型训练数据。

Xdata = np.load("./Dataset/Xdata.npy")
Ydata = np.load("./Dataset/Ydata.npy")

初始化一个支持向量机分类器,优化好参数,nu=0.02,gamma=0.02(参考数字识别,从KNN,LR,SVM,RF到深度学习),由于NuSVC训练10000组数据时耗时明显增加,这里训练7000组数据即可。

clf = NuSVC(nu=0.02, kernel='rbf', gamma=0.02)
clf.fit(Xdata[0:7000, :], Ydata[0:7000])

在模型预测之前,对测试集数据进行归一化处理,使用MinMaxScaler()类将输入数据缩放到给定区间内,参数feature_range=(0,1)表示缩放后的数据大小在0和1之间。使用fit_transform()方法将其应用在测试集上。

scaler = MinMaxScaler(feature_range=(0, 1))
arr = scaler.fit_transform(imresize)

使用reshape()函数更改测试图像数组的形状,预测输入图像所对应的数字,展示模型预测的结果,验证支持向量机算法预测的数字是否与我们所写数字一致。

prediction = clf.predict(arr.reshape(1, -1))
print(prediction[0])

完整代码

import numpy as np
import matplotlib.pyplot as plt
import datetime as dt
import cv2
from sklearn.preprocessing import MinMaxScaler
from sklearn.svm import NuSVC

def feature_extra(figpath):
    image = cv2.imread(figpath, cv2.IMREAD_GRAYSCALE)
    image = 255 - image

    ret, binary = cv2.threshold(image, 50, 255, cv2.THRESH_BINARY)
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    digits = []
    positions = []
    if len(contours) != 0:
        for contour in contours:
            x, y, w, h = cv2.boundingRect(contour)
            # 变量positions:对图像中数字所在位置的简单排序,可能失效
            positions.append(x + y * 3)
            image_clip = image[y:y + h, x:x + w]
            edge_h = int(h * 0.25)
            edge_w = (h + edge_h + edge_h - w) * 0.5
            if edge_w < edge_h:
                edge_w = edge_h * 2
            else:
                edge_w = int(edge_w * 0.9)
            constant = cv2.copyMakeBorder(image_clip, edge_h, edge_h, edge_w, edge_w, cv2.BORDER_CONSTANT,
                                          value=(0, 0, 255))
            kernel = np.ones((10, 10))
            imdilate = cv2.dilate(constant, kernel, iterations=1)
            imresize = cv2.resize(imdilate, (28, 28), interpolation=cv2.INTER_AREA)
            digits.append(imresize)

    return digits, positions

def digitsCLF(digits, positions):
    Xdata = np.load("./Dataset/Xdata.npy")
    Ydata = np.load("./Dataset/Ydata.npy")

    clf = NuSVC(nu=0.02, kernel='rbf', gamma=0.02)
    clf.fit(Xdata[0:7000, :], Ydata[0:7000])

    results = []
    for digit in digits:
        scaler = MinMaxScaler(feature_range=(0, 1))
        arr = scaler.fit_transform(digit)

        prediction = clf.predict(arr.reshape(1, -1))
        results.append(int(prediction[0]))

    orders = np.argsort(positions)
    str_digit = ' '
    for i in orders:
        vari = results[i]
        str_digit = str_digit + str(vari)

    print(f'The handwritten digits are:{str_digit}')

if __name__ == '__main__':
    figpath = "./Figures/Fig (1).png"
    dt1 = dt.datetime.now()
    digit, position = feature_extra(figpath)
    if len(position) == 0:
        print('Recognition failed!')
    else:
        digitsCLF(digit, position)
    print(f'Duration: {dt.datetime.now() - dt1}')

  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 支持向量机(SVM)是一种常用的机器学习算法,广泛应用于分类和回归任务中。在手写数字识别中,我们可以使用SVM训练一个分类器,将手写数字分为不同的类别。 在Python中,有许多开源的机器学习库可以使用,如scikit-learn、TensorFlow等。这些库提供了许多SVM模型的实现,我们可以直接调用库函数实现手写数字识别任务。 具体步骤如下: 1. 数据集准备:我们需要准备一个数据集,包含许多手写数字的图像以及它们对应的类别标签。这个数据集可以从开源资源中获得,如MNIST数据集。 2. 特征提取:将每个图像转换为数字特征向量,以便SVM算法能够进行训练。我们可以使用常用的特征提取方法,例如灰度化、二值化、HOG特征等。 3. SVM模型训练:使用scikit-learn库中的SVM分类器,将数据集的特征向量和标签输入到模型中进行训练。 4. 模型测试:使用测试集中的手写数字图像,通过模型进行预测,并与真实标签进行比较,来测试模型的准确率。 最终,我们可以通过SVM算法完成手写数字识别的任务。这种方法的优点是准确率较高且具有较好的泛化能力,可以应用于许多其他的分类任务中。同时,也可以通过调整特征提取方法和SVM模型参数来提高识别准确率。 ### 回答2: 基于支持向量机(Support Vector Machine,SVM)的手写数字识别使用Python进行实现,需要以下步骤: 1. 数据预处理:将手写数字图片转换成数字矩阵,可以使用Python中的OpenCV或PIL库来读取图片,并将RGB值转换成灰度值;接着可以使用Numpy将灰度矩阵转换为特征向量。 2. 数据切分:将预处理后的数据集分为训练集和测试集。通过切分数据集,我们可以在模型训练之前评估数据集的质量和模型性能。 3. 特征提取:使用特征提取方法,将数字矩阵转换为一组数字特征。可以考虑使用HOG方法来提取特征,或者使用其他的特征提取算法。 4. 模型训练:使用SVM算法来训练模型,并根据模型的训练误差和准确率做出相应的调整。 5. 模型评估:在测试集上进行模型评估,计算模型的精确度、召回率、F1值等性能指标,判断模型是否达到预期的识别效果。 最后,我们可以将训练好的SVM模型应用于未知的手写数字图像中,实现数字识别的功能。通过不断的优化和调整模型参数,可以提高识别准确率和效率,满足实际生产和应用的需求。 ### 回答3: 支持向量机(Support Vector Machine, SVM)是一种常见的机器学习算法,它也是许多图像识别、语音识别等领域所使用的算法之一。手写数字识别是机器学习领域中的一个经典问题,基于SVM的手写数字识别的原理也是类似。具体实现可以使用Python语言。 首先,需要准备一个手写数字数据集,这里可以使用MINST数据集。将数据集用Python程序读取进来,并将其转换为向量形式,然后对向量数据进行标准化处理。 然后,使用Python中的scikit-learn库来训练一个SVM模型,此处以线性核函数为例。训练过程中,需要将数据集划分为训练集和测试集。在训练过程中调整参数,找到最优的超参数。 训练完成以后,使用测试集测试模型的准确率。针对错误分类的样本,可以通过可视化方式帮助分析算法的性能,并对算法进行优化。 最后,将模型保存下来,以便后续使用。 综上所述,基于SVM的手写数字识别Python实现,需要以下步骤:数据预处理、训练SVM模型、测试模型性能、分析结果并对模型进行优化、保存模型。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值