如何在opencv中访问特定的kmeans集群

Posted

技术标签:

【中文标题】如何在opencv中访问特定的kmeans集群【英文标题】:How to access a particular kmeans cluster in opencv 【发布时间】:2015-10-05 00:31:58 【问题描述】:

我是opencv的新手,我正在尝试查找并保存kmeaned集群图像的最大集群。我有:

按照 Mercury 和 Bill the Lizard 在以下帖子 (Color classification with k-means in OpenCV) 中提供的方法对图像进行聚类,

通过从 kmeans 输出 (bestLables) 中找到最大的标签计数来确定最大的集群

试图存储构成Point2i数组中最大簇的像素的位置

然而,神秘的是,我发现自己存储的点数明显少于计数 试图找到最大的集群时获得。换句话说: inc

我做错了什么?还是有更好的方法来做我想做的事情?,任何输入将不胜感激。 提前感谢您的宝贵帮助!

#include <iostream>
#include "opencv2/opencv.hpp"
#include<opencv2/highgui/highgui.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

using namespace cv;
using namespace std;



int main(int argc, char** argv)


    Mat img = imread("pic.jpg", CV_LOAD_IMAGE_COLOR);   

    if (!img.data) 
    
        cout << "Could not open or find the image" << std::endl;
        return -1;
    


    //imshow("img", img);

    Mat imlab;
    cvtColor(img, imlab, CV_BGR2Lab);

    /* Cluster image */
    vector<cv::Mat> imgRGB;
    int k = 5;
    int n = img.rows *img.cols;
    Mat img3xN(n, 3, CV_8U);

    split(imlab, imgRGB);

    for (int i = 0; i != 3; ++i)
        imgRGB[i].reshape(1, n).copyTo(img3xN.col(i));

    img3xN.convertTo(img3xN, CV_32F);

    Mat bestLables;
    kmeans(img3xN, k, bestLables, cv::TermCriteria(), 10, cv::KMEANS_RANDOM_CENTERS);

    /*bestLables= bestLables.reshape(0,img.rows);
    cv::convertScaleAbs(bestLables,bestLables,int(255/k));
    cv::imshow("result",bestLables);*/

    /* Find the largest cluster*/
    int max = 0, indx= 0, id = 0;
    int clusters[5];

    for (int i = 0; i < bestLables.rows; i++)
    
        id = bestLables.at<int>(i, 0);
        clusters[id]++;

        if (clusters[id] > max)
        
            max = clusters[id];
            indx = id;
        
    

    /* save largest cluster */
    int cluster = 1, inc = 0;
    Point2i shape[2000]; 

    for (int y = 0; y < imlab.rows; y++)
    
        for (int x = 0; x < imlab.cols; x++)
        
            if (bestLables.data[y + x*imlab.cols] == cluster) shape[inc++] =  y, x ;
        
    


    waitKey(0);


    return 0;

【问题讨论】:

您是在一张图片上还是在多张图片中进行聚类?您希望两个像素之间的距离影响聚类,还是只影响颜色值? 抱歉沉默。是的,我正在对一张图像进行聚类。我不确定距离,但只要它允许我区分集群,即访问我选择的标签的所有像素(例如最大的像素),它对我有用。谢谢! 我不知道你是在寻找输入图像中所有相同颜色的像素还是所有相邻的相同颜色的像素(即寻找特定的目的)。如果您想要原始图像中颜色相似且附近的像素,则 k-means 不太可能起作用,因为它不考虑像素位置,只考虑颜色。此外,我要注意的另一件事是每次运行 k-means 时都会出现明显不同的“最大”颜色。如果您遇到这些问题,请告诉我。如果没有,那么看起来 Miki 有你的好去处! 也谢谢你!! 【参考方案1】:

您非常接近,但有一些错误。下面的代码应该按预期工作。我还添加了一小段代码来显示分类结果,其中较大簇的像素为红色,另一个为绿色阴影。

    您从未初始化过int clusters[5];,因此它会在开头包含随机数,从而将其用作累加器。我建议改用vector&lt;int&gt;。 您使用错误的索引访问bestLabels。而不是bestLables.data[y + x*imlab.cols],它应该是bestLables.data[y*imlab.cols + x]。这导致了您的inc &lt; max 问题。在下面的代码中,我使用了vector&lt;int&gt; 来包含索引,因为它更容易看到向量的内容。所以我访问bestLabels有点不同,即bestLables[y*imlab.cols + x]而不是bestLables.data[y*imlab.cols + x],但结果是一样的。 你有Point2i shape[2000];。我使用了vector&lt;Point&gt;。注意Point 只是Point2i 的类型定义。由于您不知道会有多少点,因此最好使用动态数组。如果您知道会有 2000 个积分,您最好致电reserve 以避免重新分配,但这不是强制性的。使用Point2i shape[2000];,如果您有超过 2000 分,您将超出界限,使用vector,您是安全的。我使用emplace_back 在附加点时避免复制(就像你对初始化列表所做的那样)。注意Point 的构造函数是(x,y),而不是(y,x)。 使用vector&lt;Point&gt;,您不需要inc,因为您将值附加到末尾。如果您需要inc 来存储最大集群中的点数,只需调用int inc = shape.size(); 您已初始化int cluster = 1。这是一个错误,你应该用最大集群的索引来初始化它,即int cluster = indx;。 您正在调用平面矢量imgRGB,但您正在使用 Lab。您最好更改名称,但这本身不是问题。另外,请记住 RGB 值在 OpenCV 中存储为 BGR,而不是 RGB(倒序)。 比起Mat,我更喜欢Mat1bMat3b 等...。它允许更容易访问并且更具可读性(在我看来)。这不是问题,但您会在我的代码中看到这一点。

我们开始吧:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;

int main(int argc, char** argv)

     Mat3b img = imread("path_to_image");   

    if (!img.data) 
    
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    

    Mat3b imlab;
    cvtColor(img, imlab, CV_BGR2Lab);

    /* Cluster image */
    vector<cv::Mat3b> imgRGB;
    int k = 5;
    int n = img.rows * img.cols;
    Mat img3xN(n, 3, CV_8U);

    split(imlab, imgRGB);

    for (int i = 0; i != 3; ++i)
        imgRGB[i].reshape(1, n).copyTo(img3xN.col(i));

    img3xN.convertTo(img3xN, CV_32F);

    vector<int> bestLables;
    kmeans(img3xN, k, bestLables, cv::TermCriteria(), 10, cv::KMEANS_RANDOM_CENTERS);

    /* Find the largest cluster*/
    int max = 0, indx= 0, id = 0;
    vector<int> clusters(k,0);

    for (size_t i = 0; i < bestLables.size(); i++)
    
        id = bestLables[i];
        clusters[id]++;

        if (clusters[id] > max)
        
            max = clusters[id];
            indx = id;
        
    

    /* save largest cluster */
    int cluster = indx;

    vector<Point> shape; 
    shape.reserve(2000);

    for (int y = 0; y < imlab.rows; y++)
    
        for (int x = 0; x < imlab.cols; x++)
        
            if (bestLables[y*imlab.cols + x] == cluster) 
            
                shape.emplace_back(x, y);
            
        
    

    int inc = shape.size();

    // Show results
    Mat3b res(img.size(), Vec3b(0,0,0));
    vector<Vec3b> colors;
    for(int i=0; i<k; ++i)
    
        if(i == indx) 
            colors.push_back(Vec3b(0, 0, 255));
         else 
            colors.push_back(Vec3b(0, 255 / (i+1), 0));
        
    

    for(int r=0; r<img.rows; ++r)
    
        for(int c=0; c<img.cols; ++c)
        
            res(r,c) = colors[bestLables[r*imlab.cols + c]];
        
    

    imshow("Clustering", res);
    waitKey(0);

    return 0;

【讨论】:

嘿三木!对沉默感到抱歉。我真的很感谢你的回答。事实上,我事先并不知道形状的大小;我还保留了“imgRGB”,因此我借用的代码很容易识别。我会试一试,让你知道。谢谢!! @Gene 让我知道如果有什么事情没有按预期工作 哇!有效。我使用maxshape 保留空间。但是,我不得不恢复使用Mat,因为在将原始图像img 转换为Lab 之后,我将其拆分以在聚类之前消除L channel(用0 填充它),我不能弄清楚如何使 L 为零。我喜欢使用Mat3b,所以我想知道如何将 L 通道归零。将imlab 拆分为imRGB 时出现另一个错误,但我怀疑它与 L 消除问题有关。 如果你不需要L通道,就不要插入它(在拆分后的for中)。我不明白你评论第二部分的意思。但可能使用 split 和 setTo(0) 应该适合你。 一切都很顺利。再次感谢您的帮助!

以上是关于如何在opencv中访问特定的kmeans集群的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV K-Means (kmeans2)

如何在 3D 绘图(熊猫)中指定 kmeans 簇的颜色?

opencv:如何使用kmeans()按角度聚类

如何从 KMeans 集群中获取集群的名称?

如何使用 KMeans 查找在同一个集群中的文档

如何在kmeans scikit learn中识别集群标签