【应用篇】支持向量机SVM识别数字集(数据采集+模型训练+预测输出)

支持向量机

支持向量机(SVM)是建立在统计学理论理论上的一种数据挖方法,能成功的处理回归问题和模式识别(分类问题、判别问题)等诸多问题,并可推广与预测和综合评价等领域学科。SVM理论的机理是寻找一个满足分类要求的最优解分类超平面,使得该超平面在保证精度的同时,能够支持超平面两侧的空白区域最大化。理论上,支持向量机能够实现对线性可分类数据的最优分类。

使用SVM识别一个物体

在机器视觉的领域中比较重要的有分类问题,我们就可以使用SVM进行图像的分类。当然,如果需要跟进一步的知道图像中的目标在图像的那个位置,具体是什么,这就涉及到目标检测的方法。
使用SVM支持向量机算法主要解决了识别物体是什么的问题,物体的具体位置通过相关的计算机视觉算法完成。具体的实现步骤分为3步:第一步,将输入图像进行预处理,转化为合适大小的灰度图;第二步,查找图像轮廓并根据轮廓大小进行筛选,找到符合预期大小的轮廓位置;第三步,根据轮廓的最小外界矩提取出图像ROI并拉伸为固定的60*60大小;第四步,加载模型数据,将拉伸后的ROI进行数据转化为可以处理的标准数据后进行预测并输出预测结果。
通过这样的方法其实是可以获得轮廓的外界矩中心坐标点的,这个坐标点反应的位置是一个大概的估计,对比起深度学习方法的目标检测还欠缺很多。如果你的场景只是在简单的应用,并进行一些分类识别,那SVM也是可以胜任的。
在这里插入图片描述

必须了解的两个参数

在SVM方法中有两个重要的参数对结果的影响较大,分别是(1)误差惩罚参数C和(2)核函数的形式及其参数。
在(1)中通过设置C实现对错样本比例和算法复杂度折衷,即在确定的特征空间中调节学习机器的置信范围和经验风险比例,使学习机器的推广能力最好。其选取由具体的问题而定,并取决于数据中噪声的数量。每个特征空间至少存在一个合适的C使得SVM的推广能力最好。
在(2)中不同的核函数对分类性能有影响,相同核函数不同参数也有影响。

SVM的识别流程

SVM的数据采集、数据标准化、模型训练、预测结果等几个主要流程。使用SVM的主要步骤如下:根据目标物使用轮廓筛选等方法采集特征区域图像,将特征区域图像进行保存和分类,之后再进一步将分类好的图像数据进行格式转化和数据降维,处理好的事数据将导入到模型训练中,根据设定的核函数参数以及惩罚系数进行模型的训练,训练好模型后将其保存。在预测时通过加载训练好的模型文件,用轮廓筛选的方法查找出图像中的待定目标区域,截取该区域后输入到SVM分类器中,根据模型文件分类器会给出当前模型的分类值。进而就得到了目标的轮廓中心位置和分类信息。
在这里插入图片描述

代码实现

在这里我们一步一步的实现
代码的环境为:windws; pycharm; anaconda; python3.6.9; opencv-python4.5.1.48; numpy1.19.5

图像采集

第一步当然是采集需要分类的数字图像,通过采集到的推向调节图像预处理的各个参数,达到将数字从图像中提取的效果。
下面的代码可以将我图像中的数字给抠出来,在不同的摄像机和背景条件下需要修改几个参数,来让roi提取的准确。后期的数字能准确的识别到的基础就是前期参数的调节是否准确。需要调参的地方看注释

import cv2 as cv

img = cv.imread('101.jpg',0)
kernel = np.ones((3,3),np.uint8)    #开运算算子
im = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)    #腐蚀、膨胀
#thresh = cv.adaptiveThreshold(bul,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,11,2)  #自适应阈值化(高斯均值),需要改自己的合适的阈值
thresh = cv.adaptiveThreshold(im,255,cv.ADAPTIVE_THRESH_MEAN_C,cv.THRESH_BINARY,13,4) #自适应阈值化(平均值),需要改自己的合适的阈值,两个自适应阈值方法那个提取的区域更纯净就用哪个
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)  #寻找轮廓

x0 = 30      #截取部分坐标记录值
count = 0

