OpenCV中的SVM(支持向量机)训练及识别代码

见:
https://dwz.cn/BvkG3CeN 例子(这个是OpenCV2的),结合(这个是OpenCV3):http://t.cn/AiTUFdzf
http://t.cn/AiTPAEOd(讲参数)
下面是所有可能出现的问题所要解决的网址:
opencv3.0 SVM::Params中Params不识别问题:http://t.cn/AiT4LKJR
OpenCV3:https://dwz.cn/yAIavpWi
讲解参数:https://livezingy.com/svm-in-opencv3-1/
大杂烩:https://dwz.cn/sF8E5jsZ

Opencv3.4.1与C++实现对分类问题的训练与预测
https://dwz.cn/rpkTLnuC

另一个参考:
https://dwz.cn/RIU5lRJN

SVM(概念)
SVM是一种训练机器学习的方法,可以用于解决分类和回归问题,同时还使用了一种称之为kernel trick(支持向量机的核函数)的技术进行数据转换,然后再根据这些转换信息,在可能的输出之中找到一个最优的边界(超平面)。简单来说就是做一些非常复杂的数据转换工作,然后根据预定义的标签或者输出进而计算出如何分离用户的数据。
支持向量机方法是建立在统计学理论的VC维论和结构风险最小原理基础上的,根据有限的样本信息在模型的复杂性(即对特定训练样本的学习精度,Accuracy)和学习能力(即无错误地识别任意样本的能力)之间寻求最佳折中,以期获得最好的推广能力(活成泛化能力)。
支持向量机(SVM)中最核心的是——“支持向量”,一旦在两类或多类样本集中定位到某些特定的点作为支持向量,就是可以依据这些支持向量计算出来分类超平面,再依据超平面对类别进行归类划分。支持向量,如下图,需要对红色和蓝色的两类训练样本进行区分,实现绿线是决策面(超平面),最靠近决策面的2个实行红色样本和一个实心蓝色样本分别是两类训练样本中的支持向量,决策面所在的位置是使得两类支持向量与决策面之间的间隔都达到最大决策面所处的位置。

一般情况下,训练样本都会重存在噪声,这就导致其中一类样本的一个或多个样本跑到了决策面的另一边,掺杂到另一类样本中。针对这种情况,SVM加入了松弛变量(惩罚变量)来应对,确保这些噪声样本不会被作为支持向量而不管他们离超平面的距离有多近。
SVM是一中有监督的学习分类方法,所以对于给出的训练样本,要明确每个样本的归类是0还是1,即每个样本都需要标注一个确切的类别标签,提供给SVM训练使用。对于样本特征,以及特征维度,SVM并没有限定,可以使用如Haar、角点、Sift、Surf、直方图等各种特征作为训练样本的表述参与SVM的训练。OpenCV要求训练数据存储在float类型的Mat结构中。
优点
1、 小样本
2、 结构风险最小
3、 非线性

SVM的核函数
简介
很多情况下低维空间向量集是难于划分的,解决办法是将他们映射到高维空间,但这个方法会造成策画错杂度的增长,而核函数正好巧妙地解决了这个问题。只要选用恰当的核函数,就可以获得高维空间的分类函数(超平面)。在SVM理论中,采取不合的核函数将会导致不合的SVM算法。在断定了核函数之后,因为断定核函数的已知数据也存在必然的误差,推敲到推广性题目,是引入了败坏系数以及处罚系数两个参变量来校订。
核函数的本质是:将低维空间的线性不可分问题,借助核函数转化为高维空间的线性可分,进而可以在高维空间找到最优边界(超平面)。

二维线性不可分 三维线性可分

核函数的分类
(1)、线性核函数

(2)、多项式核函数

(3)、径向基(RBF)核函数(高斯核函数)

(4)、Sigmoid核函数(二层神经收集核函数)

OpenCV中的核函数定义
CvSVM::LINEAR:
线性内核,没有任何向映射至高维空间,线性区分(或回归)在原始特点空间中被完成,这是最快的选择。

CvSVM::POLY:
多项式内核

CvSVM::RBF:
基于径向的函数,对于大多半景象都是一个较好的选择

CvSVM::SIGMOID:
Sigmoid函数内核

OpenCV中SVM参数设置
OpenCV中SVM参数使用CvSVMParams方法定义如下:
CvSVMParams::CvSVMParams(int svm_type,
int kernel_type,
double degree,
double gamma,
double coef0,
double Cvalue,
double nu,
double p,
CvMat* class_weights,
CvTermCriteria term_crit
)

