概述:
KMeans方法
GMM方法
分水岭方法
GrabCut
KMeans方法
1.无监督的学习方法(不需要人为的干预)
2.分类问题,输入数目,初始化中心位置
3.硬分类方法,以距离度量(不同的分类会有不同的分类标准)
4.迭代分类为聚类
基本流程:
1. 根据输入的分类数目K定义K个分类,每个分类选择一个中心点
2. 对DS(Data Set)中每个数据点做如下操作:
-计算它与K个中心点之间的距离
-把数据点指定属于K个中心点中距离最近的中心点所属的分类
3. 对K个分类中每个数据点计算平均值得到新的K个中心点
4. 比较新K个中心点之间与第一步中已经存在的K个中心差值
-当两者之间的差值没有变化或者小于指定阈值,或者迭代次数小于指定的次数,就结束分类
-当两者之间的差值或者条件不满足时候,用新计算的中心点值做为K个分类的新中心点,继续执行2~4步。直到条件满足退出。
从数学的角度来说KMeans就是要找到K个分类而且他们的中心点到各个分类中各个数据的之间差值平方和最小化,而实现这个过程就是要通过上述2~4步不断的迭代执行,直到收敛为止。公式表示如下:
图解:
注意点:
1. 初始的K个分类中每个分类的中心点选择,多数的算法实现都是支持随机选择与人工指定两种方式,OpenCV中的KMeans实现同样支持这两种方式。
2. 多维数据支持,多数时候我们要分类的特征对象的描述数据不止一个数据特征,而是一个特征向量来表示,OpenCV中通过Mat对象构建实现对多维数据KMeans分类支持。
3. 收敛条件 - 一般情况下在达到指定的迭代次数或者两次RSS差值小于给定阈值的情况下,结束执行分类处理,输出最终分类结果。
代码:数据聚类
#include<opencv2\opencv.hpp>
#include<opencv2\core\core.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<iostream>
#include<cstdio>
using namespace std;
using namespace cv;
int main(int argc, char** argv) {
Mat img(500, 500, CV_8UC3); //得到随机样本量的坐标和显示结果
RNG rng(12345);
//生成doule型数据的时候,用double类型数据作为上下限,不然会默认为int
Scalar colorTab[] =
{
Scalar(0, 0, 0),
Scalar(0, 0, 255),
Scalar(0, 255, 0),
Scalar(255, 0, 0),
Scalar(0, 255, 255),//红+绿 == 黄
Scalar(255, 0, 255),//蓝+红 == 粉
};
int numCluster = rng.uniform(2, 6); //随机分类种数
printf("number of clusters : %d\n", numCluster);
int sampleCount = rng.uniform(6, 1000); //随机处待分类的数据量
Mat points(sampleCount, 1, CV_32FC2);
//因为待分类的数据只有一种类型,一维的,所以用1列
//双通道是存储坐标(x,y),其实就是Point2f类型的数据
Mat labels; //存放表示每个簇的标签,是一个整数,从0开始的索引整数
//行数跟points一样,每一行中储存对于Points行数中的数据是属于哪一种的分类
Mat centers; //存放的是kmeans算法结束后每个簇的中心位置
// 生成随机数
for (int k = 0; k < numCluster; k++) {
Point center;
center.x = rng.uniform(0, img.cols);
center.y = rng.uniform(0, img.rows); //随机出中心点
Mat pointChunk = points.rowRange(k*sampleCount / numCluster,
k == numCluster - 1 ? sampleCount : (k + 1)*sampleCount / numCluster);
// mat.rowRange 函数从 mat 中抽取 startrow到endrow行的数据返回(类型与mat一致)
//,索取的位置是左闭右开,不包含endrow
// mat.rowRange 的返回值只是浅拷贝,指针指向还是原mat,所以下面的 rng.fill 是对points的填充
Scalar color1 = Scalar(center.x, center.y);
Scalar color2 = Scalar(img.cols*0.05, img.rows*0.05);
/*
void cv::RNG::fill( // 对矩阵mat填充随机数
InputOutputArray mat,
int distType, // 类型为RNG::UNIFORM,则表示产生均匀分布的随机数,如果 为RNG::NORMAL则表示产生高斯分布的随机数
InputArray a, // 如果随机数产生模型为均匀分布,则参数a表示均匀分布的下限,参数b表示上限。
InputArray b, // 如果随机数产生模型为高斯模型,则参数a表示均值,参数b表示方差。
bool saturateRange = false // 只有当随机数产生方式为均匀分布时才有效,表示的是是否产生的数据要布满整个范围
)
// 在center坐标周围上下左右 img.cols*0.05, img.rows*0.05 的方差内生成高斯分布的随机数,最后赋值给pointChunk
*/
rng.fill(pointChunk, RNG::NORMAL, color1, color2);
}
/*
randShuffle( // 将原数组(矩阵)打乱
InputOutputArray dst, // 输入输出数组(一维)
double iterFactor=1. , // 表示随机交换元素的数量的缩放因子,总的交换次数dst.rows*dst.cols*iterFactor
RNG* rng=0 //(可选)随机数产生器,0表示使用默认的随机数产生器,即seed=-1。rng决定了打乱的方法
)
*/
randShuffle(points, 1, &rng);//打乱points中的顺序
/*
double kmeans(
InputArray data, // 输入图像,浮点数类型
int K, // 分类数,常取2
InputOutputArray bestLabels, // 输出参数,其size为1*data.rows,其中各值为data中与bestLabels对应行数的数据分类最终得到的分类编号
TermCriteria criteria, // 迭代停止条件
int attempts, // 尝试几次,尝试的次数越多结果越理想,但是计算更耗时,常取2-3
int flags, // KMEANS_PP_CENTERS(中心初始化算法来初始化)
KMEANS_RANDOM_CENTERS(随机方式初始化)
KMEANS_USE_INITIAL_LABELS(用户指定的方式初始化)
OutputArray centers = noArray() // 输出参数,保存各分类中心点的坐标位置,其长度为参数K,类型与参数data一致
);
*/
//使用KMeans, TermCriteria类是用来作为迭代算法的终止条件的
//,参数:类型(EPS表示迭代到阈值终止),第二个参数为迭代的最大次数,最后一个是特定的阈值
kmeans(points, numCluster, labels, TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1),
3, KMEANS_PP_CENTERS, centers);
// 用不同颜色显示分类
img = Scalar::all(255);
for (int i = 0; i < sampleCount; i++) {
int index = labels.at<int>(i);
Point p = points.at<Point2f>(i);
circle(img, p, 2, colorTab[index], -1, 8);
}
// 每个聚类的中心来绘制圆
for (int i = 0; i < centers.rows; i++) {
int x = centers.at<float>(i, 0);
int y = centers.at<float>(i, 1);
printf("c.x= %d, c.y=%d\n", x, y);
circle(img, Point(x, y), 40, colorTab[i], 1, LINE_AA);
}
imshow("KMeans-Data-Demo", img);
waitKey(0);
return 0;
}
效果图:
代码:图像分割
#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
Mat srcImg = imread("toux.jpg");
if (!srcImg.data)
{
printf("could not load image....\n");
return -1;
}
imshow("input image", srcImg);
Scalar colorTab[] = {
Scalar(0,0,255),
Scalar(0,255,0),
Scalar(255,0,0),
Scalar(255,255,0),
Scalar(255,0,255),
};
int width = srcImg.cols;
int height = srcImg.rows;
int dims = srcImg.channels();
//初始化定义
int sampleCount = width*height;
int clusterCount = 3;
Mat points(sampleCount, dims, CV_32FC1, Scalar(10));
Mat labels;
Mat center(clusterCount, 1, points.type());//为什么是1列和3维?
//目前的理解是每列中的每个通道存取对应通道的中心值
//还是不太理解center三通道里的值的含义
int index = 0;
for (int row = 0; row < srcImg.rows; row++)
{
for (int col = 0; col < srcImg.cols; col++)
{
index = row*width + col; //转到一维
Vec3b bgr = srcImg.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(points, clusterCount, labels,
TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1),
3, KMEANS_PP_CENTERS, center);
//center可不写
Mat result = Mat::zeros(srcImg.size(), srcImg.type());//老是忘记初始化,哼!
index = 0;
for (int row = 0; row < srcImg.rows; row++)
{
for (int col = 0; col < srcImg.cols; col++)
{
index = row*width + col;
int label = labels.at<int>(index);
// Vec3b bgr = result.at<Vec3b>(row, col);
// bgr[0] = colorTab[label][0];
// bgr[1] = colorTab[label][1];
// bgr[2] = colorTab[label][2];
//还在疑惑为什么用上面这段代码替代下面的会得到一张黑图,地址不对????
result.at<Vec3b>(row, col)[0] = colorTab[label][0];
result.at<Vec3b>(row, col)[1] = colorTab[label][1];
result.at<Vec3b>(row, col)[2] = colorTab[label][2];
}
}
imshow("KMeans Image", result);
waitKey(0);
return 0;
}