opencv图像分割之Kmeans方法(实现聚落与图片分割)

Kmeans方法概述

  • 无监督学习方法
  • 分类问题,输入分类数目,初始化中心位置
  • 硬分类方法,以距离度量
  • 迭代分类为聚类

KMeans 原理介绍

“聚对于"监督学习"(supervisedlea rning),其训练样本是带有标记信息的,并且监督学习的目的是:对带有标记的数据集进行模型学习,从而便于对新的样本进行分类。而在“无监督学习”(unsupervised learning)中,训练样本的标记信息是未知的,目标是通过对无标记训练样本的学习来揭示数据的内在性质及规律,为进一步的数据分析提供基础。对于无监督学习,应用最广的便是"聚类"(clustering)。
  “聚类算法”试图将数据集中的样本划分为若干个通常是不相交的子集,每个子集称为一个“簇”(cluster),通过这样的划分,每个簇可能对应于一些潜在的概念或类别。
我们可以通过下面这个图来理解:

在这里插入图片描述
上图是未做标记的样本集,通过他们的分布,我们很容易对上图中的样本做出以下几种划分。
  当需要将其划分为两个簇时,即 k=2 时:

在这里插入图片描述

当需要将其划分为四个簇时,即 k=4 时:
在这里插入图片描述

 那么计算机是如何进行这样的划分的呢?这就需要聚类算法来进行实现了

大致算法流程原理如下图所示:
在这里插入图片描述在这里插入图片描述


相关函数api

随机填充

void RNG::fill( InputOutputArray mat,
                int distType,
                InputArray a,
                InputArray b,
                bool saturateRange=false )

函数介绍

  • mat:需要填充的矩阵
  • distType:随机数产生的方式,有两种
    • RNG::UNIFORM:产生均一分布的随机数
      • a:表示均匀分布的下限
      • b:表示上限
    • RNG::NORMAL:产生高斯分布的随机数
      • a:表示均值
      • b:表示方程
  • saturateRange:只有当随机数产生方式为均匀分布时才有效,表示的是是否产生的数据要布满整个范围
    需要注意的是用来保存随机数的矩阵mat可以是多维的,也可以是多通道的,目前最多只能支持4个通道。

函数api

void randShuffle(InputOutputArray dst, double iterFactor=1., RNG* rng=0 )

函数介绍

该函数表示随机打乱1D数组dst里面的数据,随机打乱的方式由随机数发生器rng决定。iterFactor为随机打乱数据对数的因子,总共打乱的数据对数为:dst.rowsdst.colsiterFactor,因此如果为0,表示没有打乱数据。


函数api

Class TermCriteria

函数介绍

TermCriteria 一般表示迭代终止的条件,如果为CV_TERMCRIT_ITER,则用最大迭代次数作为终止条件,如果为CV_TERMCRIT_EPS则用精度作为迭代条件,如果为CV_TERMCRIT_ITER+CV_TERMCRIT_EPS则用最大迭代次数或者精度作为迭代条件,看哪个条件先满足。


函数解释
rowRange为指定的行span创建一个新的矩阵头,可取指定行区间元素
colRange为指定的列span创建一个新的矩阵头,可取指定列区间元素
理解:
rowRange(int x, int y) (其中y应小于等于行数,例如一个矩阵最大为5行,那么y最大为4) 的创建矩阵范围为从x行为首行开始,往后数y-x行。
例如:rowRange(0,3) 位 从第0行开始,往后数3行。即 0 、1、2行。
colRange(int x,int y)同理。


函数api

double cv::kmeans 	( 	InputArray  	data,
		int  	K,
		InputOutputArray  	bestLabels,
		TermCriteria  	criteria,
		int  	attempts,
		int  	flags,
		OutputArray  	centers = noArray() 
	) 	

参数介绍

  • data:为cv::Mat类型,每行代表一个样本,即特征点,即mat.cols=特征长度,mat.rows=样本数,数据类型仅支持float;
  • K:指定聚类时划分为几类;
  • bestLabels:为cv::Mat类型,是一个长度为(样本数,1)的矩阵,即mat.cols=1,mat.rows=样本数;为K-Means算法的结果输出,指定每一个样本聚类到哪一个label中;
  • criteria:TermCriteria类,算法进行迭代时终止的条件,可以指定最大迭代次数,也可以指定预期的精度,也可以这两种同时指定;
  • attempts:指定K-Means算法执行的次数,每次算法执行的结果是不一样的,选择最好的那次结果输出;
  • flags:初始化均值点的方法,目前支持三种:KMEANS_RANDOM_CENTERSKMEANS_PP_CENTERSKMEANS_USE_INITIAL_LABELS
  • centers:为cv::Mat类型,输出最终的均值点,mat.cols=特征长度,mat.rols=K.

一般当attempts和TermCriteria中迭代次数值越大时,聚类效果越好。


代码演示(kmeans实现聚类演示)

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace  std;
using namespace cv;

int main(void)
{
    //创建图片
   Mat img(500,500,CV_8UC3);

   //定义随机数产生器
   RNG rng(12345);

   //定义5种颜色  最大分类不超过5
   Scalar color_tab[]={
       Scalar(0,0,255),
       Scalar(0,255,0),
       Scalar(255,0,0),
       Scalar(0,255,255),
       Scalar(255,0,255)
   };
    //定义分类  即函数K值 多少个分类点
    int num_cluster = rng.uniform(2,5);
    printf("num of num_cluster is %d\n",num_cluster);

    //一共产生多少点数供我们测试
    int sample_count = rng.uniform(2,1000);
    printf("num of sample_count is %d\n",sample_count);

    Mat points(sample_count,1,CV_32FC2); //产生的样本数,实际上为2通道的列向量,元素类型为Point2f
    Mat lables;   //聚类标签 每个点归哪个类
    Mat centers;

    //生成随机数
    for(int i=0;i<num_cluster;i++)
    {
        Point center;
        center.x = rng.uniform(0,img.cols);
        center.y = rng.uniform(0,img.rows);

        //获取填充区域 只是获取到区域 没有填充数值
        Mat point_chunk = points.rowRange(i*sample_count/num_cluster,i == num_cluster-1 ? sample_count:
                                          (i+1)*sample_count/num_cluster);
        //向获取到的填充区域随机数填充  高斯分布 满足(均值为Scalar(center.x,center.y) 方程为Scalar(img.cols*0.05,img.rows*0.05))
        rng.fill(point_chunk,RNG::NORMAL,Scalar(center.x,center.y),Scalar(img.cols*0.05,img.rows*0.05));
    }
    //打乱
    randShuffle(points,3,&rng);

    //使用Kmeans
    kmeans(points,num_cluster,lables,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT,10,0.1),
           3,KMEANS_PP_CENTERS,centers);

    //用不同的颜色分类显示
    img = Scalar::all(255);  //全部初始化为0  白色背景

    //给每个点 根据相应分类类不同 进行着色
    for(int i=0;i<sample_count;i++)
    {
        int index = lables.at<int>(i);

        Point p = points.at<Point2f>(i);
        circle(img,p,2,color_tab[index],-1,8);
    }



    //每个聚类绘制中心
    for(int i=0;i<num_cluster;i++)
    {
        int x = centers.at<float>(i,0);
        int y = centers.at<float>(i,1);
        circle(img,Point(x,y),45,color_tab[i],2);
    }
    imshow("kmeans-data",img);
    waitKey(0);
    destroyAllWindows();
    return 0;
}



效果

在这里插入图片描述


代码演示(kmeans实现图片分割)


#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace  std;
using namespace cv;

#define PIC_PATH "/work/opencv_pic/"
#define PIC_NAME "kmeans.jpeg"

int main(void)
{
    Mat src;

    //获取完整的图片路径及名称
    string pic = string(PIC_PATH)+string(PIC_NAME);

    //打印图片路径
    cout << "pic path is :"<<pic<<endl;

    //读取图片
    src = imread(pic);

    //判断图片是否存在
    if(src.empty())
    {
        cout<<"pic is not exist!!!!"<<endl;
        return -1;
    }

    //显示图片
    namedWindow("src pic",WINDOW_AUTOSIZE);
    imshow("src pic",src);

    //定义5种颜色  最大分类不超过5
    Scalar color_tab[]={
        Scalar(0,0,255),
        Scalar(0,255,0),
        Scalar(255,0,0),
        Scalar(0,255,255),
        Scalar(255,0,255)
    };

    //定义分类  即函数K值 多少个分类点
    int num_cluster = 4;
    printf("num of num_cluster is %d\n",num_cluster);


    int width = src.cols;
    int height = src.rows;
    int dims = src.channels();

    //初始化定义

    //一共产生多少点数供我们测试
    int sample_count = width*height;
    printf("num of sample_count is %d\n",sample_count);

    Mat points(sample_count,dims,CV_32F); //产生的样本数,实际上为2通道的列向量,元素类型为Point2f
    Mat lables;   //聚类标签 每个点归哪个类
    Mat centers(num_cluster,1,points.type());

    //RGB数据转换到样本数据
    int index =0 ;
    for(int row=0;row<height;row++)
        for(int col=0;col<width;col++)
        {
            index = row*width +col;
            Vec3b bgr = src.at<Vec3b>(row,col);
            points.at<float>(index,0) = static_cast<int>(bgr[0]);
            points.at<float>(index,1) = static_cast<int>(bgr[1]);
            points.at<float>(index,2) = static_cast<int>(bgr[2]);
        }

    //使用Kmeans
    kmeans(points,num_cluster,lables,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT,10,0.1),
           3,KMEANS_PP_CENTERS,centers);

    //显示图像分割结果
    Mat result = Mat::zeros(src.size(),src.type());
    for(int row=0;row<height;row++)
        for(int col=0;col<width;col++)
        {
            index = row*width +col;
            int label = lables.at<int>(index,0);
            result.at<Vec3b>(row,col)[0] = color_tab[label][0];
            result.at<Vec3b>(row,col)[1] = color_tab[label][1];
            result.at<Vec3b>(row,col)[2] = color_tab[label][2];
        }

    imshow("kmeans-data",result);


    waitKey(0);
    destroyAllWindows();
    return 0;
}

效果

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值