CvSVMParams方法如果不传自定义参数会按如下代码进行默认初始化:
CvSVMParams::CvSVMParams() : svm_type(CvSVM::C_SVC), kernel_type(CvSVM::RBF), degree(0),
gamma(1), coef0(0), C(1), nu(0), p(0), class_weights(0)
{
term_crit = cvTermCriteria( CV_TERMCRIT_ITER+CV_TERMCRIT_EPS, 1000, FLT_EPSILON );
}

kernel_type:SVM的内核类型(4种)
见核函数的分类

svm_type:指定SVM的类型(5种)
1、CvSVM::C_SVC : C类支撑向量分类机。 n类分组 (n≥2),容许用异常值处罚因子C进行不完全分类。
2、CvSVM::NU_SVC : 类支撑向量分类机。n类似然不完全分类的分类器。参数为庖代C(其值在区间[0,1]中,nu越大,决定计划鸿沟越腻滑)。
3、CvSVM::ONE_CLASS : 单分类器,所有的练习数据提取自同一个类里,然后SVM建树了一个分界线以分别该类在特点空间中所占区域和其它类在特点空间中所占区域。
4、CvSVM::EPS_SVR : 类支撑向量回归机。练习集中的特点向量和拟合出来的超平面的间隔须要小于p。异常值处罚因子C被采取。
5、CvSVM::NU_SVR : 类支撑向量回归机。 庖代了 p。

其他
degree:内核函数(POLY)的参数degree。
gamma:内核函数(POLY/RBF/SIGMOID)的参数γ。
coef0:内核参数(POLY/SIGMOID)的参数coef0。
Cvalue:SVM类型(C_SVC/EPS_SVR/NU_SVR)的参数C。
nu:SVM类型(NU_SVC/ ONE_CLASS/ NU_SVR)的参数 。
p:SVM类型(EPS_SVR)的参数 。
class_weights:C_SVC中的可选权重,赋给指定的类,乘以C后变成 。所以这些权重影响不合类此外错误分类处罚项。权重越大,某一类此外误分类数据处罚项就越大。
term_crit:SVM迭代联系过程中的中断前提,解决项目组受束缚二次最优题目。可以指定公差和/或最大迭代次数。

