开发环境 OpenCV-3.4.2
概述
K-Means方法
GMM方法
分水岭方法
GrabCut
K-Means方法
1. 无监督学习方法(不需要人为干预)
2. 分类问题,输入分类数目,初始化中心位置
3. 硬分类方法,以距离度量
4. 迭代分类为聚类
基本流程:
图解:
代码: 数据聚类
#include "../common/common.hpp"
void main(int argc, char** argv)
{
Mat img(500, 500, CV_8UC3);
RNG rng(12345);
Scalar colorTab[] = {
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, 5); // 分类数,随机
printf("number of clusters : %d\n", numCluster);
int sampleCount = rng.uniform(5, 1000); // 从图像中抓取多少个点
cout << "sampleCount=" << sampleCount << ", CV_32FC2=" << CV_32FC2 << endl;
Mat points(sampleCount, 1, CV_32FC2); // 1列,双通道
Mat labels;
Mat centers;
// 生成随机数
for (int k = 0; k < numCluster; k++)
{
Point center;
center.x = rng.uniform(0, img.cols); // 中心点随机从图像中抽取
center.y = rng.uniform(0, img.rows);
cout << "k=" << k << ", center.x=" << center.x << ", center.y=" << center.y << endl;
// mat.rowRange 函数从 mat 中抽取 startrow到endrow行的数据返回(类型与mat一致),但是不包括下标为endrow行的数据
// mat.rowRange 的返回值只是浅拷贝,指针指向还是原mat,所以下面的 rng.fill 是对points的填充
Mat pointChunk = points.rowRange(k*sampleCount / numCluster,
k == numCluster - 1 ? sampleCount : (k + 1)*sampleCount / numCluster); // 这里是抓取points中部分行数据
/*
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.x, center.y 坐标周围上下左右 img.cols*0.05, img.rows*0.05 的方差内生成高斯分布的随机数,最后赋值给pointChunk
rng.fill(pointChunk, RNG::NORMAL, Scalar(center.x, center.y), Scalar(img.cols*0.05, img.rows*0.05));
}
/*
randShuffle( // 将原数组(矩阵)打乱
InputOutputArray dst, // 输入输出数组(一维)
double iterFactor=1. , // 决定交换数值的行列的位置的一个系数...
RNG* rng=0 //(可选)随机数产生器,0表示使用默认的随机数产生器,即seed=-1。rng决定了打乱的方法
)
*/
randShuffle(points, 1, &rng);
/*
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); // 用不同颜色在img上绘制上面随机产生的分类点
}
// 每个聚类的中心来绘制圆
for (int i = 0; i < centers.rows; i++) // 通过kmeans得到的centers就是上面产生随机数据的各 center.x center.y (会有点偏差,不是相等)
{
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); // 绘制聚类圆,半径差不多为 img.rows*0.05?
}
imshow("src5-2", img);
waitKey(0);
}
效果图
代码: 图像分割
#include "../common/common.hpp"
void main(int argc, char** argv)
{
Mat src = imread(getCVImagesPath("images/toux.jpg"));
imshow("src5-4", src);
Scalar colorTab[] = {
Scalar(0, 0, 255), // 红
Scalar(0, 255, 0), // 绿
Scalar(255, 0, 0), // 蓝
Scalar(0, 255, 255), // 黄
Scalar(255, 0, 255) // 品红
};
int width = src.cols;
int height = src.rows;
int dims = src.channels();
// 初始化定义
int sampleCount = width*height;
int clusterCount = 4; // 分类数
Mat points(sampleCount, dims, CV_32FC1, Scalar(10)); // 行数为src的像素点数,列数为src的通道数,每列数据分别为src的b g r,src从上到下从左到右顺序读取数据
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; // points每列数据分别为src的b g r,src从上到下从左到右顺序读取数据
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]);
}
}
// 运行K-Means
TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1);
kmeans(points, clusterCount, labels, criteria, 3, KMEANS_PP_CENTERS, centers);
cout << "points.size=" << points.size() << ", labels.size=" << labels.size() << endl; // points.size=[3 x 127434], labels.size=[1 x 127434] cols x rows
// 显示图像分割结果
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 = labels.at<int>(index, 0); // labels只有1列,只能用0
result.at<Vec3b>(row, col)[0] = colorTab[label][0]; // 分类编号对应的颜色值的 b g r,分别赋值给result的b g r
result.at<Vec3b>(row, col)[1] = colorTab[label][1];
result.at<Vec3b>(row, col)[2] = colorTab[label][2];
}
}
for (int i = 0; i < centers.rows; i++) {
int x = centers.at<float>(i, 0);
int y = centers.at<float>(i, 1);
printf("center %d = c.x : %d, c.y : %d\n", i, x, y); // 不是基于src中像素位置的距离计算出来的,而是src中像素的颜色数据的中心值
}
imshow("KMeans Segmentation5-4", result);
waitKey(0);
}