OpenCV
开源计算机视觉库
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
支持向量机入门

上一篇教程: 条码识别
下一篇教程: 用于非线性可分数据的支持向量机

原作者Fernando Iglesias García
兼容性OpenCV >= 3.0

目标

在本教程中,您将学习如何

什么是SVM?

支持向量机(SVM)是一种判别分类器,由分离超平面正式定义。换句话说,给定标记的训练数据(监督学习),该算法输出一个最优超平面,该超平面对新的示例进行分类。

获得的超平面在什么意义上是最优的?让我们考虑以下简单问题

对于属于两个类别之一的线性可分2D点集,找到一条分离直线。

注意
在这个例子中,我们处理的是笛卡尔平面上的直线和点,而不是高维空间中的超平面和向量。这是对问题的简化。重要的是要理解,这样做仅仅是因为我们的直觉更容易从易于想象的例子中构建。然而,相同的概念也适用于分类示例位于维度高于二的空間的任务。

在上图中,您可以看到存在多条直线可以解决这个问题。其中任何一条都比其他更好吗?我们可以直观地定义一个标准来估计这些直线的价值:如果一条直线经过过于靠近点的位置,那么它将对噪声敏感,并且不会正确泛化。 因此,我们的目标应该是找到一条尽可能远离所有点的直线。

然后,SVM算法的操作基于寻找能够给出与训练示例最大最小距离的超平面。这个距离的两倍在SVM理论中被称为裕度。因此,最优分离超平面最大化训练数据的裕度。

如何计算最优超平面?

让我们介绍用于正式定义超平面的符号

f(x)=β0+βTx,

其中β被称为权重向量β0被称为偏差

注意
您可以从书中第4.5节(分离超平面)中找到关于此和超平面的更深入描述:T. Hastie、R. Tibshirani和J. H. Friedman的统计学习要素 ([273])。

通过对ββ0进行缩放,可以以无限多种不同的方式表示最优超平面。按照约定,在超平面所有可能的表示中,选择的是

|β0+βTx|=1

其中x表示最靠近超平面的训练示例。通常,最靠近超平面的训练示例称为支持向量。这种表示称为规范超平面

现在,我们使用几何结果,该结果给出点x和超平面(β,β0)之间的距离

distance=|β0+βTx|||β||.

特别是对于规范超平面,分子等于1,并且到支持向量的距离是

distance support vectors=|β0+βTx|||β||=1||β||.

回想一下,上一节中介绍的裕度,这里表示为M,是到最接近示例距离的两倍

M=2||β||

最后,最大化M的问题等同于在某些约束条件下最小化函数L(β)的问题。约束条件模拟了超平面正确分类所有训练示例xi的要求。形式上,

minβ,β0L(β)=12||β||2 受制于 yi(βTxi+β0)1 i,

其中yi表示每个训练示例的标签。

这是一个拉格朗日优化问题,可以使用拉格朗日乘子来求解最优超平面的权重向量β和偏差β0

源代码

  • 可下载代码: 点击 此处
  • 代码概览
    #include <opencv2/core.hpp>
    #include <opencv2/ml.hpp>
    using namespace cv;
    using namespace cv::ml;
    int main(int, char**)
    {
    // 设置训练数据
    int labels[4] = {1, -1, -1, -1};
    float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
    Mat trainingDataMat(4, 2, CV_32F, trainingData);
    Mat labelsMat(4, 1, CV_32SC1, labels);
    // 训练SVM
    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 image = Mat::zeros(height, width, CV_8UC3);
    // 显示SVM给出的决策区域
    Vec3b green(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) << j,i);
    float response = svm->predict(sampleMat);
    if (response == 1)
    image.at<Vec3b>(i,j) = green;
    否则如果 (response == -1)
    image.at<Vec3b>(i,j) = blue;
    }
    }
    // 显示训练数据
    int thickness = -1;
    circle( image, Point(501, 10), 5, Scalar( 0, 0, 0), thickness );
    circle( image, Point(255, 10), 5, Scalar(255, 255, 255), thickness );
    circle( image, Point(501, 255), 5, Scalar(255, 255, 255), thickness );
    circle( image, Point( 10, 501), 5, Scalar(255, 255, 255), thickness );
    // 显示支持向量
    thickness = 2;
    Mat sv = svm->getUncompressedSupportVectors();
    for (int i = 0; i < sv.rows; i++)
    {
    const float* v = sv.ptr<float>(i);
    circle(image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness);
    }
    imwrite("result.png", image); // 保存图像
    imshow("SVM 简单示例", image); // 显示给用户
    返回 0;
    }
    派生自 Mat 的模板矩阵类。
    定义 mat.hpp:2247
    n 维密集数组类
    定义 mat.hpp:829
    uchar * ptr(int i0=0)
    返回指向指定矩阵行的指针。
    _Tp & at(int i0=0)
    返回对指定数组元素的引用。
    int cols
    定义 mat.hpp:2155
    int rows
    行和列的数量,当矩阵具有超过 2 维时为 (-1, -1)
    定义 mat.hpp:2155
    定义迭代算法终止条件的类。
    定义 types.hpp:893
    短数值向量的模板类,是 Matx 的一个特例。
    定义 matx.hpp:369
    std::shared_ptr< _Tp > Ptr
    定义 cvstd_wrapper.hpp:23
    #define CV_32SC1
    定义 interface.h:112
    #define CV_32F
    定义 interface.h:78
    #define CV_8UC3
    定义 interface.h:90
    @ circle
    定义 gr_skig.hpp:62
    void imshow(const String &winname, InputArray mat)
    在指定的窗口中显示图像。
    int waitKey(int delay=0)
    等待按下键。
    CV_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
    将图像保存到指定文件。
    int main(int argc, char *argv[])
    定义 highgui_qt.cpp:3
    定义 ml.hpp:75
    定义 core.hpp:107

