- 支持向量机( SVM )是一个类分类器,正式的定义是一个能够将不同类样本在样本空间分隔的超平面;
即给定一些标记(label)好的训练样本 (监督式学习), SVM算法输出一个最优化的分隔超平面 - (下例用直线分割2个类)定义一条评价直线好坏的标准:
距离样本太近的直线不是最优的,因为这样的直线对噪声敏感度高,泛化性较差;
因此我们的目标是找到一条直线,离所有点的距离最远 - 计算最优超平面
权重向量+偏置
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
using namespace cv::ml;
//opencv4https://blog.youkuaiyun.com/AlonewaitingNew/article/details/100764610
//opencv2http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html#id2
int main()
{
//用于显示分类结果的图像
const int Kwidth = 512;
const int Kheight = 512;
Mat image = Mat::zeros(Kheight, Kwidth, CV_8UC3);
//1.建立训练样本
int labels[4] ={ 1, -1, -1, -1 };//2个类
float trainingData[4][2]={ {501, 10}, {255, 10}, {501, 255}, {10, 501} };//分属两类的2维点
//训练数据存储在float类型的Mat结构中
Mat trainingMat(4, 2, CV_32FC1, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);
//样本数据必须是CV_32FC1类型,opencv3版本决定的
//样本标签必须是CV_32SC1类型,opencv3后从int数组转换为CV_32SC1类型,而opencv2是从float数据转换
/*
//1.建立训练样本
int labels[20];
for (int i = 0; i < 10; i++)
labels[i] = 1;
for (int i = 10; i < 20; i++)
labels[i] = -1;
float trainDataArray[20][2];
RNG rng;
for (int i = 0; i < 10; i++)
{
trainDataArray[i][0] = 400 + static_cast<float>(rng.gaussian(30));
trainDataArray[i][1] = 400 + static_cast<float>(rng.gaussian(30));
}
for (int i = 10; i < 20; i++)
{
trainDataArray[i][0] = 30 + static_cast<float>(rng.gaussian(30));
trainDataArray[i][1] = 30 + static_cast<float>(rng.gaussian(30));
}
//训练数据存储在float类型的Mat结构中
Mat labelsMat(20, 1, CV_32SC1, labels);
Mat trainingMat(20, 2, CV_32FC1, trainDataArray);
*/
//2.设置SVM参数
Ptr<SVM> model = SVM::create();
//参数设置
/*svm->setGamma(0.01);
svm->setC(10.0);*/
model->setC(1);
model->setType(SVM::C_SVC);//SVM类型:C类型,该类型可以用于n-类分类问题 (n>=2)
//model->setKernel(SVM::LINEAR);//SVM(线性)核类型,目的是为了将训练样本映射到更有利于可线性分割的样本集
//但是生成支持向量时,会把所有支持向量压缩为一个(左上角),可提高运行效率
//参考:https://blog.youkuaiyun.com/heroacool/article/details/50998154
model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));//算法终止条件
model->setKernel(SVM::POLY);//SVM(多项式)核类型
model->setDegree(1.0);
//3.训练支持向量机
cout << "开始训练SVM,请等待..." << endl;
Ptr<TrainData> data = TrainData::create(trainingMat, ROW_SAMPLE, labelsMat);
model->train(data);
cout << "SVM训练完成" << endl;
//4.SVM区域分割
//对图像内所有512*512个背景点进行预测,不同的预测结果,图像背景区域显示不同的颜色
Vec3b pink(255,0,255),blue(255, 0, 0);
Mat sampleMat;
for (int i = 0; i < image.rows; ++i)
{
for (int j = 0; j < image.cols; ++j)
{
sampleMat = (Mat_<float>(1, 2) << j,i); //生成测试数据
//predict通过重建训练完毕的支持向量机来将输入的样本分类
//通过该函数给向量空间着色, 及将图像中的每个像素当作卡迪尔平面上的一点,
//每一点的着色取决于SVM对该点的分类类别:粉色表示标记为1的点,蓝色表示标记为-1的点
int guesslabel = static_cast<int>(model->predict(sampleMat)); //进行预测,实现分类,返回1或-1
if (guesslabel == 1)
{
image.at<Vec3b>(i,j) = pink;
}
else if(guesslabel == -1)
{
image.at<Vec3b>(i,j) = blue;
}
//因为只有两个类,这里省略else{}
}
}
//5.支持向量
//把训练样本点,显示在图相框内
for (int i = 0; i < trainingMat.rows; i++)
{
const float* v = trainingMat.ptr<float>(i);
Point pt = Point((int)v[0], (int)v[1]);
if (labels[i] == 1) //不同类的圆点,标记不同的颜色
{
circle(image, pt, 2, Scalar::all(255), 2, 8);
}
else if (labels[i] == -1)
{
circle(image, pt, 2, Scalar(0, 0, 255), 2, 8);
}
//因为只有两个类,这里省略else{}
}
//支持向量(的样本点)
Mat svm = model->getSupportVectors();
for (int i = 0; i < svm.rows; i++)
{
const float* v = svm.ptr<float>(i);
circle(image,Point((int)v[0],(int)v[1]),6,Scalar(128, 128, 128), 2, 8);
}
//显示分类结果图像
imshow("SVM", image);
waitKey(0);
return 0;
}
输出结果:
- 支持向量机(SVM)对线性不可分数据的处理
下例的训练样本由分属于两个类别的2维点组成
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
using namespace cv::ml;
int main()
{
//用于显示分类结果的图像
const int WIDTH = 512, HEIGHT = 512;
Mat Image = Mat::zeros(HEIGHT, WIDTH, CV_8UC3);
//--------------------- 1.建立训练样本---------------------------------------------
//训练数据存储在float类型的Mat结构中
Mat trainData(2 * 100, 2, CV_32FC1);//2个类,每个类的样本数是100
Mat labels(2 * 100, 1, CV_32SC1);
//随机建立训练样本
RNG rng;
int nLinearSamples = (int)(0.9f * 100);
//----------------①先生成两类线性可分样本
//rowRange和colRange函数可以获取某些范围内行或列的指针
//class 1随机样本生成
Mat trainClass = trainData.rowRange(0, nLinearSamples);
//x坐标范围[0,0.4)
Mat c = trainClass.colRange(0, 1);
rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(0.4 * WIDTH));
//y坐标范围[0,1)
c = trainClass.colRange(1, 2);
rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));
//class 2随机样本生成
trainClass = trainData.rowRange(2 * 100 - nLinearSamples, 2 * 100);
//x坐标范围[0.6, 1]
c = trainClass.colRange(0, 1);
rng.fill(c, RNG::UNIFORM, Scalar(0.6*WIDTH), Scalar(WIDTH));
//y坐标范围[0, 1)
c = trainClass.colRange(1, 2);
rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));
//----------------②同时生成重叠分布线性不可分的两类样本
//classes 1,2 随机样本生成
trainClass = trainData.rowRange(nLinearSamples, 2 * 100 - nLinearSamples);
//x坐标范围[0.4, 0.6)
c = trainClass.colRange(0, 1);
rng.fill(c, RNG::UNIFORM, Scalar(0.4*WIDTH), Scalar(0.6*WIDTH));
//y坐标范围[0, 1)
c = trainClass.colRange(1, 2);//这些新样本点分布在x[0.4,0.6),标签label随机
rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));
//------------------------- label ------------------------------------------------------------
labels.rowRange(0, 100).setTo(1); // Class 1 //setTo全部设置为*
labels.rowRange(100, 2 * 100).setTo(2); // Class 2
//------------------------ 2. 设置SVM参数 -----------------------------------------------------
Ptr<SVM> params = SVM::create();
//参数设置
/*svm->setGamma(0.01);
svm->setC(10.0);*/
params->setC(0.1);
params->setType(SVM::C_SVC);//SVM类型:C类型,该类型可以用于n-类分类问题 (n>=2)
//params->setKernel(SVM::LINEAR);//SVM核类型,目的是为了将训练样本映射到更有利于可线性分割的样本集
params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, (int)1e7, 1e-6));//算法终止条件
params->setKernel(SVM::POLY);//SVM(多项式)核类型
params->setDegree(1.0);
//------------------------ 3. 训练SVM支持向量机 ------------------------------------------------
cout << "开始训练SVM,请等待..." << endl;
Ptr<TrainData> data = TrainData::create(trainData, ROW_SAMPLE, labels);
params->train(data);
cout << "SVM训练完成" << endl;
//------------------------ 4. SVM区域分割 ----------------------------------------
Vec3b green(0, 100, 0), blue(100, 0, 0);
for (int i = 0; i < Image.rows; ++i)
for (int j = 0; j < Image.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1, 2) << i, j);
int response = static_cast<int>(params->predict(sampleMat));
if (response == 1)
{
Image.at<Vec3b>(j, i) = green;
}
else if (response == 2)
{
Image.at<Vec3b>(j, i) = blue;
}
}
//----------------------- 5. 显示训练样本 --------------------------------------------
float px, py;
// Class 1
for (int i = 0; i < 100; ++i)
{
px = trainData.at<float>(i, 0);
py = trainData.at<float>(i, 1);
circle(Image, Point((int)px, (int)py), 3, Scalar(0, 255, 0), -1, 8);
}
// Class 2
for (int i = 100; i < 2 * 100; ++i)
{
px = trainData.at<float>(i, 0);
py = trainData.at<float>(i, 1);
circle(Image, Point((int)px, (int)py), 3, Scalar(255, 0, 0), -1, 8);
}
//------------------------- 6. 支持向量 --------------------------------------------
//支持向量(的样本点)
Mat svm = params->getSupportVectors();
for (int i = 0; i < svm.rows; i++)
{
const float* v = svm.ptr<float>(i);
circle(Image, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), 2, 8);
}
imshow("SVM", Image); // SVM图像
waitKey(0);
return 0;
}
输出结果: