使用opencv实现支持向量机(SVM)

支持向量机的百度百科:http://baike.baidu.com/link?url=be_sHxP-L6kpwFWCrFTDgd5KRR3xlye3N-QI_ndE8lyKEoZJGFxuYfrWHviFq8DZBAGhU3Uh39gZlOFEvElrip4hh6qAi_rYFdcPm7TumAAa428tZ1rV8z9eSmOY1tFMYc7BPKEIx1f_W79E4i-R9q
机器学习:
机器学习是研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。它是人工智能的核心,是使计算机具有智能的根本途径,其应用遍及人工智能的各个领域。
机器学习的大致分类:
1)分类(模式识别):要求系统依据已知的分类知识对输入的未知模式(该模式的描述)作分析,以确定输入模式的类属,例如手写识别(识别是不是这个数)。
2)问题求解:要求对于给定的目标状态,寻找一个将当前状态转换为目标状态的动作序列。
SVM一般是用来分类的
一般首先从二维的分类做起,扩展到三维,以及超维度的分类划分,下面是一个二维空间,将空间中的两个点分类:

使用opencv实现支持向量机(SVM)_第1张图片

SVM的学习在于经过训练后可以找到最合适的线或平面将空间进行分割,实现点的分类。
在低维度的空间中难以分割的图像,如:
使用opencv实现支持向量机(SVM)_第2张图片
线性不可分

解决方法可以采用将其扩展到高维度的空间中,实现分割:

三维空间

不考虑具体的函数实现原理,只分析以怎样的心态和角度来应用支持向量机,解决一些现实中需要解决的问题,现在假设已知一定数量的点坐标和其两种不同的分类状况,需要将其在二维空间中进行分割,在图像中绘制分割状况,问题就会变得易懂。下面开始分析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),他接受低维空间的输入值,却能算出高维空间的内积值,可以选择性阅读了解:
(1)线性核函数![](http://upload-images.jianshu.io/upload_images/1825077-9eae42ebaf95b52c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

(2)多项式核函数![](http://upload-images.jianshu.io/upload_images/1825077-7c6b30ee088d7566.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

(3)径向基(RBF)核函数(高斯核函数)![](http://upload-images.jianshu.io/upload_images/1825077-325d0454112cfd06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

(4)Sigmoid核函数(二层神经收集核函数)![](http://upload-images.jianshu.io/upload_images/1825077-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

include

include

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_(1,2) << i,j);

        //当前样本被分到哪一个类,预测函数
        float response = SVM.predict(sampleMat);

        if (response == 1)
            image.at(j, i)  = green;
        else if (response == -1) 
            image.at(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);

}





你可能感兴趣的:(使用opencv实现支持向量机(SVM))