OpenCV笔记(3)实现支持向量机(SVM)
参考教程:点击打开链接
参考教程使用的是OpenCV 2.0 版本,这里将其修改为3.0版本。
1.SVM(支持向量机)
SVM是一种训练机器学习的算法,可以用于解决分类和回归问题。
正式定义:是一个能将不同类样本在样本空间分割的超平面,给定一些标记好的训练样本,SVM算法输出一个最优化的分隔超平面。
判定是否为最优平面的依据:
如上图所示,给定一些分属两类的特征点,这些点可以通过直线分割,我们要找到一个最优的直线。满足的直线很多,该怎么定义最优?有个评价标准是直线到所有点的距离最远。距离样本点太近的话,直线对噪声敏感度高,泛化性比较差。
SVM算法就是找到能将某个值最大化的超平面,这个值是超平面离所有样本点的最小距离。这个距离叫间隔。
相比较其他机器学习算法,它的优点有样本小 、结构风险小、非线性等。
SVM离不开核函数。简单来说,核函数就是将低维空间的线性不可分类问题,转化为高维空间的线性可分问题,在高维空间找到最优边界。
2.OpenCV实现
首先,建立训练样本:
//建立训练样本
int labels[10] = {1, 1, -1, 1, -1, -1, -1, 1, -1, -1};
float trainingData[10][2] = {{501,150},{255,10},{501,255},{10,501},{25,80},{150,300},{77,200},{300,300},{45,250},{200,200}};
Mat labelsMat(10, 1, CV_32SC1, labels);
Mat trainingDataMat(10, 2, CV_32FC1, trainingData);
labels是训练数据的分类标记,有两类:1和-1。
trainingData是训练数据。
设置SVM的参数:
Ptr<SVM> svm = SVM::create();//创建分类器
svm->setType(SVM::C_SVC);//SVM类型
svm->setKernel(SVM::LINEAR);//核函数的类型
svm->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON));//算法终止条件
这里opencv 2.x版本和3.x版本设置方式不一样,下面是2.x版本:
CvSVMParams params;//创建分类器
params.svm_type = CvSVM::C_SVC;//SVM类型
params.kernel_type = CvSVM::LINEAR;//核函数的类型
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);//算法终止条件
其它的参数我们需要时,可以继续设置。
开始训练:
//设置训练数据并训练分类器
Ptr<TrainData> tData = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat);
svm->train(tData);
下面是2.x版本的训练:
CvSVM SVM;
SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);
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);
//float response = SVM.predict(sampleMat); 这里有不同
if (response == 1)
{
image.at<Vec3b>(i, j) = green;
}
else if (response == -1)
{
image.at<Vec3b>(i, j) = blue;
}
}
}
给空间上色,着色取决于SVM的分类,绿色是标记为1的点,蓝色是标记为-1的点。
显示结果:
//显示训练数据
int thickness = -1;
int lineType = 8;
Scalar c1 = Scalar::all(0); //标记为1的显示成黑点
Scalar c2 = Scalar::all(255); //标记成-1的显示成白点
for (int i = 0; i < labelsMat.rows; i++)
{
const float* v = trainingDataMat.ptr<float>(i);
Point pt = Point((int)v[0], (int)v[1]);
if (labels[i] == 1)
{
circle(image, pt, 5, c1, thickness, lineType);
}
else
{
circle(image, pt, 5, c2, thickness, lineType);
}
}
imshow("SVM", image);
waitKey(0);
最终结果:
程序创建一张图像,在其中显示训练样本,1为黑点,-1为白点。
训练得到SVM,并将图像的每一个像素分类。分类的结果将图像分为蓝绿两部分,中间线是最优分割平面。
完整代码:
#include <stdio.h>
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace ml;
int main()
{
int width = 512;
int height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
//建立训练样本
int labels[10] = { 1, -1, 1, 1, -1, 1, -1, 1, -1, -1 };
float trainingData[10][2] = {{501,150},{255,10},{501,255},{10,501},{25,80},{150,300},{77,200},{300,300},{45,250},{200,200}};
Mat labelsMat(10, 1, CV_32SC1, labels);
Mat trainingDataMat(10, 2, CV_32FC1, trainingData);
Ptr<SVM> svm = SVM::create();//创建分类器
svm->setType(SVM::C_SVC);//SVM类型
svm->setKernel(SVM::LINEAR);//核函数的类型
svm->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON));//算法终止条件
//设置训练数据并训练分类器
Ptr<TrainData> tData = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat);
svm->train(tData);
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);
//float response = SVM.predict(sampleMat); 这里有不同
if (response == 1)
{
image.at<Vec3b>(i, j) = green;
}
else if (response == -1)
{
image.at<Vec3b>(i, j) = blue;
}
}
}
//显示训练数据
int thickness = -1;
int lineType = 8;
Scalar c1 = Scalar::all(0); //标记为1的显示成黑点
Scalar c2 = Scalar::all(255); //标记成-1的显示成白点
for (int i = 0; i < labelsMat.rows; i++)
{
const float* v = trainingDataMat.ptr<float>(i);
Point pt = Point((int)v[0], (int)v[1]);
if (labels[i] == 1)
{
circle(image, pt, 5, c1, thickness, lineType);
}
else
{
circle(image, pt, 5, c2, thickness, lineType);
}
}
imshow("SVM", image);
waitKey(0);
}