一、监督学习聚类
1、不同聚类方法原理介绍
1.1.1 K紧邻原理介绍
对于一个未知样本,统计距离它最近的 K 个已知样本,根据这些邻居的类别来决定当前样本属于哪一类。

1.1.2 支持向量机原理
寻找一个"最优超平面",将不同类别的样本最大间隔地分开。
即:
- 同类样本尽量聚集
- 不同类样本尽量远离
- 分类边界距离最近样本最远

2、StatModel模块介绍
1.2.1 ml模块介绍
cpp
/* 返回值:bool类型,true表示训练成功,false表示训练失败
用途:用于使用训练数据对机器学习模型进行训练 */
virtual bool cv::ml::StatModel::train(const Ptr<TrainData>& data, int flags=0);
/*
data:训练时使用的数据,类型为Ptr<TrainData>,包含:训练样本数据、样本标签、数据划分信息
flags:构建建模方法标志,例如UPDATE_MODEL表示对使用新数据对模型进行更新
*/
cpp
/* 返回值:float类型,返回第一个样本的预测结果
用途:用于使用已经训练完成的机器学习模型进行预测 */
virtual float cv::ml::StatModel::predict( InputArray samples,
OutputArray results=noArray(),
int flags=0 ) const = 0;
/*
samples:输入数据矩阵,矩阵数据类型必须时CV_32
results:对输入数据预测结果的输出矩阵
flags:预测方式控制标志,不同模型支持不同参数,常见参数包括:
StatModel::RAW_OUTPUT:输出原始预测值而不是分类结果
*/
cpp
/* 返回值:Ptr<TrainData>类型,返回训练数据对象
用途:用于创建OpenCV机器学习模块中的训练数据对象。
该函数会将:样本数据、标签数据、特征信息、样本信息封装成统一的TrainData对象,
供机器学习模型训练使用 */
static Ptr<TrainData> cv::ml::TrainData::create(InputArray samples, int layout,
InputArray responses,
InputArray varIdx=noArray(),
InputArray sampleIdx=noArray(),
InputArray sampleWeights=noArray(),
InputArray varType=noArray());
/*
samples:样本数据矩阵,每一行或每一列表示一个样本,数据类型必须是CV_32F
layout:样本数据排列方式,常用参数包括:
ROW_SAMPLE:每一行表示一个样本
COL_SAMPLE:每一列表示一个样本
responses:标签矩阵,如果是标量,存储为单行或者单列的矩阵,矩阵数据类型为CV_32F或CV_32S
varIdx:参与训练的变量索引,用于指定使用哪些特征,默认表示使用全部特征,数据类型为CV_32S
sampleIdx:参与训练的样本索引,用于指定使用哪些样本,默认表示使用全部样本,数据类型为CV_32S
sampleWeights:样本权重,用于设置不同样本的重要程度,数据类型为CV_32F
varType:变量类型,用于指定每个变量:
1、数值型
2、类别型
3、输出变量
*/
1.2.2 K紧邻类(KNearest)
cpp
static Ptr<KNearest> cv::ml::KNearest::create();
cpp
/* 返回值:float类型,返回第一个输入样本的预测结果
用途:用于使用KNN(K-Nearest Neighbor)最近邻算法进行分类或回归预测。
该函数会:
1、计算待测样本与训练样本之间距离
2、寻找距离最近的K个样本
3、根据K个邻居进行投票或平均
4、输出最终预测结果 */
virtual float cv::ml::KNearest::findNearest( InputArray samples, int k,
OutputArray results,
OutputArray neighborResponses=noArray(),
OutputArray dist=noArray() ) const = 0;
/*
samples:待根据K紧邻算法预测的数据,数据需要行排列且数据类型为CV_32F
k:最近K近邻的数目
results:每个新数据的预测结果,数据类型为CV_32F
neighborResponses:可以选择输出的每个数据最近邻的k个样本
dist:可以选择输出的与k个最近邻样本的距离
*/
3、示例代码
cpp
/****************************KNN 手写数字识别训练*****************/
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat img = imread(s_imgPath + "/digits.png");
if (img.empty())
{
qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
return;
}
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
/*分割为5000个cells*/
Mat images = Mat::zeros(5000, 400, CV_8UC1);
Mat labels = Mat::zeros(5000, 1, CV_8UC1);
int index = 0;
Rect numberImg;
numberImg.x = 0;
numberImg.height = 1;
numberImg.width = 400;
for (int row = 0; row < 50; row++)
{
/*从图像中分割处20x20的图像作为独立数字图像*/
int label = row / 5;
int datay = row * 20;
for (int col = 0; col < 100; col++)
{
int datax = col * 20;
Mat number = Mat::zeros(Size(20, 20), CV_8UC1);
for (int x = 0; x < 20; x++)
{
for (int y = 0; y < 20; y++)
{
number.at<uchar>(x, y) = gray.at<uchar>(x + datay, y + datax);
}
}
/*将二维图像数据转成行数据*/
Mat row = number.reshape(1, 1);
cout << index + 1 << " data" << endl;
numberImg.y = index;
/*添加到总数据中*/
row.copyTo(images(numberImg));
/*记录每个图像对应的数字标签*/
labels.at<uchar>(index, 0) = label;
index++;
}
}
imwrite(s_imgPath + "/AllDataRowArrange.png", images);
imwrite(s_imgPath + "/label.png", labels);
/*加载训练数据集*/
images.convertTo(images, CV_32FC1);
labels.convertTo(labels, CV_32FC1);
Ptr<ml::TrainData> tdata = ml::TrainData::create(images, ml::ROW_SAMPLE, labels);
/*创建K近邻类*/
Ptr<ml::KNearest> knn = ml::KNearest::create();
knn->setDefaultK(5);/*每个类别拿出5个数据*/
knn->setIsClassifier(true);/*进行分类*/
/*训练数据*/
knn->train(tdata);
/*保存训练结果*/
knn->save(s_imgPath + "/knn_model.yml");
cout << "KNearest Train Finished And Saved" << endl;
cpp
/****************************加载已经训练好的 KNN 手写数字识别模型,
验证模型准确率,并测试新的手写数字图像能否被正确识别*****************/
/*加载KNN分类器*/
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat data = imread(s_imgPath + "/AllDataRowArrange.png");
Mat labels = imread(s_imgPath + "/label.png");
if (data.empty() || labels.empty())
{
qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
return;
}
data.convertTo(data, CV_32FC1);
labels.convertTo(labels, CV_32SC1);
Ptr<ml::KNearest> knn = Algorithm::load<ml::KNearest>(s_imgPath + "/knn_model.yml");
/*查看分类结果*/
Mat result;
knn->findNearest(data, 5, result);
/*统计分类结果与真实结果相同的数目*/
int count = 0;
for (int row = 0; row < result.rows; row++)
{
int predict = result.at<float>(row, 0);
if (labels.at<int>(row, 0) == predict)
{
count = count + 1;
}
}
float rate = 1.0 * count / result.rows;
cout << "rate: " << rate << endl;
/*测试新图像是否能够识别数字*/
Mat testImg1 = imread(s_imgPath + "/handWrite01.png", IMREAD_GRAYSCALE);
Mat testImg2 = imread(s_imgPath + "/handWrite02.png", IMREAD_GRAYSCALE);
imshow("testImg1", testImg1);
imshow("testImg2", testImg2);
/*缩放到20x20的尺寸*/
cv::resize(testImg1, testImg1, Size(20, 20));
cv::resize(testImg2, testImg2, Size(20, 20));
Mat testdata = Mat::zeros(2, 400, CV_8UC1);
Rect rect;
rect.x = 0;
rect.y = 0;
rect.height = 1;
rect.width = 400;
Mat oneData = testImg1.reshape(1, 1);
Mat twoData = testImg2.reshape(1, 1);
oneData.copyTo(testdata(rect));
rect.y = 1;
twoData.copyTo(testdata(rect));
/*数据类型转换*/
testdata.convertTo(testdata, CV_32F);
/*进行估计识别*/
Mat result2;
knn->findNearest(testdata, 5, result2);
/*查看预测的结果*/
for (int i = 0; i < result2.rows; i++)
{
int predict = result2.at<float>(i, 0);
cout << i + 1 << "Forecast results: " << predict
<< "The actual result: " << i + 1 << endl;
}
waitKey(0);
destroyAllWindows();
cpp
/*******************************使用 SVM(支持向量机)对二维坐标点进行分类训练,
并可视化分类结果与决策区域****************/
/*训练数据*/
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat samples, labls;
FileStorage fread(s_imgPath + "/point.yml", FileStorage::READ);
fread["data"] >> samples;
fread["labls"] >> labls;
fread.release();
/*不同种类坐标点拥有不同的颜色*/
vector<Vec3b> colors;
colors.push_back(Vec3b(0, 255, 0));
colors.push_back(Vec3b(0, 0, 255));
/*创建空白图像用于显示坐标点*/
Mat img(480, 640, CV_8UC3, Scalar(255, 255, 255));
Mat img2;
img.copyTo(img2);
/*在空白图像中绘制坐标点*/
for (int i = 0; i < samples.rows; i++)
{
Point2f point;
point.x = samples.at<float>(i, 0);
point.y = samples.at<float>(i, 1);
Scalar color = colors[labls.at<int>(i, 0)];
circle(img, point, 3, color, -1);
circle(img2, point, 3, color, -1);
}
imshow("img", img);
/*建立模型*/
Ptr<ml::SVM> model = ml::SVM::create();
/*参数设置*/
model->setKernel(ml::SVM::INTER);/*内核的模型*/
model->setType(ml::SVM::C_SVC);/*SVM的类型*/
model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 100, 0.01));
//model->setGamma(5.383);
//model->setC(0.01);
//model->setDegree(3);
/*训练模型*/
model->train(ml::TrainData::create(samples, ml::ROW_SAMPLE, labls));
/*用模型对图像中全部像素点进行分类*/
Mat imagePoint(1, 2, CV_32FC1);
for (int y = 0; y < img2.rows; y = y + 2)
{
for (int x = 0; x < img2.cols; x = x + 2)
{
imagePoint.at<float>(0) = (float)x;
imagePoint.at<float>(1) = (float)y;
int color = (int)model->predict(imagePoint);
img2.at<Vec3b>(y, x) = colors[color];
}
}
imshow("img2", img2);
waitKey(0);
destroyAllWindows();
二、K均值聚类
1、K均值聚类方法
K均值聚类的步骤:
- 指定将数据聚类成k类,并随机生成k个中心点
- 遍历所有数据,根据数据与中心的位置关系将每个数据归类到不同的中心里
- 计算每个聚类的平均值,并将均值作为新的中心点
- 重复Step2和Step3,直到每个聚类中心点的坐标收敛,输出聚类结果
2、相关函数介绍
cpp
/* 返回值:double类型,返回所有样本到其所属聚类中心的距离平方和(紧致度Compactness),
数值越小聚类效果通常越好
用途:用于对数据进行K均值聚类(K-Means Clustering)
该算法属于:无监督学习算法
它会自动将数据划分为K个类别,使同一类别中的数据尽量相似,不同类别之间尽量差异较大 */
double cv::kmeans( InputArray data, int K, InputOutputArray bestLabels,
TermCriteria criteria, int attempts,
int flags, OutputArray centers = noArray() );
/*
data:需要聚类的输入数据,每一行表示一个样本,数据类型通常为CV_32F
K:聚类中心(类别)数量,即最终划分的类别数
bestLabels:输出每个样本所属类别标签,标签范围为:0 ~ K-1
criteria:迭代终止条件,用于控制:
1、最大迭代次数
2、聚类中心误差精度
attempts:重复执行聚类的次数,函数会返回误差最小的结果
flags:聚类中心初始化方式标志,常用参数包括:
KMEANS_RANDOM_CENTERS:随机初始化聚类中心
KMEANS_PP_CENTERS:KMeans++初始化方式,通常效果更稳定
KMEANS_USE_INITIAL_LABELS:使用输入标签作为初始分类
centers:输出聚类中心坐标,每一行对应一个聚类中心
*/
3、示例代码
cpp
Mat img(500, 500, CV_8UC3, Scalar(255, 255, 255));
RNG rng(10000);
/*设置三种颜色*/
Scalar colorLut[3] =
{
Scalar(0,0,255),
Scalar(0,255,0),
Scalar(255,0,0),
};
/*设置三个点集,并且每个点集中点的数目随机*/
int number = 3;
int Points1 = rng.uniform(20, 200);
int Points2 = rng.uniform(20, 200);
int Points3 = rng.uniform(20, 200);
int Points_num = Points1 + Points2 + Points3;
Mat Points(Points_num, 1, CV_32FC2);
int i = 0;
for (; i < Points1; i++)
{
Point2f pts;
pts.x = rng.uniform(100, 200);
pts.y = rng.uniform(100, 200);
Points.at<Point2f>(i, 0) = pts;
}
for (; i < Points1 + Points2; i++)
{
Point2f pts;
pts.x = rng.uniform(300, 400);
pts.y = rng.uniform(100, 300);
Points.at<Point2f>(i, 0) = pts;
}
for (; i < Points1 + Points2 + Points3; i++)
{
Point2f pts;
pts.x = rng.uniform(100, 200);
pts.y = rng.uniform(390, 490);
Points.at<Point2f>(i, 0) = pts;
}
/*使用KMeans*/
Mat labels;/*每个点所属的种类*/
Mat centers;/*每类点的中心位置坐标*/
kmeans(Points, number, labels, TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1), 3, KMEANS_PP_CENTERS, centers);
/*根据分类为每个点设置不同的颜色*/
img = Scalar::all(255);
for (int i = 0; i < Points_num; i++)
{
int index = labels.at<int>(i);
Point point = Points.at<Point2f>(i);
circle(img, point, 2, colorLut[index], -1, 4);
}
/*绘制每个聚类的中心来绘制圆*/
for (int i = 0; i < centers.rows; i++)
{
int x = centers.at<float>(i, 0);
int y = centers.at<float>(i, 1);
cout << i + 1 << " center: " << x << "," << y << endl;
circle(img, Point(x, y), 50, colorLut[i], 1, LINE_AA);
}
imshow("img", img);
waitKey(0);
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat img2 = imread(s_imgPath + "/lena.jpg");
if (img2.empty())
{
qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
return;
}
Vec3b colorLut2[5] = {
Vec3b(0, 0, 255),
Vec3b(0, 255, 0),
Vec3b(255, 0, 0),
Vec3b(0, 255, 255),
Vec3b(255, 0, 255)
};
/*图像的尺寸,用于计算图像中像素点的数目*/
int width = img2.cols;
int height = img2.rows;
/*初始化定义*/
int sampleCount = width * height;
/*将图像矩阵数据转换成每行一个数据的形式*/
Mat sample_data = img2.reshape(3, sampleCount);
Mat data;
sample_data.convertTo(data, CV_32F);
/*KMean函数将像素进行分类*/
int number2 = 3;/*分割后的颜色种类*/
Mat labels2;
TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1);
kmeans(data, number2, labels2, criteria, number2, KMEANS_PP_CENTERS);
/*显示图像分割结果*/
Mat result = Mat::zeros(img2.size(), img2.type());
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
int index = row * width + col;
int label = labels2.at<int>(index, 0);
result.at<Vec3b>(row, col) = colorLut2[label];
}
}
namedWindow("img2", WINDOW_NORMAL);
imshow("img2", img2);
namedWindow("result", WINDOW_NORMAL);
imshow("result", result);
waitKey(0);
destroyAllWindows();