#include "opencv2/core.hpp" // 包含OpenCV核心功能的文件
#include "opencv2/imgproc.hpp" // 包含OpenCV图像处理功能的文件
#include "opencv2/ml.hpp" // 包含OpenCV机器学习模块的文件
#include "opencv2/highgui.hpp" // 包含OpenCV用户界面组件的文件
#include <stdio.h>
using namespace std; // 使用标准命名空间
using namespace cv; // 使用OpenCV命名空间
using namespace cv::ml; // 使用OpenCV机器学习命名空间
const Scalar WHITE_COLOR = Scalar(255,255,255); // 定义一个常量表示白色
const string winName = "points"; // 窗口名
const int testStep = 5; // 测试步长
Mat img, imgDst; // 定义两个图像矩阵,img为原图,imgDst为显示结果用
RNG rng; // 随机数生成器
vector<Point> trainedPoints; // 存储训练用的点
vector<int> trainedPointsMarkers; // 存储训练点所属的分类标记
const int MAX_CLASSES = 2; // 最大分类数
vector<Vec3b> classColors(MAX_CLASSES); // 存储每个分类的颜色
int currentClass = 0; // 当前选择的分类
vector<int> classCounters(MAX_CLASSES); // 存储每个分类的计数器
// 定义一些预处理器标志以确定哪些分类器将会被使用
#define _NBC_ 1 // 正态贝叶斯分类器
#define _KNN_ 1 // K最近邻分类器
#define _SVM_ 1 // 支持向量机
#define _DT_ 1 // 决策树
#define _BT_ 1 // ADA提升
#define _GBT_ 0 // 梯度提升树
#define _RF_ 1 // 随机森林
#define _ANN_ 1 // 人工神经网络
#define _EM_ 1 // 期望最大化
// 鼠标事件回调函数
static void on_mouse( int event, int x, int y, int /*flags*/, void* )
{
if( img.empty() )
return;
int updateFlag = 0; // 更新标志
if( event == EVENT_LBUTTONUP ) // 当鼠标左键松开时
{
trainedPoints.push_back( Point(x,y) ); // 添加一个点到训练集中
trainedPointsMarkers.push_back( currentClass ); // 为这个点标记当前的类别
classCounters[currentClass]++; // 相应类别的计数器加一
updateFlag = true; // 设置更新标志为真
}
// 绘制
if( updateFlag )
{
img = Scalar::all(0); // 将图像设置为黑色
// 绘制点
for( size_t i = 0; i < trainedPoints.size(); i++ )
{
Vec3b c = classColors[trainedPointsMarkers[i]]; // 获取点的颜色
circle( img, trainedPoints[i], 5, Scalar(c), -1 ); // 在图像上用这个颜色绘制一个圆
}
imshow( winName, img ); // 显示图像
}
}
// 准备训练样本的辅助函数
static Mat prepare_train_samples(const vector<Point>& pts)
{
Mat samples;
Mat(pts).reshape(1, (int)pts.size()).convertTo(samples, CV_32F); // 将Point集合转换为Mat格式并改变数据类型
return samples;
}
// 准备训练数据的辅助函数
static Ptr<TrainData> prepare_train_data()
{
Mat samples = prepare_train_samples(trainedPoints); // 准备样本
return TrainData::create(samples, ROW_SAMPLE, Mat(trainedPointsMarkers)); // 创建训练数据对象
}
// 使用模型进行预测并绘制结果的辅助函数
static void predict_and_paint(const Ptr<StatModel>& model, Mat& dst)
{
Mat testSample( 1, 2, CV_32FC1 ); // 创建一个包含两个float类型数值的测试样本
for( int y = 0; y < img.rows; y += testStep ) // 按步长遍历图像所有的y坐标
{
for( int x = 0; x < img.cols; x += testStep ) // 按步长遍历图像所有的x坐标
{
testSample.at<float>(0) = (float)x; // 设置样本x值
testSample.at<float>(1) = (float)y; // 设置样本y值
int response = (int)model->predict( testSample ); // 使用模型进行预测
dst.at<Vec3b>(y, x) = classColors[response]; // 根据预测结果上色
}
}
}
// 下面的一系列#if预处理器指令是为了在最终的程序中包含或者排除某些分类器的相关代码
#if _NBC_
// 使用正态贝叶斯分类器找出决策边界
static void find_decision_boundary_NBC()
{
// 学习分类器
Ptr<NormalBayesClassifier> normalBayesClassifier = StatModel::train<NormalBayesClassifier>(prepare_train_data());
// 预测并绘制决策边界
predict_and_paint(normalBayesClassifier, imgDst);
}
#endif
#if _KNN_
// 使用K最近邻分类器找出决策边界,并设置K值
static void find_decision_boundary_KNN( int K )
{
// 创建KNN对象
Ptr<KNearest> knn = KNearest::create();
// 设置K值
knn->setDefaultK(K);
// 设置为分类模式
knn->setIsClassifier(true);
// 训练模型
knn->train(prepare_train_data());
// 预测并绘制决策边界
predict_and_paint(knn, imgDst);
}
#endif
#if _SVM_
// 使用支持向量机找出决策边界,并设置错误惩罚参数C
static void find_decision_boundary_SVM( double C )
{
// 创建SVM对象
Ptr<SVM> svm = SVM::create();
// 设置SVM类型为C-SVC
svm->setType(SVM::C_SVC);
// 设置核函数为多项式核
svm->setKernel(SVM::POLY); // 也可以选用线性核SVM::LINEAR;
// 设置多项式核参数degree
svm->setDegree(0.5);
// 设置核函数参数gamma
svm->setGamma(1);
// 设置核函数参数coef0
svm->setCoef0(1);
// 设置SVM参数Nu
svm->setNu(0.5);
// 设置SVM参数P
svm->setP(0);
// 设置训练算法的终止条件
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 1000, 0.01));
// 设置错误惩罚参数C
svm->setC(C);
// 训练模型
svm->train(prepare_train_data());
// 预测并绘制决策边界
predict_and_paint(svm, imgDst);
// 绘制支持向量
Mat sv = svm->getSupportVectors();
for( int i = 0; i < sv.rows; i++ )
{
const float* supportVector = sv.ptr<float>(i);
circle( imgDst, Point(saturate_cast<int>(supportVector[0]),saturate_cast<int>(supportVector[1])), 5, Scalar(255,255,255), -1 );
}
}
#endif
#if _DT_
// 使用决策树找出决策边界
static void find_decision_boundary_DT()
{
// 创建决策树对象
Ptr<DTrees> dtree = DTrees::create();
// 设置决策树最大深度
dtree->setMaxDepth(8);
// 设置节点最小样本数
dtree->setMinSampleCount(2);
// 设置是否使用代理分割
dtree->setUseSurrogates(false);
// 设置交叉验证折数
dtree->setCVFolds(0); // 0表示不进行交叉验证
// 设置是否使用1SE规则
dtree->setUse1SERule(false);
// 设置是否修剪被减枝的决策树
dtree->setTruncatePrunedTree(false);
// 训练模型
dtree->train(prepare_train_data());
// 预测并绘制决策边界
predict_and_paint(dtree, imgDst);
}
#endif
#if _BT_
// 使用提升树算法找出决策边界
static void find_decision_boundary_BT()
{
// 创建提升树对象
Ptr<Boost> boost = Boost::create();
// 设置提升类型
boost->setBoostType(Boost::DISCRETE);
// 设置弱分类器数量
boost->setWeakCount(100);
// 设置权重修剪率
boost->setWeightTrimRate(0.95);
// 设置最大深度
boost->setMaxDepth(2);
// 设置是否使用代理分割
boost->setUseSurrogates(false);
// 设置类别先验概率
boost->setPriors(Mat());
// 训练模型
boost->train(prepare_train_data());
// 预测并绘制决策边界
predict_and_paint(boost, imgDst);
}
#endif
#if _GBT_
// 使用梯度提升树算法找出决策边界
static void find_decision_boundary_GBT()
{
// 设置GBT模型参数
GBTrees::Params params( GBTrees::DEVIANCE_LOSS, // 损失函数类型
100, // 弱分类器数量
0.1f, // 收缩率
1.0f, // 子采样比例
2, // 最大深度
false // 是否使用代理分割
);
// 创建梯度提升树对象
Ptr<GBTrees> gbtrees = StatModel::train<GBTrees>(prepare_train_data(), params);
// 预测并绘制决策边界
predict_and_paint(gbtrees, imgDst);
}
#endif
#if _RF_
// 使用随机森林算法找出决策边界
static void find_decision_boundary_RF()
{
// 创建随机森林对象
Ptr<RTrees> rtrees = RTrees::create();
// 设置最大深度
rtrees->setMaxDepth(4);
// 设置节点最小样本数
rtrees->setMinSampleCount(2);
// 设置回归精度
rtrees->setRegressionAccuracy(0.f);
// 设置是否使用代理分割
rtrees->setUseSurrogates(false);
// 设置类别数量
rtrees->setMaxCategories(16);
// 设置类别先验概率
rtrees->setPriors(Mat());
// 设置是否计算变量重要性
rtrees->setCalculateVarImportance(false);
// 设置活跃变量数量
rtrees->setActiveVarCount(1);
// 设置训练算法的终止条件
rtrees->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 5, 0));
// 训练模型
rtrees->train(prepare_train_data());
// 预测并绘制决策边界
predict_and_paint(rtrees, imgDst);
}
#endif
#if _ANN_
// 使用人工神经网络找出决策边界
static void find_decision_boundary_ANN( const Mat& layer_sizes )
{
// 创建类别标签矩阵
Mat trainClasses = Mat::zeros( (int)trainedPoints.size(), (int)classColors.size(), CV_32FC1 );
for( int i = 0; i < trainClasses.rows; i++ )
{
trainClasses.at<float>(i, trainedPointsMarkers[i]) = 1.f; // 对应类别置为1
}
// 准备训练样本
Mat samples = prepare_train_samples(trainedPoints);
// 创建训练数据对象
Ptr<TrainData> tdata = TrainData::create(samples, ROW_SAMPLE, trainClasses);
// 创建人工神经网络对象
Ptr<ANN_MLP> ann = ANN_MLP::create();
// 设置网络层大小
ann->setLayerSizes(layer_sizes);
// 设置激活函数
ann->setActivationFunction(ANN_MLP::SIGMOID_SYM, 1, 1);
// 设置训练算法的终止条件
ann->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 300, FLT_EPSILON));
// 设置训练方法,这里使用反向传播算法
ann->setTrainMethod(ANN_MLP::BACKPROP, 0.001);
// 训练模型
ann->train(tdata);
// 预测并绘制决策边界
predict_and_paint(ann, imgDst);
}
#endif
#if _EM_
// 使用期望最大化算法找出决策边界
static void find_decision_boundary_EM()
{
img.copyTo(imgDst); // 将当前图像复制给目标图像
Mat samples = prepare_train_samples(trainedPoints); // 准备训练样本
int i, j, nmodels = (int)classColors.size(); // 分类数量
vector<Ptr<EM>> em_models(nmodels); // 存储每个类别对应的EM模型
Mat modelSamples;
// 遍历所有分类来训练EM模型
for (i = 0; i < nmodels; i++)
{
const int componentCount = 3; // 每个模型的分量数
modelSamples.release(); // 释放模型样本空间
// 提取当前类别的所有样本
for (j = 0; j < samples.rows; j++)
{
if (trainedPointsMarkers[j] == i)
modelSamples.push_back(samples.row(j));
}
// 训练模型
if (!modelSamples.empty()) // 如果当前类别有样本
{
Ptr<EM> em = EM::create(); // 创建EM模型
em->setClustersNumber(componentCount); // 设置聚类数量
em->setCovarianceMatrixType(EM::COV_MAT_DIAGONAL); // 设置协方差类型为对角线
em->trainEM(modelSamples, noArray(), noArray(), noArray()); // 训练EM模型
em_models[i] = em; // 存储训练好的模型
}
}
// 使用贝叶斯分类器对坐标平面的点进行分类
// y(x) = arg max_i=1_modelsCount likelihoods_i(x)
Mat testSample(1, 2, CV_32FC1); // 测试样本
Mat logLikelihoods(1, nmodels, CV_64FC1, Scalar(-DBL_MAX)); // 存储每个样本的概率
// 遍历图像上的每个点
for (int y = 0; y < img.rows; y += testStep)
{
for (int x = 0; x < img.cols; x += testStep)
{
testSample.at<float>(0) = (float)x;
testSample.at<float>(1) = (float)y;
for (i = 0; i < nmodels; i++)
{
if (!em_models[i].empty()) // 如果EM模型有效
// 计算测试样本的对数似然概率
logLikelihoods.at<double>(i) = em_models[i]->predict2(testSample, noArray())[0];
}
Point maxLoc;
// 找到概率最大的类别
minMaxLoc(logLikelihoods, 0, 0, 0, &maxLoc);
// 将图像对应位置染成最大概率类别的颜色
imgDst.at<Vec3b>(y, x) = classColors[maxLoc.x];
}
}
}
#endif
int main()
{
cout << "Use:" << endl
<< " key '0' .. '1' - switch to class #n" << endl
<< " left mouse button - to add new point;" << endl
<< " key 'r' - to run the ML model;" << endl
<< " key 'i' - to init (clear) the data." << endl << endl;
cv::namedWindow("points", 1); // 创建一个名为"points"的窗口
img.create(480, 640, CV_8UC3); // 创建一个大小为480x640的图像
imgDst.create(480, 640, CV_8UC3); // 创建一个用于显示结果的图像
imshow("points", img); // 显示原始图像
setMouseCallback("points", on_mouse); // 设置鼠标回调函数,response to mouse events
// 初始化两个分类的颜色,第一个分类颜色为绿色,第二个分类颜色为红色
classColors[0] = Vec3b(0, 255, 0);
classColors[1] = Vec3b(0, 0, 255);
// 主循环
for (;;)
{
char key = (char)waitKey(); // 等待按键
if (key == 27) break; // 如果按键是ESC,退出程序
if (key == 'i') // 初始化
{
img = Scalar::all(0); // 清除图像
trainedPoints.clear(); // 清除训练点
trainedPointsMarkers.clear(); // 清除训练点标记
classCounters.assign(MAX_CLASSES, 0); // 重置类别计数器
imshow(winName, img); // 显示清除后的图像
}
if (key == '0' || key == '1') // 切换类别
{
currentClass = key - '0'; // 根据按键设置当前类别
}
if (key == 'r') // 运行机器学习模型
{
double minVal = 0;
minMaxLoc(classCounters, &minVal, 0, 0, 0); // 检查每个类别至少有一个点
if (minVal == 0)
{
printf("each class should have at least 1 point\n"); // 否则提示并继续循环
continue;
}
img.copyTo(imgDst); // 将图像复制到目标图像上
#if _NBC_
find_decision_boundary_NBC(); // 执行NBC算法找决策边界
imshow("NormalBayesClassifier", imgDst); // 显示NBC的结果
#endif
#if _KNN_
// 使用K-最近邻算法找出决策边界,这里展示了两种不同的K值
find_decision_boundary_KNN(3); // 使用k值为3来找到决策边界
imshow("kNN", imgDst); // 显示k值为3的结果
find_decision_boundary_KNN(15); // 使用k值为15来找到决策边界
imshow("kNN2", imgDst); // 显示k值为15的结果
#endif
#if _SVM_
// 使用支持向量机算法找出决策边界
find_decision_boundary_SVM(1); // 使用C值为1来找到决策边界
imshow("classificationSVM1", imgDst); // 显示C值为1的结果
find_decision_boundary_SVM(10); // 使用C值为10来找到决策边界
imshow("classificationSVM2", imgDst); // 显示C值为10的结果
#endif
#if _DT_
// 使用决策树算法找出决策边界
find_decision_boundary_DT(); // 执行决策树算法找到决策边界
imshow("DT", imgDst); // 显示决策树的结果
#endif
#if _BT_
// 使用提升树算法找出决策边界
find_decision_boundary_BT(); // 执行提升树算法找到决策边界
imshow("BT", imgDst); // 显示提升树的结果
#endif
#if _GBT_
// 使用梯度提升树算法找出决策边界
find_decision_boundary_GBT(); // 执行梯度提升树算法找到决策边界
imshow("GBT", imgDst); // 显示梯度提升树的结果
#endif
#if _RF_
// 使用随机森林算法找出决策边界
find_decision_boundary_RF(); // 执行随机森林算法找到决策边界
imshow("RF", imgDst); // 显示随机森林的结果
#endif
#if _ANN_
// 使用人工神经网络算法找出决策边界
Mat layer_sizes1(1, 3, CV_32SC1); // 设置神经网络的层数和每层的节点数
layer_sizes1.at<int>(0) = 2; // 输入层节点数
layer_sizes1.at<int>(1) = 5; // 隐藏层节点数
layer_sizes1.at<int>(2) = (int)classColors.size(); // 输出层节点数,即类别数
find_decision_boundary_ANN(layer_sizes1); // 执行人工神经网络算法找到决策边界
imshow("ANN", imgDst); // 显示人工神经网络的结果
#endif
#if _EM_
// 执行EM算法找决策边界
find_decision_boundary_EM();
// 显示EM算法的结果
imshow("EM", imgDst);
#endif
}
}
return 0; // 程序结束
}
这段代码实现了一个简单的机器视觉和机器学习应用程序。其主要功能是在图形用户界面中用鼠标标记出不同的类别点,然后通过多种机器学习模型(如正态贝叶斯分类器、K最近邻分类器、支持向量机等),学习这些点的分布规律,并预测整个图像坐标面中每个点的类别,并进行着色以区分不同类别。
代码的核心部分包括鼠标回调函数(on_mouse
),它处理用户的鼠标事件,根据用户点击添加新的类别点;数据准备辅助函数,用于将标记的点转换为训练模型所需的格式;模型训练和预测函数,包括每个分类器独立的训练和预测逻辑;以及主函数,它负责初始化应用程序界面、处理用户输入并触发机器学习模型的训练和预测。
这段代码展示了多个不同的机器学习算法在机器视觉和机器人技术应用程序中确定决策边界的方法。每个#if块介绍了如何使用一个特定的算法来训练一个模型用于分类,并预测图像上每个像素点的类别,并用不同的颜色来表示不同类别的决策区域。具体来说,包括:
使用正态贝叶斯分类器(NBC)确定决策边界。
使用K最邻近算法(KNN)确定决策边界,K值可以调整。
使用支持向量机(SVM)确定决策边界,其中C为错误项的惩罚系数。
使用决策树(DT)来确定决策边界。
使用提升树(Boosted Trees, BT)来确定决策边界。
使用梯度提升树(Gradient Boosted Trees, GBT)来确定决策边界。
使用随机森林(Random Forest, RF)来确定决策边界。
使用人工神经网络(Artificial Neural Network, ANN)来确定决策边界,其中层的大小可以调整,通常由输入层、一个或多个隐藏层和输出层构成。
9. 期望最大化(Expectation Maximization, EM)算法在寻找决策边界的实现方式。EM是一种迭代优化算法,用于参数估计,特别适合处理有隐变量的概率模型。它通常用于聚类或者潜在变量模型的估计。在find_decision_boundary_EM
函数中,这个算法先是为每个类别训练了一个EM模型。接着通过遍历图像上的每个像素,计算该像素属于每个模型的概率,然后选择概率最大的模型对应的类别,为该像素分配颜色。
这些算法通常用于机器视觉中的模式识别、分类任务或者机器人路径规划中的决策制定等应用场景。