OpenCV联合C++/Qt 学习笔记(二十五)----监督学习聚类及K均值聚类

一、监督学习聚类

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均值聚类的步骤:

  1. 指定将数据聚类成k类,并随机生成k个中心点
  2. 遍历所有数据,根据数据与中心的位置关系将每个数据归类到不同的中心里
  3. 计算每个聚类的平均值,并将均值作为新的中心点
  4. 重复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();
相关推荐
玖釉-3 小时前
C++ 中的矩阵介绍:以二维矩阵查找为例
c++·windows·算法·矩阵
red_redemption3 小时前
自由学习记录(191)
学习
j_xxx404_3 小时前
Linux线程:从内存分页机制(Page Table/TLB/Page Fault)彻底读懂 Linux 线程本质
linux·运维·服务器·开发语言·c++·人工智能·ai
小新同学^O^3 小时前
OpenClaw 数据采集工具新手入门指南
python·学习·openclaw·纯ai文
2301_789015623 小时前
C++_string增删查改模拟实现
java·开发语言·c++
lzp07913 小时前
基于多模态视觉模型和图文向量模型的工业图像知识库研究与应用(伍)
数据库·学习·neo4j
学习,学习,在学习3 小时前
Qt 串口通讯架构
开发语言·c++·qt·架构·qt5
郝学胜-神的一滴3 小时前
干货版《算法导论》05:从集合接口到排序
开发语言·数据结构·c++·程序人生·算法·排序
05候补工程师3 小时前
【考研线代】矩阵相似与对角化核心解题套路与防坑指南 (附实战笔记)
经验分享·笔记·线性代数·考研·矩阵