1、概述
在了解支持向量机SVM用法之前先了解一些概念:
(1)线性可分和线性不可分
如果在一个二维空间有一堆样本,如下图所示,如果能找到一条线把这两类样本分开至线的两侧,那么这个样本集就是线性可分,否则就是线性不可分。
《opencv实用探索·二十二》支持向量机SVM用法-LMLPHP
如果在一个三维空间有一堆样本,如果能找到一个平面把三维空间中的两类样本分开至平面的两侧,那么这个样本就是线性可分,否则为线性不可分。
如果在一个超过三维空间的更高维度上能找一个平面(这里我们称这个平面为超平面)把两类样本分开至超平面两侧,那么这个样本就是线性可分,否则为线性不可分。

一般情况下,把能够可以被一条直线(更一般的情况,即一个超平面)分割的数据称为线性可分的数据,所以超平面是线性分类器。

(2)支持向量机(Support Vector Machine,SVM)
在OpenCV中,支持向量机(Support Vector Machine,SVM)是一种用于图像分类和对象识别的机器学习算法。SVM 是一种监督学习算法,它可以用于二分类或多分类问题。在图像处理领域,SVM 经常用于训练分类器,从而对图像中的对象进行识别。

SVM 的基本思想是找到一个最优的超平面,将不同类别的样本分开,并且使得两个类别之间的间隔最大化。这个最优的超平面由支持向量(Support Vectors)定义,它们是离超平面最近的样本点。该怎么理解这句话?

我们还是以一个二维平面的样本集为例,如下图所示:
L1,L2,L3都可以把两类样本分离,L1离P1样本向量最近,L3离P2样本向量最近,我们称这两个样本向量为支持向量,这两条线(更宽泛一点称为超平面)称为支撑超平面,他们分别支撑两类数据。L1和L3之间的距离d称为间隔,我们需要找到一个最好的超平面L2使得间隔最大。
《opencv实用探索·二十二》支持向量机SVM用法-LMLPHP

2、支持向量机SVM用法步骤
在OpenCV中,SVM的使用通常涉及以下步骤:
(1)数据准备: 收集并准备训练数据,确保每个样本都有相应的标签。
(2)创建SVM对象: 使用OpenCV的 cv::ml::SVM 类创建一个SVM对象。
(3)设置训练参数: 设置SVM的训练参数,例如核函数、惩罚系数等。
(4)训练SVM模型: 使用训练数据对SVM进行训练。
(5)预测: 使用训练好的SVM模型对新的数据进行分类预测。

1.数据准备
训练数据(Training Data): 这是用于训练SVM模型的输入数据集。对于分类问题,每个训练样本都是一个输入向量,表示数据的特征。下面代码中,trainingDataMat 是一个矩阵,每一行代表一个样本,每一列代表一个特征。在这个例子中,每个样本有两个特征,分别是 x 和 y 的坐标。

// 设置训练数据
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
Mat trainingDataMat(4, 2, CV_32FC1, trainingData);

如果把这4个点在图像上标出来,大致位置如下图所示(注意:501表示像素的列,10表示像素的行,其他坐标也是如此),同时根据下面设置的样本标签来看,图中右上角的点为正样本,其余为负样本,图中红线位置大概就是超平面,把两类样本分至两侧。
《opencv实用探索·二十二》支持向量机SVM用法-LMLPHP
标签(Labels): 这是训练数据对应的输出标签或类别。标签表示每个训练样本所属的类别。在下面代码中,labelsMat 是一个矩阵,每一行对应于训练数据中的一个样本,表示该样本所属的类别。通常,标签是整数值,例如 1 表示正类别,-1 表示负类别。

// 设置标签
int labels[4] = { 1, -1, -1, -1 };
Mat labelsMat(4, 1, CV_32SC1, labels);

对于上面的四个训练样本,每个样本有两个特征。对应的标签是 {1, -1, -1, -1}。这表示第一个样本属于正类别(1),而其余三个样本属于负类别(-1)。这些训练数据和标签将用于训练支持向量机模型,使其能够对新的输入数据进行分类。

2.创建SVM对象

//如果使用OpenCV 4.x 版本请用如下代码
Ptr<SVM> svm = SVM::create();

//如果使用OpenCV 3.x版本请用如下代码
SVM::Params params;

3.设置SVM的训练参数

//如果使用OpenCV 4.x 版本请用如下代码
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::LINEAR);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));

//如果使用OpenCV 3.x版本请用如下代码
params.svmType = SVM::C_SVC;
params.kernelType = SVM::LINEAR;
params.termCrit = TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6);

setType 用于设置 SVM 的类型,即分类器的类型。
SVM 的类型:
cv::ml::SVM::C_SVC:用于分类。
cv::ml::SVM::NU_SVC:用于分类,支持类别不平衡。
cv::ml::SVM::ONE_CLASS:用于单类别分类。
cv::ml::SVM::EPS_SVR:用于回归问题。
cv::ml::SVM::NU_SVR:用于回归问题,支持回归损失参数。