for cnt in contours:
    mates = cv.contourArea(cnt)
    if mates>13000 and mates<15500:     #使用边缘面积过滤较小边缘框	,需要自己调节参数,找出合适的轮廓面积
        rect = cv.minAreaRect(cnt)
        box = cv.boxPoints(rect)
        box = np.int0(box)
        cv.drawContours(im, cnt, -1, (175,0), 3)    #绘制轮廓
        #[x,y,w,h] = []
        [x,y,w,h] = cv.boundingRect(cnt)    #读取当前轮廓的坐标值
        #cv.imshow('norm2', im)
        if(abs((x-x0))>60):             #过滤重复的选框
            x0 = x
            if  h>120 and h < 160 and w>90 and w<120:        #使用高过滤小框和大框,需要自己调节参数,找出高和宽合适的轮廓
                count+=1
                cv.rectangle(im,(x,y),(x+w,y+h),(0,0,255),2)    #画框
                roi = thresh[y:y+h,x:x+w]                       #框出带选定区域
                roismall = cv.resize(roi,(60,60))               #拉伸剪裁区域
                cv.imshow('norm2'+ str(count),roismall)

cv.imshow('norm',im)
cv.waitKey(0)
cv.destroyAllWindows()

在程序中定义了x0 = 30 count = 0,x0的作用是过滤掉位置比较接近的框,保证一个数字周围只有一个框。count 的作用是计数提取到的roi区域。
在这里插入图片描述
通过原始图像提取出来的数字全部拉升到固定的尺寸,提取的效果就像上面截图中小的那两个一样。

保存采集的图像

这里我们简单的把上面识别一张图形的代码该做摄像头的视频流,然后把取出来的60*60小数字图像保存在自己电脑的文件夹里。

import cv2 as cv
import numpy as np

num = 1 # 递增,用来保存文件名
x0 = 30      # 截取部分坐标记录值
num0 = 0
num1 = 0
num2 = 0

kernel = np.ones((3,3),np.uint8)    # 开运算算子

def imgewrite(imge):    #保存采集的数据
    global num
    cv.imwrite("F:/CV/Project/num/newlimg/" + str(num) + ".jpg", imge)	#我电脑是windows,这里根据自己需要修改保存图像的路径
    print("success to save" + str(num) + ".jpg")
    num += 1

if __name__ == "__main__":
    count = 0
    cap = cv.VideoCapture(0)    # 从摄像头读取数据
    while cap.isOpened():
        ret,frame = cap.read()
        img = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        if not ret:
            print("Can't receive frame (stream end?). Exiting ...")
            break
        #img = cv.imread('18.jpg',0)
        bul = cv.GaussianBlur(img,(7,7),0)     # 定义高斯核滤波
        im = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)    # 腐蚀、膨胀
        #thresh = cv.adaptiveThreshold(bul,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,11,2)  # 自适应阈值化(高斯均值)
        thresh = cv.adaptiveThreshold(im,255,cv.ADAPTIVE_THRESH_MEAN_C,cv.THRESH_BINARY,13,4) # 自适应阈值化(平均值)

        contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)  #寻找轮廓
        # cv.imshow('image' ,thresh)
        for cnt in contours:
            mates = cv.contourArea(cnt)
            if mates>13000 and mates<15500:     # 使用边缘面积过滤较小边缘框
                [x,y,w,h] = cv.boundingRect(cnt)    # 读取当前轮廓的坐标值
                #cv.imshow('norm2', im)
                if(abs((x-x0))>60):             # 过滤重复的选框
                    x0 = x
                    if  h>120 and h < 160 and w>90 and w<120:        # 使用高过滤小框和大框
                        count+=1
                        roi = thresh[y:y + h, x:x + w]  # 框出带选定区域
                        roismall = cv.resize(roi, (60, 60))  # 拉伸剪裁区域
                        imgewrite(roismall)    # 保存剪裁的区域用于图像训练,预测数据时注释
                        cv.drawContours(im, cnt, -1, (175, 0), 3)    # 绘制轮廓
                        cv.rectangle(im,(x,y),(x+w,y+h),(0,0,255),2)    #画框
        cv.imshow('test',im)
        if cv.waitKey(1) == ord('q'):
            break

    cap.release() #释放摄像头
    cv.destroyAllWindows()  #释放窗口

运行脚本,然后移动摄像头,图像会自动采集并且保存在路径中。因为是数字图像,我们需要手动把保存下来的图像分类,放到不同的文件夹中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
效果呢大概就是上面图像中的那样。到这里我们就完成了图像的采集。

数据集处理

采集到了分类好的图像数据,我们需要对图像数据进行转化和处理,让他变为我们训练SVM模型时需要的数据。通过下面的脚本可以生成需要的CSV文件。这里的脚本注意修改自己的文件路径。

#从指定路径下遍历每个数字标签对应的文件夹中的所有图片
#读取出该文件夹下的所有图片并保存到csv文件中
#读取的图片大小必须拉伸为固定长宽的图像
# @2022-1-12
# @from SWUST-IPC

