1.kmeans简介
虽然 k-means 并不能保证总是能得到全局最优解,但是对于这样的问题,像 k-means 这种复杂度的算法,这样的结果已经是很不错的了。
下面我们来总结一下 k-means 算法的具体步骤:
[1] 选定 K 个中心 \mu_k 的初值。这个过程通常是针对具体的问题有一些启发式的选取方法,或者大多数情况下采用随机选取的办法。因为前面说过 k-means 并不能保证全局最优,而是否能收敛到全局最优解其实和初值的选取有很大的关系,所以有时候我们会多次选取初值跑 k-means ,并取其中最好的一次结果。
[2] 将每个数据点归类到离它最近的那个中心点所代表的 cluster 中。
[3] 用公式 \mu_k = \frac{1}{N_k}\sum_{j\in\text{cluster}_k}x_j 计算出每个 cluster 的新的中心点。
[4] 重复第二步,一直到迭代了最大的步数或者前后的 J 的值相差小于一个阈值为止。
OPENCV中提供了kmeans具体的实现,这里就不介绍了。直接上代码。
Mat labels;
Mat center;
Mat p = Mat::zeros(pDoc->m_MatImage.cols*pDoc->m_MatImage.rows, 3, CV_32F); //初始化全0矩阵
for (int i = 0; i<pDoc->m_MatImage.cols*pDoc->m_MatImage.rows; i++)
{
p.at<float>(i, 0) = (i / pDoc->m_MatImage.cols) / pDoc->m_MatImage.rows; // p.at<uchar>(y,x) 相当于 p->Imagedata[y *p->widthstep + x], p是8位uchar
p.at<float>(i, 1) = (i%pDoc->m_MatImage.cols) / pDoc->m_MatImage.cols; // p.at<float>(y,x) 相当于 p->Imagedata[y *p->widthstep + x], p是32位float
p.at<float>(i, 2) = pDoc->m_MatImage.at<uchar>(i); //灰度值
}
vector<int> cenValue;//聚类中心的值,可能以后不需要
vector<cv::Mat> maskMat;//掩码矩阵
kmeans(p, 4, labels, TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 100, 1.0), 1, KMEANS_PP_CENTERS, center);//)
for (int i = 0; i <center.rows; i++)//列是分为了几类
{
Mat wp(pDoc->m_MatImage.rows,pDoc->m_MatImage.cols,CV_8U,255);
maskMat.push_back(wp);//初始化掩码矩阵
}
for (int i = 0; i<pDoc->m_MatImage.cols*pDoc->m_MatImage.rows; i++)
{
int intensity = labels.at<int>(i);//分为4类,生成4个msak矩阵
if (intensity == 0)
{
pDoc->m_MatImage.at<uchar>(i) = 0;
maskMat.at(0).at<uchar>(i) = 0;
}
else if (intensity == 1)
{
pDoc->m_MatImage.at<uchar>(i) = 100;
maskMat.at(1).at<uchar>(i) = 0;
}
else if (intensity == 2)
{
pDoc->m_MatImage.at<uchar>(i) = 200;
maskMat.at(2).at<uchar>(i) = 0;
}
else
{
pDoc->m_MatImage.at<uchar>(i) = 255;
maskMat.at(3).at<uchar>(i) =0;
}
}
没法修改图片了,第一副是原图,第二幅是聚类为4类后用不同的灰度表示,第三幅是4个掩码图,与原图做与就可以得到要分割的部分了。
原图
聚类结果图
四个掩码图