OpenCV计算机视觉开发入门与实践<三十七>:OpenCV中的机器学习

摘要 :本文介绍 OpenCV 机器学习模块中的两类经典算法。第一部分讲解支持向量机(SVM)的原理,包括线性函数、核技巧等核心概念,并详细列出 OpenCV 4.10 中 cv::ml::SVM 类的常用函数与参数;第二部分介绍贝叶斯分类器的基本原理、常见类型(Naive Bayes、TAN、BAN、GBN)及分类流程。文中均配有完整的 C++ 示例代码,帮助读者快速上手实践。

  OpenCV专门提供了一个机器学习模块来实现机器学习算法。该模块包含常见的机器学习算法:

(1)Bayesian,Normal Bayes Classifier(贝叶斯分类)。

(2)K-Nearest Neighbour Classifier(K-邻近算法)。

(3)SVM,Support Vector Machine(支持向量机)。

(4)Expectation-Maximization(EM算法)。

(5)Decision Tree(决策树)。

(6)Random Trees Classifier(随机森林算法)。

(7)Extremely Randomized Trees Classifier(绝对随机森林算法)。

(8)Boosted Tree Classifier(Boost树算法)。

(9)Gradient Boosted Trees(梯度Boost树算法)。

(10)ANN,Artificial Neural Networks(人工神经网络)。

  OpenCV机器学习模块中还集成了一些目前比较优秀的算法库,如libsvm等,不仅可以用于图像处理,也可以用于解决其他机器学习问题。

1. SVM向量机

1.1 SVM概述

  支持向量机(Support Vector Machine,SVM,又名支持向量网络)是监督学习中最有影响力的方法之一。它类似于逻辑回归,也是基于线性函数wx+b的。不同于逻辑回归的是,支持向量机不输出概率,只输出类别。当wx+b为正时,支持向量机预测属于正类;当wx+b为负时,支持向量机预测属于负类。支持向量机的一个重要创新是核技巧(kernel trick)。核技巧观察到许多机器学习算法都可以写成样本间点积的形式。例如,支持向量机中的线性函数可以重写为:

  其中,x是训练样本,α是系数向量。学习算法重写为这种形式,允许我们将x替换为特征函数Ø(x)的输出,点积替换为被称为核函数(kernel function)的函数k(x,x)= Ø(x)·Ø(x)。运算符·表示类似于Ø(x)TØ(x)的点积。对于某些特征空间,我们可能不会书面地使用向量内积。在某些无限维空间中,我们需要使用其他类型的内积,如基于积分而非和的内积。使用核估计替换点积之后,我们可以使用如下函数进行预测:

  这个函数关于x是非线性的,关于Ø(x)是线性的。α和f(x)之间的关系也是线性的。核函数完全等价于用Ø(x)预处理所有的输入,然后在新的转换空间学习线性模型。核技巧十分强大的原因有两个。其一,它使我们能够使用保证有效收敛的凸优化技术来学习非线性模型(关于x的函数)。这是可能的,因为我们可以认为Ø是固定的,仅优化α,即优化算法可以将决策函数视为不同空间中的线性函数。其二,核函数k的实现方法通常比先构建Ø(x)再算点积高效很多。

1.2 OpenCV中的SVM

  OpenCV 4.10中给出了支持向量机的实现,即cv::ml::SVM类。此类的声明在include/opencv2/ml.hpp文件中,实现在modules/ml/src/svm.cpp文件中。它既支持两分类,也支持多分类,还支持回归等。OpenCV中SVM的实现源自libsvm库。其中:

(1)cv::ml::SVM类:继承自cv::ml::StateModel,而cv::ml::StateModel又继承自

cv::Algorithm。

(2)create函数:用来创建一个SVM对象。

(3)setType/getType函数:设置/获取SVM公式类型,包括C_SVC、NU_SVC、

ONE_CLASS、EPS_SVR、NU_SVR,用于指定分类、回归等,默认值为C_SVC。

(4)setGamma/getGamma函数:设置/获取核函数的γ参数,默认值为1。

(5)setCoef0/getCoef0函数:设置/获取核函数的coef0参数,默认值为0。

(6)setDegree/getDegree函数:设置/获取核函数的degree参数,默认值为0。

(7)setC/getC函数:设置/获取SVM优化问题的C参数,默认值为0。

(8)setNu/getNu函数:设置/获取SVM优化问题的υ参数,默认值为0。

(9)setP/getP函数:设置/获取SVM优化问题的ε参数,默认值为0。

(10)setClassWeights/getClassWeights函数:应用在SVM::C_SVC中,设置/获取

weights,默认值是空cv::Mat。

(11)setTermCriteria/getTermCriteria函数:设置/获取SVM训练时迭代终止条件,

默认值是cv::TermCriteria(cv::TermCriteria::MAX_ITER + TermCriteria::EPS,1000, FLT_EPSILON)。

(12)setKernel/getKernelType函数:设置/获取SVM核函数类型,包括CUSTOM、LINEAR、POLY、RBF、SIGMOID、CHI2、INTER,默认值为RBF。

(13)setCustomKernel函数:初始化CUSTOM核函数。

(14)trainAuto函数:用最优参数训练SVM。

(15)getSupportVectors/getUncompressedSupportVectors函数:获取所有的支持向量。

(16)getDecisionFunction函数:决策函数。

(17)getDefaultGrid/getDefaultGridPtr函数:生成SVM参数网格。

(18)save/load函数:保存/载入已训练好的model,支持XML、YAML、JSON格式。

(19)train/predict函数:用于训练/预测。

