将矩阵输入到OpenCV kmeans聚类算法中

7
这个问题是关于OpenCV的: OpenCV文档中给出的kmeans示例有一个2通道矩阵,每个维度的特征向量都有一个通道。但是,其他一些示例似乎说应该是一个单通道矩阵,其中每行表示一个样本,每列表示一个特征。哪一个是正确的?
如果我的特征向量有5个维度,我应该使用什么输入矩阵: 这个:
cv::Mat inputSamples(numSamples, 1, CV32FC(numFeatures))

这是其中一个例子:或者这个:
cv::Mat inputSamples(numSamples, numFeatures, CV_32F)
2个回答

31

正确的答案是cv::Mat inputSamples(numSamples, numFeatures, CV_32F)。 关于 kmeans 的 OpenCV 文档指出:

samples – 输入样本的浮点数矩阵,每个样本占一行。

因此,它不是像另一种选项中那样是n维浮点向量。哪些示例表明了这种行为?

这里还有一个小例子,展示了如何使用kmeans。它聚类了图像的像素并显示了结果:

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

using namespace cv;

int main( int argc, char** argv )
{
  Mat src = imread( argv[1], 1 );
  Mat samples(src.rows * src.cols, 3, CV_32F);
  for( int y = 0; y < src.rows; y++ )
    for( int x = 0; x < src.cols; x++ )
      for( int z = 0; z < 3; z++)
        samples.at<float>(y + x*src.rows, z) = src.at<Vec3b>(y,x)[z];


  int clusterCount = 15;
  Mat labels;
  int attempts = 5;
  Mat centers;
  kmeans(samples, clusterCount, labels, TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 10000, 0.0001), attempts, KMEANS_PP_CENTERS, centers );


  Mat new_image( src.size(), src.type() );
  for( int y = 0; y < src.rows; y++ )
    for( int x = 0; x < src.cols; x++ )
    { 
      int cluster_idx = labels.at<int>(y + x*src.rows,0);
      new_image.at<Vec3b>(y,x)[0] = centers.at<float>(cluster_idx, 0);
      new_image.at<Vec3b>(y,x)[1] = centers.at<float>(cluster_idx, 1);
      new_image.at<Vec3b>(y,x)[2] = centers.at<float>(cluster_idx, 2);
    }
  imshow( "clustered image", new_image );
  waitKey( 0 );
}

我想知道在clusterCount变量声明之前的循环中你在做什么,以及在kmeans之后的for循环中你在做什么。您觉得能否更新答案并提供这些信息?谢谢! - Jean-François Côté
第一个循环将图像数据从(行,列,3)矩阵重新排序为(行*列,3)矩阵(每个像素一行)。最后的循环将图像中的每个像素替换为相应的聚类中心以进行可视化。 - sietschie
能否使用 Mat::reshape() 代替嵌套的 for 循环? - Jayesh
最终权转换时的索引出错了,应该是 int cluster_idx = bestLabels.at<int>(x + y*img.cols,0); - ejectamenta
你为什么要这样奇怪地重新排列数据?从循环中看,图像的第一行现在将成为0、10、20、30...样本行,第二行将成为1、11、21、31... - simplename

2
作为手动重塑输入矩阵的替代方法,您可以使用OpenCV reshape函数以更少的代码实现类似的结果。这是我使用K-Means方法(在Java中)减少颜色计数的工作实现:
private final static int MAX_ITER = 10;
private final static int CLUSTERS = 16;

public static Mat colorMapKMeans(Mat img, int K, int maxIterations) {

    Mat m = img.reshape(1, img.rows() * img.cols());
    m.convertTo(m, CvType.CV_32F);

    Mat bestLabels = new Mat(m.rows(), 1, CvType.CV_8U);
    Mat centroids = new Mat(K, 1, CvType.CV_32F);
    Core.kmeans(m, K, bestLabels, 
                new TermCriteria(TermCriteria.COUNT | TermCriteria.EPS, maxIterations, 1E-5),
                1, Core.KMEANS_RANDOM_CENTERS, centroids);
    List<Integer> idx = new ArrayList<>(m.rows());
    Converters.Mat_to_vector_int(bestLabels, idx);

    Mat imgMapped = new Mat(m.size(), m.type());
    for(int i = 0; i < idx.size(); i++) {
        Mat row = imgMapped.row(i);
        centroids.row(idx.get(i)).copyTo(row);
    }

    return imgMapped.reshape(3, img.rows());
}

public static void main(String[] args) {
    System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    Highgui.imwrite("result.png", 
        colorMapKMeans(Highgui.imread(args[0], Highgui.CV_LOAD_IMAGE_COLOR),
            CLUSTERS, MAX_ITER));
}

OpenCV将图像读入二维、三通道矩阵中。首次调用reshape - img.reshape(1, img.rows() * img.cols()); - 实际上是将3个通道展开成列。在结果矩阵中,一行对应输入图像的一个像素,3列对应RGB分量。

在K-Means算法完成其工作并应用了颜色映射之后,我们再次调用reshape - imgMapped.reshape(3, img.rows()),但现在将列回滚到通道,并将行数减少到原始图像的行数,从而获得原始矩阵格式,但仅具有减少的颜色。


我认为在采取这种方法之前,您需要先确保图像是连续的。http://docs.opencv.org/2.4/modules/core/doc/basic_structures.html#mat-iscontinuous - ejectamenta
如果您使用克隆,例如img.clone().reshape(1, img.rows() * img.cols()),那么图像将是连续的(并且您的原始图像将不会改变)。 - ejectamenta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接