解释

  • 设置训练数据

本练习的训练数据由一组属于两个不同类别之一的带标签的二维点组成;其中一个类别由一个点组成,另一个类别由三个点组成。

int labels[4] = {1, -1, -1, -1};
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };

稍后将使用的函数cv::ml::SVM::train要求训练数据存储为浮点数的cv::Mat对象。因此,我们根据上面定义的数组创建这些对象。

Mat trainingDataMat(4, 2, CV_32F, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);
  • 设置SVM的参数

    在本教程中,我们介绍了在最简单的情况下SVM的理论,即训练样本分散到两个线性可分的不同类别中。但是,SVM可以用于各种各样的问题(例如,具有非线性可分数据的的问题,使用核函数来提高样本维数的SVM等)。因此,在训练SVM之前,我们必须定义一些参数。这些参数存储在cv::ml::SVM类的对象中。

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

这里

  • SVM的类型。我们在这里选择C_SVC类型,它可以用于n类分类(n ≥ 2)。这种类型的重要特征是它处理类别的不完全分离(即当训练数据是非线性可分时)。这个特性在这里并不重要,因为数据是线性可分的,我们只选择这种SVM类型是因为它最常用。
  • SVM核的类型。我们还没有讨论核函数,因为它们对我们正在处理的训练数据来说并不重要。然而,让我们现在简要解释一下核函数背后的主要思想。它是对训练数据进行的映射,以提高其与线性可分数据集的相似性。这种映射包括增加数据的维数,并且使用核函数可以有效地完成。我们在这里选择LINEAR类型,这意味着不进行映射。此参数使用cv::ml::SVM::setKernel定义。
  • 算法的终止准则。SVM训练过程是通过以迭代方式解决约束二次优化问题来实现的。在这里,我们指定最大迭代次数和容错率,因此我们允许算法在较少的步骤中完成,即使最佳超平面尚未计算出来。此参数在一个cv::TermCriteria结构中定义。
  • 训练SVM 我们调用方法cv::ml::SVM::train来构建SVM模型。
svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);
  • SVM分类的区域

    方法 cv::ml::SVM::predict 用于使用训练好的 SVM 对输入样本进行分类。在本例中,我们使用此方法根据 SVM 的预测结果对空间进行着色。换句话说,程序遍历图像,将其像素解释为笛卡尔平面上的点。每个点根据 SVM 预测的类别着色;如果是标签为 1 的类别则为绿色,如果是标签为 -1 的类别则为蓝色。

Vec3b green(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) << j,i);
float response = svm->predict(sampleMat);
if (response == 1)
image.at<Vec3b>(i,j) = green;
否则如果 (response == -1)
image.at<Vec3b>(i,j) = blue;
}
}
  • 支持向量

    这里我们使用几种方法来获取有关支持向量的信息。方法 cv::ml::SVM::getSupportVectors 获取所有支持向量。我们在这里使用此方法来查找作为支持向量的训练样本并突出显示它们。

thickness = 2;
Mat sv = svm->getUncompressedSupportVectors();
for (int i = 0; i < sv.rows; i++)
{
const float* v = sv.ptr<float>(i);
circle(image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness);
}

结果

  • 代码打开一个图像并显示两类的训练样本。一类的点用白圈表示,另一类用黑圈表示。
  • SVM 经过训练,并用于对图像的所有像素进行分类。这导致图像被划分为蓝色区域和绿色区域。这两个区域之间的边界是最佳分离超平面。
  • 最后,使用灰色环围绕训练样本显示支持向量。