【例13.1】利用SVM进行机器学习

cpp 复制代码
#include <iostream>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/ml/ml.hpp"
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace cv::ml;

int main()
{
	cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);

	// 视觉表示数据
	int width = 512, height = 512;
	Mat image = Mat::zeros(height, width, CV_8UC3);

	// 设置训练数据
	// ! [setup1]
	int labels[4] = { 1, -1, -1, -1 };
	float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
	// ! [setup1]
	// ! [setup2]
	Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
	Mat labelsMat(4, 1, CV_32SC1, labels);
	// ! [setup2]

	// 训练 SVM
	// ! [init]

	Ptr<SVM> svm = SVM::create();
	svm->setType(SVM::C_SVC);
	svm->setKernel(SVM::LINEAR);
	svm->setDegree(1.0);
	svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
	// ! [init]
	// ! [train]
	svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);
	// ! [train]

	// 显示支持向量机给出的决策区域
	// ! [show]
	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 data[] = { (float)j, (float)i };
			Mat sampleMat(1, 2, CV_32FC1, data);

			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]

	// 显示训练数据
	// ! [show_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_data]
	// 显示支持矩阵
	// ! [show_vectors]
	thickness = 2;
	lineType = 8;
	Mat sv = svm->getSupportVectors();

	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, CV_RGB(255, 0, 0), thickness, lineType);
	}

	// ! [show_vectors]

	imwrite("result.png", image); // 保存图片

	imshow("SVM Simple Example", image); // 向用户显示 
	waitKey(0);
}

2. 贝叶斯分类器

  贝叶斯分类器是各种分类器中分类错误概率最小,或者在预先给定代价的情况下平均风险最小的分类器。它的设计方法是一种最基本的统计分类方法,其分类原理是通过某对象的先验概率,利用贝叶斯公式计算出其后验概率,即该对象属于某一类的概率,选择具有最大后验概率的类作为该对象所属的类。

  研究较多的贝叶斯分类器主要有4种,分别是Naive Bayes、TAN、BAN和GBN。贝叶斯网络是一个带有概率注释的有向无环图,图中的每一个节点均表示一个随机变量,图中两节点间若存在一条弧,则表示这两节点相对应的随机变量是概率相依的,反之则说明这两个随机变量是条件独立的。网络中任意一个节点X均有一个相应的条件概率表(Conditional Probability Table,CPT),用来表示节点X在其父节点取各可能值时的条件概率。若节点X无父节点,则X的CPT为其先验概率分布。贝叶斯网络的结构及各节点的CPT定义了网络中各变量的概率分布。

  应用贝叶斯网络分类器进行分类主要分成两个阶段:第一阶段是贝叶斯网络分类器的学习,即从样本数据中构造分类器,包括结构学习和CPT学习;第二阶段是贝叶斯网络分类器的推理,即计算类节点的条件概率,对分类数据进行分类。这两个阶段的时间复杂性均取决于特征值间的依赖程度,甚至可以是NP完全问题,因而在实际应用中,往往需要对贝叶斯网络分类器进行简化。根据对特征值间不同关联程度的假设,可以得出各种贝叶斯分类器。Naive Bayes、TAN、BAN、GBN就是其中比较典型、研究较深入的贝叶斯分类器。

【例13.2】利用贝叶斯分类器进行机器学习

cpp 复制代码
#include <iostream>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace cv::ml;

int main(int, char**)
{
	cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);

	int width = 512, height = 512;
	Mat image = Mat::zeros(height, width, CV_8UC3);  //创建窗口可视化

	// 设置训练数据
	int labels[10] = { 1, -1, 1, 1,-1,1,-1,1,-1,-1 };
	Mat labelsMat(10, 1, CV_32SC1, labels);

	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 trainingDataMat(10, 2, CV_32FC1, trainingData);

	// 创建贝叶斯分类器
	Ptr<NormalBayesClassifier> model = NormalBayesClassifier::create();

	// 设置训练数据
	Ptr<TrainData> tData = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat);

	//训练分类器
	model->train(tData);

	Vec3b green(0, 255, 0), blue(255, 0, 0);
	// 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 data[] = { (float)j, (float)i };
			Mat sampleMat(1, 2, CV_32FC1, data);
			float response = model->predict(sampleMat);  //进行预测,返回1或-1

			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("normal Bayessian classifier Simple Example", image); // show it to the user
	waitKey(0);
	return 0;
}
相关推荐
kyle~1 小时前
ISP--- RAW 图像 从传感器噪声模型到快门时序与频闪效应
人工智能·计算机视觉·接口隔离原则
2601_962099082 小时前
在Python中使用LSTM和PyTorch进行时间序列预测
pytorch·机器学习·lstm·时间序列预测·数据预处理
搞科研的小刘选手2 小时前
【东莞理工学院主办 | 香港举办】第二届计算机视觉和影像计算国际学术会议(CVIC 2026)
计算机视觉·学术会议·影像计算·会议推荐·东莞理工学院
老赵的博客2 小时前
c++ 之观察者模式
c++·qt
坚定学代码3 小时前
static在c++中的使用
开发语言·c++
hansang_IR3 小时前
【题解】P5364 [SNOI2017] 礼物
c++·线性代数·算法
大熊背3 小时前
拜耳模式(Bayer Raw数据)噪声滤波器
图像处理·计算机视觉·脉冲噪声
无小道3 小时前
C/C++——tuple小记
c++·tuple
AI云海3 小时前
计算机视觉之YOLO11整体架构、多任务能力
人工智能·计算机视觉·架构