OpenCV中KMeans算法介绍与应用

一:KMeans算法介绍

KMeans算法MacQueen在1967年提出的,是最简单与最常见数据分类方法之一并且最为一种常见数据分析技术在机器学习、数据挖掘、模式识别、图像分析等领域都用应用。如果从分类角度看KMeans属于硬分类即需要人为指定分类数目,而MeanSift分类方法则可以根据收敛条件自动决定分类数目。从学习方法上来说KMeans属于非监督学习方法即整个学习过程中不需要人为干预的学习方法,自动完成整个数据集合分类。对于给定的数据集合DS (Data Set)与输入的分类数目K,KMeans的整个工作原理可以描述如下:

  1. 根据输入的分类数目K定义K个分类,每个分类选择一个中心点
  2. 对DS中每个数据点做如下操作
    • 计算它与K个中心点之间的距离
    • 把数据点指定属于K个中心点中距离最近的中心点所属的分类
  3. 对K个分类中每个数据点计算平均值得到新的K个中心点
  4. 比较新K个中心点之间与第一步中已经存在的K个中心差值
    • 当两者之间的差值没有变化或者小于指定阈值,结束分类
    • 当两者之间的差值或者条件不满足时候,用新计算的中心点值做为K个分类的新中心点,继续执行2~4步。直到条件满足退出。

从数学的角度来说KMeans就是要找到K个分类而且他们的中心点到各个分类中各个数据的之间差值平方和最小化,而实现这个过程就是要通过上述2~4步不断的迭代执行,直到收敛为止。公式表示如下:

以上是KMeans算法的基本思想,想要实现或者应用该算法有三个注意点值得关注

  1. 初始的K个分类中每个分类的中心点选择,多数的算法实现都是支持随机选择与人工指定两种方式,OpenCV中的KMeans实现同样支持者两种方式。
  2. 多维数据支持,多数时候我们要分类的特征对象的描述数据不止一个数据特征,而是一个特征向量来表示,OpenCV中通过Mat对象构建实现对多维数据KMeans分类支持。
  3. 收敛条件 - 一般情况下在达到指定的迭代次数或者两次RSS差值小于给定阈值的情况下,结束执行分类处理,输出最终分类结果。

下图是一个例子,黑色的点代表数据点,十字表示中心点位置,初始输入的分类数目K=2时,KMeans各步执行结果:

二:OpenCV中KMeans相关函数说明

KMeans是OpenCV核心模块的一个API函数

各个参数的详细解释如下:

参数名称

解释

data

表示输入的数据集合,可以一维或者多维数据,类型是Mat类型,比如Mat points(count, 2, CV_32F)表示数据集合是二维,浮点数数据集

K

表示分类的数目,最常见的是K=2表示二分类

bestLabels

表示计算之后各个数据点的最终的分类索引,是一个INT类型的Mat对象

criteria

表示算法终止的条件,达到最大循环数目或者指定的精度阈值算法就停止继续分类迭代计算

attempts

表示为了获得最佳的分类效果,算法要不同的初始分类尝试次数

flags

表示表示选择初始中心点选择方法用哪一种KMEANSRANDOMCENTERS 表示随机选择中心点。KMEANSPPCENTERS 基于中心化算法选择。KMEANSUSEINITIAL_LABELS第一次分类中心点用输入的中心点

centers

表示输出的每个分类的中心点数据

三:应用案例-利用KMeans实现图像分割

KMeans在图像处理中经典应用场景就是根据用户输入的分类数目实现图像自动区域分割,本例就是基于OpenCV KMeans函数实现图像的自动分割, 对彩色图像来说,每个像素点都有RGB三个分量,整个图像可以看成是一个3维数据集合,只要把这个三维数据集作为输入参数传给KMeans函数即可,算法执行完毕之后,根据分类标记的索引设置不同的颜色即可。所以演示程序的实现步骤如下:

  1. 将输入图像转换为数据集合
  2. 使用KMeans算法对数据实现分类
  3. 根据每个数据点的分类索引,对图像重新填充颜色,显示分割后图像

运行效果如下:

完整的代码实现如下:

#include <opencv2/opencv.hpp>#include <iostream>using namespace cv;using namespace std;int main(int argc, char** argv) {    Mat src = imread("D:/vcprojects/images/toux.jpg");    imshow("input", src);    int width = src.cols;    int height = src.rows;    int dims = src.channels();    // 初始化定义    int sampleCount = width*height;    int clusterCount = 4;    Mat points(sampleCount, dims, CV_32F, Scalar(10));    Mat labels;    Mat centers(clusterCount, 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 rgb = src.at<Vec3b>(row, col);            points.at<float>(index, 0) = static_cast<int>(rgb[0]);            points.at<float>(index, 1) = static_cast<int>(rgb[1]);            points.at<float>(index, 2) = static_cast<int>(rgb[2]);        }    }    // 运行K-Means数据分类    TermCriteria criteria = TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0);    kmeans(points, clusterCount, labels, criteria, 3, KMEANS_PP_CENTERS, centers);    // 显示图像分割结果    Mat result = Mat::zeros(src.size(), CV_8UC3);    for (int row = 0; row < height; row++) {        for (int col = 0; col < width; col++) {            index = row*width + col;            int label = labels.at<int>(index, 0);            if (label == 1) {                result.at<Vec3b>(row, col)[0] = 255;                result.at<Vec3b>(row, col)[1] = 0;                result.at<Vec3b>(row, col)[2] = 0;            }            else if (label == 2) {                result.at<Vec3b>(row, col)[0] = 0;                result.at<Vec3b>(row, col)[1] = 255;                result.at<Vec3b>(row, col)[2] = 0;            }            else if (label == 3) {                result.at<Vec3b>(row, col)[0] = 0;                result.at<Vec3b>(row, col)[1] = 0;                result.at<Vec3b>(row, col)[2] = 255;            }            else if (label == 0) {                result.at<Vec3b>(row, col)[0] = 0;                result.at<Vec3b>(row, col)[1] = 255;                result.at<Vec3b>(row, col)[2] = 255;            }        }    }    imshow("kmeans-demo", result);    //imwrite("D:/vcprojects/images/cvtest.png", result);    waitKey(0);    return 0;}

你以为遥不可及的高度,只是别人开始的起点

原文发布于微信公众号 - OpenCV学堂(CVSCHOOL)

原文发表时间:2017-04-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏PaddlePaddle

【图像分类】使用经典模型进行图像分类

场景文字识别 图像相比文字能够提供更加生动、容易理解及更具艺术感的信息,是人们转递与交换信息的重要来源。图像分类是根据图像的语义信息对不同类别图像进行区分,是计...

1.6K50
来自专栏人工智能LeadAI

谈谈Tensorflow的Batch Normalization

tensorflow中关于BN(Batch Normalization)的函数主要有两个,分别是: tf.nn.moments tf.nn.batch_norm...

52670
来自专栏超智能体

YJango:TensorFlow高层API Custom Estimator建立CNN+RNN的演示

该文是YJango:TensorFlow中层API Datasets+TFRecord的数据导入的后续。

1.6K70
来自专栏漫漫深度学习路

pytorch学习笔记(三):自动求导

auto gradient 本片博文主要是对http://pytorch.org/docs/notes/autograd.html的部分翻译以及自己的理解,如有...

320100
来自专栏tkokof 的技术,小趣及杂念

数学笔记(二)之平面表示

  假设我们知道垂直于平面的法向量n,以及平面上的一点p0,如何使用这两个元素来表示该平面呢?

9220
来自专栏LhWorld哥陪你聊算法

【TensorFlow篇】--Tensorflow框架可视化之Tensorboard

TensorBoard是tensorFlow中的可视化界面,可以清楚的看到数据的流向以及各种参数的变化,本文基于一个案例讲解TensorBoard的用法。

15620
来自专栏目标检测和深度学习

30分钟学会用scikit-learn的基本回归方法(线性、决策树、SVM、KNN,Adaboost和GBRT)

15110
来自专栏机器学习算法工程师

从0 到1 实现YOLO v3(part two)

本部分是 从0到1 实现YOLO v3 的第二部分 的第二部分,前两部分主要介绍了YOLO的工作原理,包含的模块的介绍以及如何用pytorch搭建完整的YOL...

90140
来自专栏全球人工智能的专栏

如何用 Keras 为序列预测问题开发复杂的编解码循环神经网络?

在本篇文章中,你将学会如何用 Keras 为序列预测问题开发复杂的编解码循环神经网络。

40800
来自专栏IT派

30分钟学会用scikit-learn的基本回归方法(线性、决策树、SVM、KNN,Adaboost和GBRT)

前言:本教程主要使用了numpy的最最基本的功能,用于生成数据,matplotlib用于绘图,scikit-learn用于调用机器学习方法。如果你不熟悉他们(我...

14820

扫码关注云+社区

领取腾讯云代金券