OpenCV中SVM训练函数
CvSVM::train(const CvMat* trainData,
const CvMat* responses,
const CvMat* varIdx=0,
const CvMat* sampleIdx=0,
CvSVMParams params=CvSVMParams()

trainData:练习数据,必须是CV_32FC1(32位浮点类型,单通道)。数据必须是CV_ROW_SAMPLE的,即特点向量以行来存储。
responses:响应数据,凡是1D向量存储在CV_32SC1(仅仅用在分类题目上)或者CV_32FC1格局。
varldx:指定感受爱好的特点。可索引整数(32sC1)向量,例如以0为开端的索引,或者8位(8uC1)的应用的特点或者样本的掩码。用户也可以传入NULL指针,用来默示练习中应用所有变量/样本。
sampleldx:指定感爱好的样本。描述同上。
params:SVM参数。

OpenCV中的SVM识别(预言)函数
OpenCV的预言函数所有重载如下:
float CvSVM::predict(const Mat& sample, bool returnDFVal=false ) const
float CvSVM::predict(const CvMat* sample, bool returnDFVal=false ) const
float CvSVM::predict(const CvMat* samples, CvMat* results) const

1、 sample:需要猜测的输入样本。
2、 samples:需要猜测的输入样本们。
3、 returnDFVal:指定返回值类型。若值是true,则是一个2类分类题目,该办法返回的决定计划函数值是边沿的符号间隔。
4、 results:响应的样本输出猜测的响应。

这个函数用来猜测一个新样本的响应数据(response)。在分类题目中,这个函数返回类别编号;在回归题目中,返回函数值。输入的样本必须与传给trainData的练习样本同样大小。若是练习中应用了varIdx参数,必然记住在predict函数中应用跟练习特点一致的特点。

OpenCV中SVM分类问题代码流程
(1) 获得练习样本及制作其类别标签(trainingDataMat,labelsMat)
(2) 设置练习参数(CvSVMParams)
(3) 对SVM进行训练(CvSVM::train)
(4) 对新的输入样本进行猜测(CvSVM::predict),并输出结果类型(对应标签)
(5) 获得支持向量(CvSVM::get_support_vector_count,CvSVM::get_support_vector)

SVM多分类问题的几种方法
构造SVM多分类器的主要方法有两类:一类是直接法,直接在目标函数上进行修改,将多分类的参数求解合并到一个最优化问题中,通过求解该最优化问题“一次性”实现多分类。这种方法计算复杂度比较高,实现起来比较困难,只适用于小型问题中;另一类是间接法,主要是通过组合多个二分类器来实现多分类器的构造,常见的方法有one-against-one和one-again-all两种。

一对多法(one-versus-rest,简称OVRSVMs)
训练时依次把某个类别的样本归为一类,其他剩余的样本归为一类,这样k个类别的样本就构造出了k个SVM。分类时将未知样本分类为具有最大分类函数值的那类。
假如有四类要划分(即有4个Label),他们是A,B,V,D。在抽取训练集的时候,分别抽取A所对应的向量作为正集,B、C、D所对应的向量作为负集;B所对应的向量作为正集,A,C,D所对应的向量作为负集;C所对应的向量作为正集,A,B,D所对应的向量作为负集;D所对应的向量作为正集,A,B,C所对应的向量作为负集,这四个训练集分别进行训练,然后的得到四个训练结果文件,在测试的时候,把对应的测试向量分别利用这四个训练结果文件进行测试,最后每个测试都有一个结果f1(x),f2(x),f3(x),f4(x).于是最终的结果便是这四个值中最大的一个。
这种方法有种缺陷,因为训练集是1:M,这种情况下存在偏差.因而不是很实用。

一对一法(one-versus-one,简称OVOSVMs或者pairwise)
在任意两类样本之间设计出一个SVM,因此k个类别的样本就需要设计k(k-1)/2个SVM。当对一个未知样本进行分类时,最后得票最多的类别即为该未知样本类别。Libsvm中多类分类就是根据这个方法实现的。
假设有A、B、C、D四类。在训练时选择A、B;A、C;A、D;B、C;B、D;C、D;所对应的向量作为训练集,然后得到六个训练结果,在测试时,把对应的向量分别对六个人结果进行测试,然后采取投片形式,最后得到一组结果。

层次支持向量机(H-SVMs)
层次分类法首先将所有类别分成两个子类,再进一步划分成两个次级子类,如此循环,直到得到一个单独的类别为止。

DAG-SVMS
DAG-SVMS是由Platt提出的决策导向的循环图DDAG导出的,是针对“一对一”SVMS存在误分、拒分现象提出的。

尝试
关于Mat类:http://t.cn/EUcL8IP
训练代码:

//SVM识别数字尝试,参考http://t.cn/AiTPAEOd
//2019年8月7日
/*****************train***************
****************训练函数**************/
#include <opencv.hpp>
#include <stdio.h>
#include <string.h>
#include <cstdio>


using namespace cv;
using namespace std;
using namespace ml;

void get_1(Mat& trainingimages, vector<int>& trainingLabels);
void get_0(Mat& trainingimages, vector<int>& trainingLabels);

int main()
{
/**************************获取训练*********************************/
	Mat classes;
	Mat trainingData;
	Mat trainingImages;
	vector<int> trainingLabels;
	get_0(trainingImages, trainingLabels);
	get_1(trainingImages, trainingLabels);
	Mat(trainingImages).copyTo(trainingData);//这个是?
	trainingData.convertTo(trainingData, CV_32FC1);
	Mat(trainingLabels).copyTo(classes);//获取训练数据

/***************************配置SVM训练器参数*******************************/
	Ptr<SVM> params = SVM::create();
	params->setType(SVM::C_SVC);
	params->setKernel(SVM::LINEAR); //RBF效果不好
	params->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 10000, 1e-6));

/********************************训练**************************************/
	cout << "正在训练,请稍等...." << endl;
	Ptr<TrainData> tData = TrainData::create(trainingData, ROW_SAMPLE, classes);
	cout << "问题1" << endl;
	params->train(tData);

/*******************************保存模型***************************************/
	cout << "准备保存模型" << endl;
	params->save("D:/Desktop/SVM/numble.xml");
	cout << "训练完毕" << endl;

/************************************尝试识别*******************************/

	return 0;

}