setKernel 用于设置核函数,即 SVM 在高维空间中处理数据的方法
核函数:
cv::ml::SVM::LINEAR:线性核函数,它适用于线性可分的数据。
cv::ml::SVM::POLY:多项式核函数。
cv::ml::SVM::RBF:径向基核函数(高斯核函数)。
cv::ml::SVM::SIGMOID:Sigmoid 核函数。

setTermCriteria 用于设置 SVM 的终止标准,即训练停止的条件。
cv::TermCriteria::MAX_ITER 表示基于最大迭代次数的终止标准,这里设置为最大迭代次数为 100。
1e-6 是一个容差,表示允许的训练误差

4.训练SVM模型

//如果使用OpenCV 4.x 版本请用如下代码
svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);

//如果使用OpenCV 3.x版本请用如下代码
Ptr<SVM> svm = StatModel::train<SVM>(trainingDataMat, ROW_SAMPLE, labelsMat, params);

ROW_SAMPLE: 这是一个标志,表示每行数据是一个训练样本。在分类问题中,每行通常代表一个样本,每列是该样本的特征。这个标志告诉函数按行处理数据

svm->train() 函数用于训练 SVM 模型。在训练期间,模型将学会如何将输入数据映射到给定的类别,以便在未知数据上进行分类。

5.预测
使用训练好的SVM模型对数据进行预测
假如有一个图像宽高都是512,现在把图像中每个像素的坐标作为一个新输入的样本,并对这个样本进行预测是正样本还是负样本,如果是正样本就用绿色标出,如果是负样本就用蓝色标出。

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)
		{
		    //把图像中每个像素点的坐标作为一个样本进行创建,i是行对应y坐标,j是列对应x坐标
			Mat sampleMat = (Mat_<float>(1, 2) << j, i);
			//使用训练好的 SVM 模型对当前像素的特征进行分类。response 存储了 SVM 模型对该样本的预测结果
			//response 为1表示正样本用绿色标出,为-1表示负样本用蓝色标出
			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 模型的分类结果,将图像的不同区域标记为不同的颜色,从而可视化 SVM 模型对数据的分类效果
《opencv实用探索·二十二》支持向量机SVM用法-LMLPHP

最后在画出训练数据和支持向量

//显示训练数据 (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;
	Mat sv = svm->getSupportVectors();  //返回的是支持向量的坐标
	std::cout << "Support Vectors:\n" << sv << std::endl;  //[-0.008130081, 0.008163265]

	for (int i = 0; i < sv.rows; ++i)  //rows为1,表示每个样本类别中只有一个支持向量
	{
		const float* v = sv.ptr<float>(i);
		int x = (int)v[0];
		int y = (int)v[1];
		circle(image, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), thickness, lineType);
	}

《opencv实用探索·二十二》支持向量机SVM用法-LMLPHP
我的理解是支持向量应该是上面样本中的某个样本的坐标,这里为啥是[-0.008130081, 0.008163265]?
支持向量坐标是相对于输入特征空间的,不是图像坐标,“特征空间” 是指用于训练 SVM 模型的样本数据的特征表示的空间,这些坐标在特征空间中描述了支持向量的位置。由于 SVM 的实现可能对数据进行了缩放或标准化,因此这些坐标可能是相对于某种处理后的特征空间而言的。

最后附上完整的代码:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <opencv2/imgproc.hpp>
#include "opencv2/imgcodecs.hpp"
using namespace cv;
using namespace cv::ml;

int main()
{

	// 视觉表达数据的设置(Data for visual representation)
	int width = 512, height = 512;
	Mat image = Mat::zeros(height, width, CV_8UC3);

	//建立训练数据( Set up training data)
	int labels[4] = { 1, -1, -1, -1 };
	Mat labelsMat(4, 1, CV_32SC1, labels);

	float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
	Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
	std::cout << trainingDataMat << std::endl;

	//设置支持向量机SVM的参数(Set up SVM's parameters)

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


	// 训练支持向量机(Train the SVM)
	svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);

	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)
		{
			Mat sampleMat = (Mat_<float>(1, 2) << j, i);
			float response = svm->predict(sampleMat);

			if (response == 1)
				image.at<Vec3b>(i, j) = green;
			else if (response == -1)
				image.at<Vec3b>(i, j) = 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;
	Mat sv = svm->getSupportVectors();
	std::cout << "Support Vectors:\n" << sv << std::endl;

	for (int i = 0; i < sv.rows; ++i)
	{
		const float* v = sv.ptr<float>(i);
		int x = (int)v[0];
		int y = (int)v[1];
		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用法-LMLPHP
02-28 06:24