import csv
import os
import cv2

def convert_img_to_csv(img_dir):
    #设置需要保存的csv路径
    with open(r"F:\CV\Project\SVM\num\newlimg\limge.csv","w",newline="")as f:
        #设置csv文件的列名
        column_name = ["label"]
        column_name.extend(["pixel%d"%i for i in range(60*60)])
        #将列名写入到csv文件中
        writer = csv.writer(f)
        writer.writerow(column_name)
        #该目录下有9个目录,目录名从0-9
        for i in [1,2,3,4,5,6,7,8]:
            #获取目录的路径
            img_temp_dir = os.path.join(img_dir,str(i))
            #获取该目录下所有的文件
            img_list = os.listdir(img_temp_dir)
            #遍历所有的文件名称
            for img_name in img_list:
                #判断文件是否为目录,如果为目录则不处理
                if not os.path.isdir(img_name):
                    #获取图片的路径
                    img_path = os.path.join(img_temp_dir,img_name)
                    #因为图片是黑白的,所以以灰色读取图片
                    img = cv2.imread(img_path,cv2.IMREAD_GRAYSCALE)
                    #图片标签
                    row_data = [i]
                    #获取图片的像素
                    row_data.extend(img.flatten())
                    #将图片数据写入到csv文件中
                    writer.writerow(row_data)

if __name__ == "__main__":
    #将该目录下的图片保存为csv文件
    convert_img_to_csv(r"F:\CV\Project\SVM\num\newlimg")

在这里插入图片描述
然后就会得到这样的一个文件。

训练数据

在训练这里我没有添加数据测试,有需要的自己改一改就可以。
需要注意的是自己各个文件对应的路径,不要弄错。

import pandas as pd
from sklearn.decomposition import PCA
# PCA方法用于数据的预处理,数据降维,PCA的一般步骤是:先对原始数据零均值化,然后求协方差矩阵,
# 接着对协方差矩阵求特征向量和特征值,这些特征向量组成了新的特征空间。
from sklearn import svm
import joblib   # 导入包用于模型的保存和加载预测
import time

if __name__ =="__main__":
    # train_num = 5000
    # test_num = 7000
    data = pd.read_csv('limge.csv')
    train_data = data.values[0: ,1:]
    train_label = data.values[0: ,0 ]
    #test_data = data.values[train_num:test_num,1:]
    #test_label = data.values[train_num:test_num,0]
    t = time.time()

    # PCA降维
    # PCA降维,数据预处理
    # @n_componentsPCA:算法中所要保留的主成分个数n,也即保留下来的特征个数n
    # @whiten:白化,使得每个特征具有相同的方差。
    pca = PCA(n_components=0.8, whiten=True)
    print('start pca...')
    train_x = pca.fit_transform(train_data)
    #test_x = pca.transform(test_data)
    print(train_x.shape)

    # svm训练
    # @C:惩罚系数,用来控制损失函数的惩罚系数
    # @kernel:算法使用的核函数类型
    #  "RBF"径向基核,也就是高斯核函数;Linear指的是线性核函数;Poly指的是多项式核;Sigmoid指的是双曲正切函数tanh核
    print('start svc...')
    svc = svm.SVC(kernel = 'rbf', C = 10)
    svc.fit(train_x,train_label)    # @fit方法:在数据集(X,y)上拟合SVM模型
    #pre = svc.predict(test_x)  #训练后返回预测标签值

    #保存模型
    joblib.dump(svc, 'model.m')     # 保存SVM模型
    joblib.dump(pca, 'pca.m')       # 保存预处理后的数据

    # 计算准确率
    #score = svc.score(test_x, test_label)
    #print(u'准确率:%f,花费时间:%.2fs' % (score, time.time() - t))

这之后会生成两个文件,分别为model.m和pca.m。只要生成了这两个文件就说明我们训练成功了。

预测数字

这里我直接把最终的预测本展示,其中包含了保存训练数据的部分,通过对不同部分的注释与使用就可以进行预测或者采集。

# 检测部分函数与数据采集
# 使用opencv轮廓处理与筛选提取出目标区域,使用支持向量机对图像数字进行分类识别,并输出
# @2022-1-12
# @from SWUST-IPC-CVTeam
# @测试环境为:windws; pycharm; anaconda; python3.6.9; opencv-python4.5.1.48; numpy1.19.5
import numpy as np
from matplotlib import pyplot as plt
import sys
import cv2 as cv
import joblib

num = 1     #递增,用来保存文件名
x0 = 30      #截取部分坐标记录值
num0 = 0
num1 = 0
num2 = 0

kernel = np.ones((3,3),np.uint8)    #开运算算子

def svmdetect(imges):
    #res_img = cv.resize(imges , (28,28))   #修改图像尺寸
    #test = res_img.res_img(1,784)  #将图片转化为1行784(28*28)列自然数
    test = imges.reshape(1,3600)
    # 加载模型
    svc = joblib.load("model.m")
    pca = joblib.load("pca.m")
    # svm
    #print('start pca...')
    test_x = pca.transform(test)    #标准化数据
#    print(test_x.shape)
    pre = svc.predict(test_x)   #预测
    #print(pre[0])   #显示预测标签值
    return pre[0]

def labershow(num):     #使相同数据只输出一次
    global  num1,num0,num2
    if (num0 + num) != num1:
        num2 = 0
        if num2 == 0:
            print("laber is ",num)
            num2 = 1
        num1 = num0 + num
    num0 = num



def imgewrite(imge):        # 保存训练图片
    global num
    cv.imwrite("F:/CV/Project/num/newlimg/" + str(num) + ".jpg", imge)
    print("success to save" + str(num) + ".jpg")
    num += 1

if __name__ =="__main__":

    cap = cv.VideoCapture(0)

    while cap.isOpened():
        successful, frame = cap.read()
        img = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        if not successful:
            print("Can't receive frame (stream end?). Exiting ...")
            break
        #img = cv.imread('18.jpg',0)
        bul = cv.GaussianBlur(img,(7,7),0)     #定义高斯核滤波
        im = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)    #腐蚀、膨胀
        #thresh = cv.adaptiveThreshold(bul,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,11,2)  #自适应阈值化(高斯均值)
        thresh = cv.adaptiveThreshold(im,255,cv.ADAPTIVE_THRESH_MEAN_C,cv.THRESH_BINARY,13,4) #自适应阈值化(平均值)

        contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)  #寻找轮廓

        samples =  np.empty((0,900))

        #cv.imshow('image' ,thresh)
        responses = []
        keys = [i for i in range(48,58)]    #48-58为ASCII码
        count =0

        for cnt in contours:
            mates = cv.contourArea(cnt)
            if mates>13000 and mates<15500:     #使用边缘面积过滤较小边缘框
                [x,y,w,h] = cv.boundingRect(cnt)    #读取当前轮廓的坐标值
                #cv.imshow('norm2', im)
                if(abs((x-x0))>60):             #过滤重复的选框
                    x0 = x
                    if  h>120 and h < 160 and w>90 and w<120:        #使用高过滤小框和大框
                        count+=1
                        roi = thresh[y:y + h, x:x + w]          # 框出带选定区域
                        roismall = cv.resize(roi, (60, 60))     # 拉伸剪裁区域
                        #imgewrite(roismall)                    # 保存剪裁的区域用于图像训练,预测数据时注释
                        laber = svmdetect(roismall)             # 预测当前区域数字,采集数据时注释
                        labershow(laber)                        # 输出,采集数据时注释
                        #font = cv.FONT_HERSHEY_SIMPLEX         # 文本显示实例化
                        #cv.putText(im, laber, (10, 400), font, 4, (255, 255), 2, cv.LINE_AA)   #将识别到的数字显示到源屏幕上
                        cv.drawContours(im, cnt, -1, (175, 0), 3)           # 绘制轮廓
                        cv.rectangle(im,(x,y),(x+w,y+h),(0,0,255),2)        # 画框
                            #cv.imshow('norm2'+ str(count) , roismall)       # 显示截取出的部分内容
                            #rect = cv.minAreaRect(cnt)       #计算最小外界矩
                            #box = cv.boxPoints(rect)        #计算外界矩坐标
                            #box = np.int0(box)
                            #cv.drawContours(im,[box],0,(0,0,255),2)    #画出矩形
                            #key = cv.waitKey(0)
                            #if key == 27:  # (escape to quit)
                            #    sys.exit()
                            #elif key in keys:
                            #    responses.append(int(chr(key)))
                            #    sample = roismall.reshape((1,900))
                            #    samples = np.append(samples,sample,0)

        #font = cv.FONT_HERSHEY_SIMPLEX      #文本显示实例化
        #cv.putText(img, laber , (10, 400), font, 4, (255, 255, 255), 2, cv.LINE_AA)
        cv.imshow('norm',im)
        if cv.waitKey(1) == ord('q'):
            break
    cap.release()
    cv.destroyAllWindows()

在这里插入图片描述
预测的效果如图所示。
写博不易,有用的话点个赞吧。

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

絮沫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值