void get_0(Mat& trainingImages, vector<int>& trainingLabels)
{
	int k = 0;
	for (int i = 0; i < 2396; i++)
	{
		Mat SrcImage = imread("D:/Desktop/SVM/装甲板数字1-8/train_date(1-5)/00/" + to_string(i) + ".png",0);
		if (SrcImage.size <= 0) continue;
		resize(SrcImage, SrcImage, Size(28, 28), 0, 0, INTER_NEAREST);
		SrcImage = SrcImage.reshape(0, 1);
		trainingImages.push_back(SrcImage);
		trainingLabels.push_back(0);
		k++;
		cout << k << endl;
	}
	cout << "0:" << k << endl;
}
void get_1(Mat& trainingImages, vector<int>& trainingLabels)
{
	int k = 0;
	for (int i = 0; i < 4271; i++)
	{
		Mat SrcImage = imread("D:/Desktop/SVM/装甲板数字1-8/train_date(1-5)/11/" + to_string(i) + ".png",0);
		if (SrcImage.size <= 0) continue;
		resize(SrcImage, SrcImage, Size(28, 28), 0, 0, INTER_NEAREST);
		SrcImage = SrcImage.reshape(0, 1);
		trainingImages.push_back(SrcImage);
		trainingLabels.push_back(1);
		k++;
		cout << k << endl;
	}
	cout << "1:" << k << endl;
}

识别:

/*******************尝试识别********************/
#include <opencv.hpp>
#include <fstream>

using namespace cv;
using namespace ml;
using namespace std;

int  predict_0 = 0, predict_1 = 0;
#define YUZI 140

void main()
{
	Ptr<SVM> svm = Algorithm::load<SVM>("D:/Desktop/SVM/numble.xml");
	VideoCapture c(1);
	Mat p,pic,imgRead;
	while (1)
	{
		c >> pic;
		pic.copyTo(p);
		cvtColor(p, p, COLOR_BGR2GRAY);
		p.copyTo(imgRead);
		threshold(p, p, YUZI, 255, 0);
		imshow("原图", pic);
		imshow("图片", p);

		resize(imgRead, imgRead, Size(28, 28), 0, 0, INTER_NEAREST);
		imgRead.convertTo(imgRead, CV_32FC1);
		Mat  predict_mat = imgRead.reshape(0, 1);
		float response = svm->predict(predict_mat);
		cout << response << endl;

		waitKey(10);
	}
	}

识别2:

/*******************尝试识别********************/
#include <opencv.hpp>
#include <fstream>

using namespace cv;
using namespace ml;
using namespace std;

int  predict_0 = 0, predict_1 = 0;
#define YUZI 100

void main()
{
	Ptr<SVM> svm = Algorithm::load<SVM>("D:/Desktop/SVM/numble.xml");
	VideoCapture c(1);
	Mat p, pic, imgRead;
	//pic = imread("D:/Desktop/m.png");
	while (1)
	{
		c >> pic;
		pic.copyTo(p);
		cvtColor(p, p, COLOR_BGR2GRAY);
		p.copyTo(imgRead);
		threshold(p, p, YUZI, 255, 0);
		//imshow("原图", pic);
		imshow("图片", p);

		vector<vector<Point>> contours;
		findContours(p, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
		vector<Rect> box(contours.size());
		
		for (int i = 0; i < contours.size(); i++)
		{
			box[i]= boundingRect(contours[i]);
			//Mat imageROI;
			imgRead = p(Rect(box[i].tl().x, box[i].tl().y, box[i].width,box[i].height));

			resize(imgRead, imgRead, Size(28, 28), 0, 0, INTER_NEAREST);
			//imshow("图",imgRead);
			imgRead.convertTo(imgRead, CV_32FC1);
			Mat  predict_mat = imgRead.reshape(0, 1);
			float response = svm->predict(predict_mat);
			if (response == 1) {
				putText(pic, "3", box[i].tl(), 1, 5, (0, 0, 255), 8);
				cout << "有数字" << endl;
				imshow("图", imgRead);
				//imshow("原图", pic);
			}
			else {
				cout << "无数字" << endl;
			}
		}
		imshow("原图", pic);
		
		//cout << response << endl;

		waitKey(10);
	}
}

总结
学习不够深入,没了解相关一些参数的定义只是单纯的复制运行了一下学长的代码。有待提高,准备后面吧SVM的一些参数设置再学习一遍,然后自己独立写一下代码。验证程序的准确性时方法没有掌握好,再改进。计划是在8月10号进行完善。8月10号上午学习SVM参数设置,下午和晚上跑程序并验证其准确性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值