所谓的聚类是指,将一个数据集中的某些方面相似的数据成员进行分类的过程,聚类就是一种发现这种内在结构的技术,聚类技术经常被称为无监督学习。k均值聚类是最著名的划分聚类算法,由于简洁和效率使得他成为所有聚类算法中最广泛使用的。给定一个数据点集合和需要的聚类数目k,k由用户指定,k均值算法根据某个距离函数反复把数据分入k个聚类中。
- k均值算法描述
先随机选取K个对象作为初始的聚类中心。然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。一旦全部对象都被分配了,每个聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是以下任何一个:
1)没有(或最小数目)对象被重新分配给不同的聚类。
2)没有(或最小数目)聚类中心再发生变化。
3)误差平方和局部最小。
而基于K均值的胡萝卜图像的分类,则是利用分类的思想,将目标区与背景区分成两类,所以,在此处将 K 的值,设置为 2;因为,是根据灰度值进行划分,则误差设置为1作为终止条件;
算法具体实现代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
IplImage* img = cvCloneImage(m_sourceImage);
IplImage* grayImage = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
//IplImage* redImage = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
//cvSplit(img, NULL, NULL, redImage, NULL);
cvCvtColor(img, grayImage, CV_RGB2GRAY);
//IplImage* img = cvCreateImage(cvGetSize(m_sourceImage), IPL_DEPTH_8U, 1);
//cvCvtColor(m_sourceImage, img, CV_RGB2GRAY);
int
total = img->height*img->width;
int
cluster_num = 2;
CvMat *row = cvCreateMat(img->height, img->width, CV_32FC3);
//cvConvert(redImage, row);
cvConvert(img, row);
//转一下类型!
CvMat *clusters = cvCreateMat(total, 1, CV_32SC1);
// CvArr* cvReshapeMatND(const CvArr* arr,
// int sizeof_header, CvArr* header,
// int new_cn, int new_dims, int* new_sizes);
//
// arr :输入数组
// sizeof_header :输出头的大小,对于IplImage, CvMat 和 CvMatND 各种结构输出的头均是不同的.
// header:被添充的输出头.
// new_cn:新的通道数,如果new_cn = 0 则通道数保持原样
// new_dims:新的维数.如果new_dims = 0 则维数保持原样。
// new_sizes
// 新的维大小.只有当 new_dims = 1值被使用,因为要保持数组的总数一致,因此如果 new_dims = 1, new_sizes 是不被使用的
cvReshape(row, row, 0, total);
//把图像转成数据矩阵,但注意实际数据未变,只是访问顺序变了。
cvKMeans2(row, cluster_num, clusters, cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0));
cvReshape(clusters, clusters, 0, img->width);
//聚类完的结果再reshape回来比较方便看~
int
i = 0, j = 0;
CvScalar s;
IplImage* resImg = cvCreateImage(cvSize(img->width, img->height), 8, 1);
//生成用来显示结果的图像
s = cvGet2D(img, i, j);
vector <
int
> v1, v2;
//存放分类后的灰度值
for
(i = 0; i < img->height; i++)
{
for
(j = 0; j < img->width; j++)
{
double
val = cvGetReal2D(grayImage, i, j);
if
(clusters->data.i[i*img->width + j] == 0)
{
v1.push_back(val);
//处理完成后的图像的白色区域
s.val[0] = 255;
cvSet2D(resImg, i, j, s);
//注意循环顺序
}
else
{
v2.push_back(val);
//处理完成后的图像的黑色区域
s.val[0] = 0;
cvSet2D(resImg, i, j, s);
}
}
}
double
thresh1 = accumulate(v1.begin(), v1.end(), 0) / v1.size();
double
thresh2 = accumulate(v2.begin(), v2.end(), 0) / v2.size();
if
(thresh2 > thresh1)
//如果黑色区域的灰度值均值大于白色区域
{
cvNot(resImg, resImg);
//图像取反
}
IplConvKernel *element = cvCreateStructuringElementEx(5, 5, 2, 2, CV_SHAPE_ELLIPSE);
cvSmooth(resImg, resImg, CV_MEDIAN);
cvErode(resImg, resImg, element, 1);
cvDilate(resImg, resImg, element, 1);
cvReleaseStructuringElement(&element);
cvSaveImage(
"s2.jpg"
, m_sourceImage);
cvSaveImage(
"b2.jpg"
, resImg);
ShowImage(resImg, IDC_PIC2);
int
key = cvWaitKey(0);
cvReleaseImage(&img);
//记得释放内存
cvReleaseImage(&resImg);
cvReleaseMat(&row);
cvReleaseMat(&clusters);
|
上述代码,对图像的处理结果进行了校正,因为,K-Mean聚类,只是简单的将目标区与背景区进行分类,而不能明确的标记出目标区还是背景区,所以,根据经验,将灰度值均值较大的部分作为目标区;
而灰度值较小的部分标记为背景区;
以下为未校正图像与校正后图像的对比:
如上图所示:左侧图像为未做校正的处理效果,右侧图像为校正后的图像;
由于此方法只是简单的根据图像的灰度值对图像进行二值化,所以,很难对复杂背景的图像进行分割;相关的算法,在以后的学习中再进行完善与补充。