(九)OpenCV其它功能_01_支持向量机(SVM)

本文介绍了OpenCV中的支持向量机(SVM)应用,它是一个用于分类的算法,寻找最佳分隔超平面以区分不同类别的样本。SVM的目标是找到远离所有样本的超平面,以提高模型的泛化能力。内容涵盖了SVM的基本概念和如何处理线性不可分的数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 支持向量机( SVM )是一个类分类器,正式的定义是一个能够将不同类样本在样本空间分隔的超平面;
    即给定一些标记(label)好的训练样本 (监督式学习), SVM算法输出一个最优化的分隔超平面
  2. (下例用直线分割2个类)定义一条评价直线好坏的标准:
    距离样本太近的直线不是最优的,因为这样的直线对噪声敏感度高,泛化性较差;
    因此我们的目标是找到一条直线,离所有点的距离最远
  3. 计算最优超平面
    权重向量+偏置
#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;
}

输出结果:
在这里插入图片描述

  1. 支持向量机(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;
}

输出结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值