使用opencv实现支持向量机(SVM)
支持向量机的百度百科:http://baike.baidu.com/link?url=be_sHxP-L6kpwFWCrFTDgd5KRR3xlye3N-QI_ndE8lyKEoZJGFxuYfrWHviFq8DZBAGhU3Uh39gZlOFEvElrip4hh6qAi_rYFdcPm7TumAAa428tZ1rV8z9eSmOY1tFMYc7BPKEIx1f_W79E4i-R9q
机器学习:
机器学习是研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。它是人工智能的核心,是使计算机具有智能的根本途径,其应用遍及人工智能的各个领域。
机器学习的大致分类:
1)分类(模式识别):要求系统依据已知的分类知识对输入的未知模式(该模式的描述)作分析,以确定输入模式的类属,例如手写识别(识别是不是这个数)。
2)问题求解:要求对于给定的目标状态,寻找一个将当前状态转换为目标状态的动作序列。
SVM一般是用来分类的
一般首先从二维的分类做起,扩展到三维,以及超维度的分类划分,下面是一个二维空间,将空间中的两个点分类:
SVM的学习在于经过训练后可以找到最合适的线或平面将空间进行分割,实现点的分类。
在低维度的空间中难以分割的图像,如: 线性不可分
解决方法可以采用将其扩展到高维度的空间中,实现分割: 三维空间
不考虑具体的函数实现原理,只分析以怎样的心态和角度来应用支持向量机,解决一些现实中需要解决的问题,现在假设已知一定数量的点坐标和其两种不同的分类状况,需要将其在二维空间中进行分割,在图像中绘制分割状况,问题就会变得易懂。下面开始分析SVM的代码实现过程:
首先,如果我们想要在一个空间里分类两类不同的点,首先需要知道每个点的具体坐标,在接收到的图片信息中,我们需要知道每个点的坐标值,还需要每一个点的分类。
可以即将上述的两类数值要求存入两个数组内,假设共有n个点,那么首先设置Datetrain[n][2],这时可以在下标为0的列中存储每个点的横坐标,在下标为1的列中存储每个点的纵坐标。每个点对应一个分组,可以把每个分组标记为1,-1,每个点的分组信息存储为一个数组中:label[n]。
存储好点的信息,下面就需要进行训练了,训练需要实例化向量机,因此创建CvSVM类型的对象SVM,CVSVM的训练方法为:
CvSVM::train(const CvMat* trainData,
const CvMat* responses,
const CvMat* varIdx=0,
const CvMat* sampleIdx=0,
CvSVMParams params=CvSVMParams()
)
其中,参数信息:
1、trainData: 练习数据,必须是CV_32FC1 (32位浮点类型,单通道)。数据必须是CV_ROW_SAMPLE的,即特点向量以行来存储。
2、responses: 响应数据,凡是是1D向量存储在CV_32SC1 (仅仅用在分类题目上)或者CV_32FC1格局。
3、varIdx: 指定感爱好的特点。可所以整数(32sC1)向量,例如以0为开端的索引,或者8位(8uC1)的应用的特点或者样本的掩码。用户也可以传入NULL指针,用来默示练习中应用所有变量/样本。
4、sampleIdx: 指定感爱好的样本。描述同上。
5、params: SVM参数。
可以看出,练习数据trainData就是已经存储的数组Datetrain,而相应数据responses就相对于数组label,为了达到SVM训练相对应的数据类型,可以进行转化:
Mat trainingDataMat(n, 2, CV_32FC1, Datatrain);
Mat labelsMat(n,1, CV_32FC1, label);
接下来需要重点介绍的是SVM参数params:
在opencv2中,有专门的CvSVMParams方法来对训练支持向量机SVM进行参数的设置,CvSVMParams的构造方法如下,都是对于SVM训练的参数设定:
struct CvSVMParams
{
CvSVMParams();
CvSVMParams( int _svm_type, int _kernel_type,
double _degree, double _gamma,double _coef0,
double _C, double _nu, double_p,
CvMat* _class_weights, CvTermCriteria_term_crit );
int svm_type;
int kernel_type;
double degree; // for poly
double gamma; // for poly/rbf/sigmoid
double coef0; // for poly/sigmoid
double C; // for CV_SVM_C_SVC, CV_SVM_EPS_SVR andCV_SVM_NU_SVR
double nu; // forCV_SVM_NU_SVC, CV_SVM_ONE_CLASS, and CV_SVM_NU_SVR
double p; // forCV_SVM_EPS_SVR
CvMat* class_weights; // forCV_SVM_C_SVC
CvTermCriteria term_crit; // termination criteria
};```
第一个参数:svm_type,SVM的类型
作用主要在于使用SVM处理每一个数据点时的分类问题,包括处理意外的点,如果需要拟合高维度的图像时的拟合距离等。
CvSVM::C_SVC:表示在不能精确地使用线性曲线来分割时,可以忽略某些点,但是需要保持尽量最好地线性分类
CvSVM::NU_SVC:n类似然不完全分类的分类器。参数nu取代了c,其值在区间【0,1】中,nu越大,决策边界越平滑。
CvSVM::ONE_CLASS : 单分类器,把当前需要分类的所有数据归为一个类,线性分界线用于区分当前类和另外一个类
CvSVM::EPS_SVR :回归。 训练集中的特征向量和拟合出来的超平面的距离需要小于p。异常值惩罚因子C被采用。
CvSVM::NU_SVR :回归;nu 代替了p
第二个参数:kernel_type,核类型:
核函数的基本作用就是接受两个低维空间里的向量,能够计算出经过某个变换后在高维空间里的向量内积值。因此,核类型的作用在于选择哪种方法进行多维度的参数拟合。
CvSVM::LINEAR :线性核函数,当前维度内进行分界,最快的选择
CvSVM::POLY :多项式核: d(x,y)= (gamma•(x•y)+coef0)degree
CvSVM::RBF :径向基,对于大多数情况都是一个较好的选择:d(x,y)= exp(-gamma•|x-y|2)
CvSVM::SIGMOID: sigmoid函数被用作核函数:d(x,y) = tanh(gamma·(x•y)+coef0)
下面是不同的核函数,其中K(w,x),他接受低维空间的输入值,却能算出高维空间的内积值<w’,x’>,可以选择性阅读了解:
(1)线性核函数![](https://img.haomeiwen.com/i1825077/9eae42ebaf95b52c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
(2)多项式核函数![](https://img.haomeiwen.com/i1825077/7c6b30ee088d7566.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
(3)径向基(RBF)核函数(高斯核函数)![](https://img.haomeiwen.com/i1825077/325d0454112cfd06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
(4)Sigmoid核函数(二层神经收集核函数)![](https://img.haomeiwen.com/i1825077/1a1a897ba3a941c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
C, nu, p:在一般的SVM优化求解时的参数。
class_weights:可选权重,赋给指定的类别。一般乘以C以后去影响不同类别的错误分类惩罚项。权重越大,某一类别的误分类数据的惩罚项就越大。
最后一个:term_crit:SVM的迭代训练过程的中止。(解决了部分受约束二次最优问题),迭代中止的方法有两种:
1、迭代到了一定的次数而中止
2、迭代到了指定的阙值后中止
这部分在学习特征检测时就有涉及,今天再次回顾:
TermCriteria(
int type,
int maxcount,
double epsilon
)
其中type取TermCriteria::MAX_ITER/TerCriteria::COUNT时表示迭代到最大次数中止,这时参数maxcount起作用;
type取TermCriteria::EPS时表示迭代到制定阙值后中止,这时参数epsilon起作用;
type取TermCriteria::EPS+TermCriteria::MAX_ITER时表示出现以上任意一种情况后都中止,这时两个参数都起作用。
到这里,已经可以对参数进行训练了,训练后得到的分界线可以由SVM.predict(Mat sampleMat)预测函数来判断,判断的结果则为label中的设定1,-1来进行标记,依次对每一个坐标进行预测都能得到该坐标的分类状况,坐标应化为Mat型的SampleMat来进行预判。
下面上代码:
include <opencv2/core/core.hpp>
include <opencv2/highgui/highgui.hpp>
include <opencv2/ml/ml.hpp>
using namespace cv;
int main()
{
// 视觉表达数据的设置(Data for visual representation)
int width = 512, height = 512;
//图片的宽,高,以及通道数(第三通道),初始化零矩阵,表示矩阵是0,黑色底面
Mat image = Mat::zeros(height, width, CV_8UC3);
//建立训练数据( Set up training data)
/*训练的是四个点,点的值分别为+1与-1,用以区分两种不同的点*/
float labels[4] = {1.0, -1.0, -1.0, -1.0};
//使用已有的初始化矩阵来初始化图像
Mat labelsMat(3,1, CV_32FC1, labels);
//已知的点的矩阵
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
//使用已有的初始化矩阵来初始化图像
Mat trainingDataMat(3, 2, CV_32FC1, trainingData);
//imshow("【train】",trainingDataMat);
//设置支持向量机的参数(Set up SVM's parameters)
CvSVMParams params;
/*SVM的类型:C_SVC:允许用异常惩罚因子C进行不完全分类 */
params.svm_type = CvSVM::C_SVC;
/*核类型 LINEAR:没有任何向映像至高维空间,线性区分在原始特征空间中被完成 */
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);
//显示由SVM给出的决定区域 (Show the decision regions given by the SVM)
for (int i = 0; i < image.rows; ++i)
for (int j = 0; j < image.cols; ++j)
{
//创建了一个一行两列的矩阵图像,并且将这两个图像的像素分别设置为当前像素点的行数和列数
//或者使用cvmSet(CvMat* mat, int row, int col, double value
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;
}
//显示训练数据 (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();
std::cout << c << std::endl;
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); // 保存图像
imshow("SVM Simple Example", image); // 显示图像
waitKey(0);
}