kmeans是非常经典的聚类算法,至今也还保留着较强的生命力,图像处理中经常用到kmeans算法或者其改进算法进行图像分割操作,在数据挖掘中kmeans经常用来做数据预处理。opencv中提供了完整的kmeans算法,其函数原型为:
double kmeans( InputArray data, int K, InputOutputArray bestLabels, TermCriteria criteria, int attempts, int flags, OutputArray centers = noArray() );
其中data表示用于聚类的数据,是N维的数组类型(Mat型),必须浮点型;
K表示需要聚类的类别数;
bestLabels聚类后的标签数组,Mat型;
criteria迭代收敛准则(MAX_ITER最大迭代次数,EPS最高精度);
attemps表示尝试的次数,防止陷入局部最优;
flags 表示聚类中心的选取方式(KMEANS_RANDOM_CENTERS 随机选取,KMEANS_PP_CENTERS使用Arthur提供的算法,KMEANS_USE_INITIAL_LABELS使用初始标签);
centers 表示聚类后的类别中心。
关于kmeans的理论可以参考:基本Kmeans算法介绍及其实现
下面给出一个关于图像聚类的示例:
#include <opencv.hpp> using namespace cv; Scalar colorTab[] = //10个颜色 { Scalar(0, 0, 255), Scalar(0, 255, 0), Scalar(255, 100, 100), Scalar(255, 0, 255), Scalar(0, 255, 255), Scalar(255, 0, 0), Scalar(255, 255, 0), Scalar(255, 0, 100), Scalar(100, 100, 100), Scalar(50, 125, 125) }; class ClusterPixels { private: Mat image; //待聚类图像 Mat labels; //聚类后的标签 int clusterCounts; //分类数,不得大于10,只是颜色定义只有10类,并不是算法限制 public: ClusterPixels() :clusterCounts(0){} ClusterPixels(const Mat& src, int clusters = 5) :clusterCounts(clusters){ image = src.clone(); } void setImage(const Mat& src){ image = src.clone(); }; void setClusters(int clusters){ clusterCounts = clusters; } Mat getLabels() {return labels; }; //返回聚类后的标签 Mat clusterGrayImageByKmeans() { //转换成灰度图 if (image.channels() != 1) cvtColor(image, image, COLOR_BGR2GRAY); int rows = image.rows; int cols = image.cols; //保存聚类后的图片 Mat clusteredMat(rows, cols, CV_8UC3); clusteredMat.setTo(Scalar::all(0)); Mat pixels(rows*cols, 1, CV_32FC1); //pixels用于保存所有的灰度像素 for (int i = 0; i < rows;++i) { const uchar *idata = image.ptr<uchar>(i); float *pdata = pixels.ptr<float>(0); for (int j = 0; j < cols;++j) { pdata[i*cols + j] = idata[j]; } } kmeans(pixels, clusterCounts, labels, TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 10, 0), 5, KMEANS_PP_CENTERS); for (int i = 0; i < rows;++i) { for (int j = 0; j < cols;++j) { circle(clusteredMat, Point(j,i), 1, colorTab[labels.at<int>(i*cols + j)]); //标记像素点的类别,颜色区分 } } return clusteredMat; } Mat clusterColorImageByKmeans() { assert(image.channels() != 1); int rows = image.rows; int cols = image.cols; int channels = image.channels(); //保存聚类后的图片 Mat clusteredMat(rows, cols, CV_8UC3); clusteredMat.setTo(Scalar::all(0)); Mat pixels(rows*cols, 1, CV_32FC3); //pixels用于保存所有的灰度像素 pixels.setTo(Scalar::all(0)); for (int i = 0; i < rows; ++i) { const uchar *idata = image.ptr<uchar>(i); float *pdata = pixels.ptr<float>(0); for (int j = 0; j < cols*channels; ++j) { pdata[i*cols*channels + j] = saturate_cast<float>(idata[j]); } } kmeans(pixels, clusterCounts, labels, TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 0), 5, KMEANS_PP_CENTERS); for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols*channels; j += channels) { circle(clusteredMat, Point(j/channels,i), 1, colorTab[labels.at<int>(i*cols + (j/channels))]); //标记像素点的类别,颜色区分 } } return clusteredMat; } };
主函数:
#include "clusterImagePixels.hpp" int main() { Mat testImage = imread("E:\\testImage\\board.jpg"); if (testImage.empty()) { return -1; } ClusterPixels clusterPix(testImage,3); Mat colorResults = clusterPix.clusterColorImageByKmeans(); Mat grayResult = clusterPix.clusterGrayImageByKmeans(); if (!colorResults.empty()) { hconcat(testImage, colorResults, colorResults); imshow("clusterImage", colorResults); } if (!grayResult.empty()) { hconcat(testImage, grayResult, grayResult); imshow("grayCluster", grayResult); } if (waitKey() == 27) return 0; }
效果图:
采用灰度聚类的结果:
采用RGB颜色聚类得到的效果图: