[opencv][ml][cpp] 学习手册3:K-Means 聚类算法

[opencv][ml][cpp] 学习手册3:K-Means 聚类算法

24_kmeans聚类算法.cpp



注:部分概念截取自教程,侵删!


1. kmeans聚类.cpp

1. 概念

K-均值聚类算法

  • 著名的划分聚类分割方法。

  • 基本思想:

    • 给定一个有N个元组或者纪录的数据集,
    • 分裂法将构造K个分组,每一个分组就代表一个聚类,K<N。
    • 而且这K个分组满足下列条件:
      1. 每一个分组至少包含一个数据纪录
      2. 每一个数据纪录属于且仅属于一个分组
  • 对于给定的K,算法首先给出一个初始的分组方法,以后通过反复迭代的方法改变分组,
    使得每一次改进之后的分组方案都较前一次好

  • 好的标准就是:同一分组中的记录越近越好,而不同分组中的纪录越远越好。

原理

  1. 首先随机从数据集中选取 K个点作为初始聚类中心
  2. 然后计算各个样本到聚类中心的距离,把样本归到离它最近的那个聚类中心所在的类。
  3. 计算新形成的每一个聚类的数据对象的平均值来得到新的聚类中心,
  4. 如果相邻两次的聚类中心没有任何变化,说明样本调整结束,聚类准则函数 已经收敛。
  • 算法特点:

    1. 每次迭代中都要考察每个样本的分类是否正确。若不正确,就要调整,在全部样本调整完后,再修改聚类中心,进入下一次迭代。
    2. 这个过程将不断重复直到满足某个终止条件。
  • 终止条件:

    1. 没有对象被重新分配给不同的聚类
    2. 聚类中心不再发生变化
    3. 误差平方和局部最小

一般步骤

  1. 从 n个数据对象任意选择 k 个对象作为初始聚类中心;
  2. 循环(3)到(4)直到每个聚类不再发生变化为止;
  3. 根据每个聚类对象的均值(中心对象),计算每个对象与这些中心对象的距离;
    并根据最小距离重新对相应对象进行划分;
  4. 重新计算每个(有变化)聚类的均值(中心对象),直到聚类中心不再变化。

2. 熟悉 cv::kmeans()

需求
使用 cv kmeans api 对一幅色块图像进行聚类(4类),并对每一个群集打上标签。

cv函数
在这里插入图片描述
在这里插入图片描述

终止条件:

  1. COUNT: 达到最大迭代数,或最大计算数
  2. MAX_ITER:达到最大迭代数
  3. EPS:达到需要的精度,或迭代算法终止时的参数的改变

在这里插入图片描述

终止条件构造函数:

  1. type:终止条件类型,见上
  2. maxCount:最大迭代数,或最大计算数
  3. epsilon:精度

在这里插入图片描述

kmeans 标记

  1. 每一次尝试选择随机中心
  2. 看paper
  3. 第一次尝试使用给定标签,而不是使用计算出的初始中心。之后的尝试,使用随机或半随机中心。

3. 代码实现:

/*
 * created by jacob 01/04/21
 */


#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/utils/logger.hpp>


using namespace std;
namespace cvlog = cv::utils::logging;


int main(int argc, char **argv) {

    cvlog::setLogLevel(cvlog::LOG_LEVEL_INFO);

    string filename = "../img/color_block_2.jpg";
    cv::Mat src = cv::imread(filename, cv::IMREAD_COLOR);
    cv::resize(src, src, cv::Size(src.cols / 4, src.rows / 4));
    cv::imshow("src", src);

    CV_LOG_INFO(NULL, "src.size()" << src.size())

    // 用于存放样本数据
    cv::Mat sampleData(src.rows * src.cols, 1, CV_32FC3);

    // 将原图像行列数据 压扁
    for (int row = 0; row < src.rows; ++row) {
        for (int col = 0; col < src.cols; ++col) {
            cv::Vec3b color = src.at<cv::Vec3b>(row, col);
            int index = row * src.cols + col;
            // 将颜色值存放到data
            sampleData.at<cv::Vec3f>(index) = color;
        }
    }

    // 指定聚类算法 终止条件
    cv::TermCriteria termCriteria(cv::TermCriteria::EPS | cv::TermCriteria::COUNT, 14, 0.1);
    cv::Mat labels;
    int K = 4;
    cv::kmeans(sampleData, K, labels, termCriteria, 3, cv::KMEANS_RANDOM_CENTERS);
    CV_LOG_INFO(NULL, "labels.type(), size(): " << labels.type() << labels.size())
    CV_LOG_INFO(NULL, "labels.type(): \n" << labels.reshape(src.rows))

    // 给每一类标记分配相同颜色
    cv::RNG rng(1234132);
    vector<cv::Vec3b> colors = {
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
    };

    cv::Mat kmeansResult(src.size(), CV_8UC3);
    for (int row = 0; row < src.rows; ++row) {
        for (int col = 0; col < src.cols; ++col) {
            // 获取当前位置的label 信息  labels [0,1,23,456]  src[row,col]
            int index = row * src.cols + col;
            // 获取对应的label标记
            int label = labels.at<int>(index);
            // 绘制输出图像
            kmeansResult.at<cv::Vec3b>(row, col) = colors[label];
        }
    }
    cv::imshow("result", kmeansResult);

    // pending
    cv::waitKey(0);

    return 0;
}

运行结果
在这里插入图片描述

原始图像为6色块图,结果图像为聚类后的4色块图

解释说明(代码)

  1. 读取 src,对 src 进行缩放(保持三通道不变)
  2. 创建一个样本矩阵 sampleData(三通道),长度为 src 的 rows * cols,类型为 CV_32FC3
  3. 使用 for-for 将 src 每个像素点的值(cv::Vec3b)存放进 sampleData
    二维索引转一维索引:index = row * cols + col
     int index = row * src.cols + col;
    
  4. 进行聚类运算
    1. 定义终止条件
       cv::TermCriteria termCriteria(cv::TermCriteria::EPS | cv::TermCriteria::COUNT, 14, 0.1);
      

      参数解释见 cv函数

    2. 调用 cv::kmeans api 进行聚类计算
      cv::kmeans(sampleData, K, labels, termCriteria, 3, cv::KMEANS_RANDOM_CENTERS);
      

      K:聚类数
      labels:标签矩阵
      attempts:算法执行次数
      flags:见cv函数

  5. 创建三通道结果矩阵 kmeansResult 用来存放聚类后的矩阵
    cv::RNG rng(1234132);
    vector<cv::Vec3b> colors = {
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
    };
    
    cv::Mat kmeansResult(src.size(), CV_8UC3);
    

2. 附加功能实现(更改背景颜色、抠图)

1. 代码实现

代码片段

{
	// 上接
		
    // 制作二值图掩膜,用来更改背景颜色
    cv::Vec3b backgroundColor = kmeansResult.at<cv::Vec3b>(10,10);
    cv::Mat binary(src.size(),CV_8UC1);
    for (int row = 0; row < src.rows; ++row) {
        for (int col = 0; col < src.cols; ++col) {
            cv::Vec3b color = kmeansResult.at<cv::Vec3b>(row,col);
            if(color == backgroundColor){
                binary.at<uchar>(row,col) = 0;
            }else{
                binary.at<uchar>(row,col) = 255;
            }
        }
    }
    imshow("binary",binary);


    // 使用掩膜、蒙版 抠出彩图
    cv::Mat copyResult;
    src.copyTo(copyResult,binary);
    imshow("copy",copyResult);

    for (int row = 0; row < copyResult.rows; ++row) {
        for (int col = 0; col < copyResult.cols; ++col) {
            cv::Vec3b color = copyResult.at<cv::Vec3b>(row,col);
            if(color == cv::Vec3b(0,0,0)){
                copyResult.at<cv::Vec3b>(row,col)=colors[1];
            }
        }
    }
    imshow("result1",copyResult);

	// 下接
}

运行结果
在这里插入图片描述

解释说明(代码)

  1. 在制作二值图掩膜(mask)代码块中,使用 cv::Vect3b 类型接收三通道像素值
  2. 使用 copyTo api 对原图像进行带掩膜的拷贝
    src.copyTo(copyResult,binary);	// src: 原图像C3,copyResult:目标图像,binary:掩膜
    

使用 kmeans 方式抠图,可以达到像素级别。


&&_参考


&&_问题解决

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值