opencv_SVM学习(一)

1、SVM支持向量机的概念

SVM是一个分类器Support Vector Machines,定义为一个能够将不同类样本在样本空间分隔的超平面,即给定一些标记好的训练样本,SVM算法输出一个最优化的分隔超平面。

2、初步学习

2.1理解SVM
参考自 http://blog.csdn.net/c406495762/article/details/78072313
http://blog.csdn.net/chunxiao2008/article/details/50266025
这2个讲的很好
这里写出我看这个时觉得讲的比较好的一些重点。
1)给一个要求:你需要在下面的特征区别较明显的两个样本之间插入一根棍子,这个棍子可以把两个样本明显地分开。
这里写图片描述
2)给一个更高的要求:在样本的数量变多以后,你这根棍子依然能够比较好的将两类样本分开,使分类变得可靠。
这里写图片描述
于是提出了SVM,这个SVM能使棍子离两个样本的距离达到最大,以保证在样本越来越多时分类也能比较可靠。
3)再给一个更高的要求,这些样本没有明显的规律可以区分了,你找不到一条棍子将这两类样本分开。那怎么办?“拍一掌(核函数),让样本飞起来,拿出一张纸(超平面)”,这样就能达到下面的效果。
这里写图片描述

2.2线性可分和线性不可分的概念
当一个分类问题,数据是线性可分的,也就是用一根棍就可以将两种小球分开的时候,我们只要将棍的位置放在让小球距离棍的距离最大化的位置即可,寻找这个最大间隔的过程,就叫做最优化。但是,现实往往是很残酷的,一般的数据是线性不可分的,也就是找不到一个棍将两种小球很好的分类。这个时候,我们就需要像大侠一样,将小球拍起,用一张纸代替小棍将小球进行分类。想要让数据飞起,我们需要的东西就是核函数(kernel),用于切分小球的纸,就是超平面。

2.3线性SVM
作者从线性可分的二分类问题讲解了这个概念。
这里写图片描述 这里写图片描述
如图两个类别是线性可分的,而且可以将两个类别分开的直线不止一条。这里的直线叫做决策面,每个决策面对应一个线性分类器。虽然从分类结果上看,两个分类器的效果是相同的。但是他们的性能是有差距的。在它们的决策面不变的情况下,添加了一个橙色的样本。可以观察到,(1)分类器依然能实现正确分类,而(2)分类器则出现了分类错误,所以得到这么一个结论,说分类器(1)的性能优于分类器(2)。这就是SVM的思想。它的依据就是分类器B的分类间隔比分类器C的分类间隔大。这里涉及到第一个SVM独有的概念”分类间隔”。在保证决策面方向不变且不会出现错分样本的情况下移动决策面,会在原来的决策面两侧找到两个极限位置(越过该位置就会产生错分现象),如虚线所示。虚线的位置由决策面的方向和距离原决策面最近的几个样本的位置决定。而这两条平行虚线正中间的分界线就是在保持当前决策面方向不变的前提下的最优决策面。两条虚线之间的垂直距离就是这个最优决策面对应的分类间隔。显然每一个可能把数据集正确分开的方向都有一个最优决策面(有些方向无论如何移动决策面的位置也不可能将两类样本完全分开),而不同方向的最优决策面的分类间隔通常是不同的,那个具有“最大间隔”的决策面就是SVM要寻找的最优解。而这个真正的最优解对应的两侧虚线所穿过的样本点,就是SVM中的支持样本点,称为”支持向量”。

2.4线性可分的情况
分类函数为
这里写图片描述
这里的f(x)表达的就是超平面,f(x)大于0的点对应y=1的数据,f(x)小于0的点对应y=-1的数据。数据点离超平面越远,就越能够确信这个数据是属于哪一类的.
有两个支撑着中间的分界超平面的超平面,称为gap。它们到分界超平面的距离相等。这两个gap上必定会有一些数据点。如果没有,我们就可以进一步扩大margin了。这些经过gap的数据点,就是支持向量(Support Vector)(它们支持了中间的超平面)。很显然,只有支持向量才决定超平面,其他的数据点不影响超平面的确定。
这是一个十分优良的特性。假设有100万个数据点,支持向量100个,我们实际上只需要用这100个支持向量进行计算!!!这将大大提高存储和计算的性能。

3、代码学习

3.1代码1学习
这里给出两个样本[1,1,1]和[2,4,3],做二分类,标签为0和1
最后给出一个测试样本,判断它是属于哪一类。

#include <opencv2/opencv.hpp>
#include <iostream>
#include "math.h"

using namespace cv;
using namespace std;

int main(int argc, char** argv[])
{
    //训练需要用到的数据
    int labels[2] = { 0, 1 };
    float trainingData[2][3] = { { 1, 1, 1 }, { 2, 4, 3 } };
    //转为Mat
    Mat trainingDataMat(2, 3, CV_32FC1, trainingData);
    Mat labelsMat(2, 1, CV_32SC1, labels);
    //训练的初始化
    Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
    svm->setType(cv::ml::SVM::C_SVC);
    svm->setKernel(cv::ml::SVM::LINEAR);
    svm->setC(2.67);
    svm->setGamma(5.383);
    svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
    //开始训练
    svm->train(trainingDataMat, cv::ml::SampleTypes::ROW_SAMPLE, labelsMat);
    //支持向量
    Mat sv = svm->getSupportVectors();
    cout << "支持向量为:" << endl;
    for (int i = 0; i < sv.rows; ++i)
    {
        const float* v = sv.ptr<float>(i);
        cout << v[0] << " " << v[1] << endl;
    }
    //预测
    Mat result;
    float testData[1][3] = { { 3, 4, 3 } };
    Mat query(1, 3, CV_32FC1, testData);
    svm->predict(query, result);
    cout << "测试数据分类结果为:" << endl;
    cout << result;

    waitKey(-1);
    while (1);
    return 0;
}

