本介绍中你将了解到:
使用OpenCV函数CvSVMware::train创建一个基于SVM的分类器,并且通过CvSVM::predict测试其性能。
支持向量机是一个由分隔超平面来正式定义有识别能力的分类器。换句话说,根据已标注训练数据(有监督学习),该算法(SVM)输出一个最优的超平面,用来分类新的样本。
什么情况下能够得到最优超平面?我们来思考一下下面的简单问题:
对于一组线性可分割的二维点,这些点属于两类之一,找出一条分割直线。
注意:在这个例子中我们都是在笛卡尔平面处理点和线,而不是超平面或者高维空间的向量。这是该问题的简化。这样处理是因为我们对容易想象的例子能更好的建立直觉,这点很重要。但是,同样的概念可应用于多维空间的分类问题。
在上图中我们能看到存在多条直线能够解决该分类问题。其中是否有一些直线比其他的更好呢?我们可以直观的定义一种标准来评估这些直线的价值:
如果一条直线距离某些点太近,我们就说这条线不好,因为它会影响灵敏度,较难正确的形成。因此我们的目标应该是找到一条直线,尽可能的离所有点都最远。
那么,SVM算法的操作是基于寻找那个离训练样本最短距离最大的超平面。而且,这个距离出现一个SVM理论中的重要名称“边缘”(margin)。因此,最优分割超平面最大化训练数据的边缘。
让我们介绍用来正式定义一个超平面的公式:
其中,β是权向量,β0是偏差。
最优超平面可以根据β和β0的不同,被用有限种方式描述。作为约定,在所有可能的超平面描述中,我们选择下面这个:
这里,x代表离超平面最近的训练样本。一般情况下,离超平面最近的训练样本被称为“支持向量”。这一表示被称为“规范超平面”。(译者注:这里可能会有读者疑惑方程怎么来的,是什么意思?首先这里是一个约定,就将满足这样一个约束条件的超平面称为一个“规范超平面”;然后,其实一个方程直接加上一个常数,就是将这个方程在笛卡尔坐标系中所代表的超平面作上下平移,或者就说二维空间中的平面上下平移,这里将原方程加上绝对值符号后再等于1,实际上是在超平面方程上加上或者减去一个常数1,绝对值符号去掉就是等于正负1嘛。因此,规范超平面实际上是我们假象的超平面向上向下各平移1个单位所得到的两个新的超平面,即规范超平面)
现在,我们通过几何学结论,点x到超平面(β, β0)的距离:
特殊的,对于规范超平面,分子等于1,所以规范超平面到支持向量的距离为:
回顾前面介绍的边缘的概念,表示为M,其距离是超平面到最近样本距离的2倍:
最后,最大化M的问题等同于服从相同约束的方程L(β)的最小化问题。约束条件模拟超平面正确分类所有训练样本xi的必要条件。正式的,
其中yi代表训练数据符号。
这是拉格朗日算子优化问题,可以通过拉格朗日乘数获得最优超平面的权向量β和偏差β0解决该问题。
#include<opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> #include<opencv2/ml/ml.hpp> usingnamespace cv; intmain() { // Data for visual representation int width = 512, height = 512; Mat image = Mat::zeros(height, width,CV_8UC3); // Set up training data float labels[4] = {1.0, -1.0, -1.0, -1.0}; Mat labelsMat(4, 1, CV_32FC1, labels);//标记 float trainingData[4][2] = { {501, 10},{255, 10}, {501, 255}, {10, 501} }; Mat trainingDataMat(4, 2, CV_32FC1,trainingData);//数据 // Set up SVM's parameters CvSVMParams params; params.svm_type = CvSVM::C_SVC; params.kernel_type = CvSVM::LINEAR; params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100,1e-6); // Train the SVM CvSVM SVM; SVM.train(trainingDataMat, labelsMat,Mat(), Mat(), params); Vec3b green(0,255,0), blue (255,0,0); //Show the decision regions given by the SVM for (int i = 0; i < image.rows; ++i) for (int j = 0; j < image.cols; ++j) { Mat sampleMat =(Mat_<float>(1,2) << j,i); float response =SVM.predict(sampleMat); if (response == 1) image.at<Vec3b>(i,j) = green; else if (response == -1) image.at<Vec3b>(i,j) =blue; } // Show the training data int thickness = -1; int lineType = 8; circle( image, Point(501, 10), 5, Scalar( 0, 0, 0), thickness, lineType); circle( image, Point(255, 10), 5, Scalar(255, 255, 255), thickness,lineType); circle( image, Point(501, 255), 5,Scalar(255, 255, 255), thickness, lineType); circle( image, Point( 10, 501), 5,Scalar(255, 255, 255), thickness, lineType); // Show support vectors thickness = 2; lineType = 8; int c = SVM.get_support_vector_count(); for (int i = 0; i < c; ++i) { const float* v =SVM.get_support_vector(i); circle( image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness, lineType); } imwrite("result.png",image); // save the image imshow("SVM Simple Example",image); // show it to the user waitKey(0); }
(1)创建训练数据
此练习中训练数据由一组已标记的二维点组成,他们分别属于两个不同的类;其中一类有一个点组成,另一类由三个点组成。
floatlabels[4] = {1.0, -1.0, -1.0, -1.0};//对应数据点标记 floattrainingData[4][2] = {{501, 10}, {255, 10}, {501, 255}, {10, 501}};//数据
MattrainingDataMat(3, 2, CV_32FC1, trainingData); MatlabelsMat (3, 1, CV_32FC1, labels);
本篇指导中,我们用最简单的例子介绍了SVM理论,其中训练样本可以被线性地区分为两类。但是,SVM能被用于各种问题(例如,数据的非线性分类问题,SVM使用一个核函数来提升样本维度等等)。基于这一结论,我们在训练SVM之前必须定义一些参数。这些参数被保存在CvSVMParams类的对象中。
CvSVMParamsparams; params.svm_type = CvSVM::C_SVC; params.kernel_type= CvSVM::LINEAR; params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100,1e-6);
注意:SVM的类型的重要特性CvSVM::C_SVC用来处理类的不完全分离(例如,当训练数据是非线性分离的)。这个特性在这里并不重要,因为这里的数据是线性分割的,并且我们选择这个SVM类型只因为他最常用。
l SVM核函数的类型:我们上面没有介绍核函数,是因为他们对我们处理的训练数据不感兴趣(译者注:此处是二维训练数据,核函数用来处理多维的情况)。然而,还是让我们来简单解释一下核函数背后的主要思想。它是对训练数据做映射,以提高数据与线性分类数据的相似性。映射包括提高数据维度,是通过核函数有效执行的。这里我们选择CvSVM::LINEAR,是指不做映射。这个参数定义在属性CvSVMParams.kernel_type。
l 算法的终止条件:实施SVM训练步骤,是通过迭代的方式来解决有约束的二次优化问题。这里我们指定一个迭代的最大数值,和一个容忍的误差值,因此我们让这个算法以最少计算步数终止,即便最优超平面还没有计算完成。这个参数定义在属性cvTermCriteria。
(3)训练SVM
我们称这个方法为CvSVM::train来创建SVM模型。
CvSVMSVM; SVM.train(trainingDataMat,labelsMat, Mat(), Mat(), params);
CvSVM::predict是通过以训练的SVM模型来分类输入样本的方法。本例中为了着色由SVM预测的空间我们就使用这个方法。也就是说,我们遍历一副图片,把它的像素当成笛卡尔平面的点。每一个点都根据SVM的分类预测染上了颜色;如果是标记为1的类别就涂成绿色的,如果是标记为-1的类,就涂成蓝色的。
Vec3bgreen(0,255,0), blue (255,0,0); for(int i = 0; i < image.rows; ++i) for (int j = 0; j < image.cols; ++j) { Mat sampleMat = (Mat_<float>(1,2)<< i,j); float response = SVM.predict(sampleMat); if (response == 1) image.at<Vec3b>(j, i) = green; else if (response == -1) image.at<Vec3b>(j, i) = blue; }
这里我们使用很多方法来获得支持向量的信息。方法CvSVM::get_support_vector_count输出该问题中支持向量的总数,CvSVM::get_support_vector可通过索引获取每一个支持向量。这里,我们使用这种方法找到是支持向量的训练样本并把他们标记出来。
intc = SVM.get_support_vector_count(); for(int i = 0; i < c; ++i) { constfloat* v = SVM.get_support_vector(i); // get and then highlight with grayscale circle( image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128),thickness, lineType); }
l 此代码打开了一副图片,显示了两类中的训练样本。其中一类中的所有点表示成白色,另一类是黑色。
l 训练支持向量机,并用来分类图片中的所有像素。结果图片被分成蓝色区域和绿色区域。两块区域的边界位置极为最优分割超平面。
l 最后,训练样本中带有灰色环的就是支持向量。
原文:http://docs.opencv.org/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html#source-code