美文网首页
使用opencv实现支持向量机(SVM)

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

作者: 碧影江白 | 来源:发表于2017-01-20 15:18 被阅读1928次

    支持向量机的百度百科: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);
    

    }

    
    
    
    
    

    相关文章

      网友评论

          本文标题:使用opencv实现支持向量机(SVM)

          本文链接:https://www.haomeiwen.com/subject/ttpwbttx.html