3.2代码1解读
1)给出训练所需要的数据

    //训练需要用到的数据
    int labels[2] = { 0, 1 };
    float trainingData[2][3] = { { 1, 1, 1 }, { 2, 4, 3 } };
    //转为Mat
    Mat trainingDataMat(2, 3, CV_32FC1, trainingData);
    Mat labelsMat(2, 1, CV_32SC1, labels);

2)训练初始化

    Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
    svm->setType(cv::ml::SVM::C_SVC);
    svm->setKernel(cv::ml::SVM::LINEAR);
    svm->setC(2.67);
    svm->setGamma(5.383);
    svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));

3)开始训练

svm->train(trainingDataMat, cv::ml::SampleTypes::ROW_SAMPLE, labelsMat);

4、输出支持向量

    Mat sv = svm->getSupportVectors();
    cout << "支持向量为:" << endl;
    for (int i = 0; i < sv.rows; ++i)
    {
        const float* v = sv.ptr<float>(i);
        cout << v[0] << " " << v[1] << endl;
    }

5、对测试数据进行分类

    Mat result;
    float testData[1][3] = { { 3, 4, 3 } };
    Mat query(1, 3, CV_32FC1, testData);
    svm->predict(query, result);
    cout << "测试数据分类结果为:" << endl;
    cout << result;

6、结果
根据
这里写图片描述
这里我还不是特别理解这个支持向量sv输出的V[0]和V[1]具体代表什么意思。。
希望有大神可以告诉我一下。。

3.3代码2学习
这里的代码是从http://m.blog.csdn.net/zmdsjtu/article/details/53610244此处学习的。

#include<opencv2\opencv.hpp> 

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

int main()
{
    //训练需要用到的数据
    int labels[4] = { 1, 2, 3, 4 };
    float trainingData[4][2] = { { 31, 12 }, { 65, 220 }, { 440, 350 }, { 400, 400 } };
    //转为Mat以调用
    Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
    Mat labelsMat(4, 1, CV_32SC1, labels);
    //训练的初始化
    Ptr<SVM> svm = SVM::create();
    svm->setType(SVM::C_SVC);
    svm->setKernel(SVM::LINEAR);
    svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
    //开始训练
    svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);

    //-----------无关紧要的美工的部分-----------------------  
    //----其实对每个像素点的坐标也进行了分类----------------
    int width = 512, height = 512;
    Mat imageshow = Mat::zeros(height, width, CV_8UC3);
    Vec3b green(0, 255, 0), blue(255, 0, 0), red(0, 0, 255), black(0, 0, 0);
    for (int i = 0; i < imageshow.rows; ++i)
    for (int j = 0; j < imageshow.cols; ++j)
    {
        Mat sampleMat = (Mat_<float>(1, 2) << j, i);//生成测试数据
        float response = svm->predict(sampleMat);

        if (response == 1)
            imageshow.at<Vec3b>(i, j) = green;
        else if (response == 2)
            imageshow.at<Vec3b>(i, j) = blue;
        else if (response == 3)
            imageshow.at<Vec3b>(i, j) = red;
        else if (response == 4)
            imageshow.at<Vec3b>(i, j) = black;
    }
    //--------把初始化训练的点画进图片------------
    int thickness = -1;
    int lineType = 8;
    for (int i = 0; i < sizeof(labels) / sizeof(int); i++) {
        circle(imageshow, Point(trainingData[i][0], trainingData[i][1]), 10, Scalar(255, 255, 255), thickness, -1);
    }
    // 把 support vectors  cout粗来看看……
    Mat sv = svm->getSupportVectors();
    cout << "Support Vectors为:" << endl;
    for (int i = 0; i < sv.rows; ++i)
    {
        const float* v = sv.ptr<float>(i);
        cout << v[0] << " " << v[1] << endl;
        // circle( image,  Point( (int) v[0], (int) v[1]),   6,  Scalar(128, 128, 128), thickness, lineType);//这里可以将支持向量画出来。

    }
    //测试测试
    Mat 结果;
    float teatData[2][2] = { { 20, 11 }, { 310, 411 } };
    Mat query(2, 2, CV_32FC1, teatData);
    svm->predict(query, 结果);
    cout << "分类结果为:" << endl;
    cout << 结果;
    imshow("SVM显示", imageshow);
    waitKey(-1);

}

结果如图所示:
这里写图片描述
这里为什么会有6个支持向量?

问题又来了: circle( image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness, lineType);
这一句代码应该是画出支持向量经过的点,这里的v[0]和v[1]应该是那两点的横纵坐标,为什么在代码1中输出时仅有一个支持向量呢?

猜你喜欢

转载自blog.csdn.net/csdn_dzh/article/details/78984222
今日推荐