2

I am new to opencv, and I am trying to find and save the largest cluster of a kmeaned clustered image. I have:

  • clustered the image following the method provided by Mercury and Bill the Lizard in the following post (Color classification with k-means in OpenCV),

  • determined the largest cluster by finding the largest label count from the kmeans output (bestLables)

  • tried to store the position of the pixels that constitute the largest cluster in an array of Point2i

However, the mystery is that I found myself with a number of stored points that is significantly less than the count obtained when trying to find the largest cluster. In other words: inc < max. Plus the number given by inc does not even correspond to any other clusters' number of points.

What did I do wrong? or is there a better way to do what I'm trying to do?, any input will be much appreciated. Thanks in advance for your precious help!!

#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;
}
Community
  • 1
  • 1
Gene
  • 23
  • 1
  • 4
  • Are you clustering on one image or among many? Do you want the distance between two pixels to affect the clustering, or just the color values? – Mark Miller Jul 15 '15 at 19:48
  • Sorry for the silence.Yes I am clustering one image. I am not sure about the distance but as long as it allows me to discriminate between the clusters i.e to access all the pixels of a label that I choose (for example the largest one) it works for me. Thanks! – Gene Jul 16 '15 at 12:43
  • I can't tell if you're looking for all the pixels of the same color or all the pixels of the same color that are nearby each other in the input image (i.e, looking for a specific object). If you want pixels similar in color and nearby in the original image, then k-means isn't likely to work, because it doesn't take into account pixel location, just color. Also, another thing I'd watch out for is a significantly different "largest" color each time k-means is run. If you run into these problems, let me know. If not, then it looks like Miki has you good to go! – Mark Miller Jul 16 '15 at 13:44
  • Thanks you as well !! – Gene Jul 16 '15 at 23:19

1 Answers1

3

You are pretty close, but there are a few errors. The code below should work as expected. I also added a small piece of code to show the classification result, where pixels of the larger cluster are red, the other with shades of green.

  1. You never initialized int clusters[5];, so it will contains random numbers at the beginning, compromising it as an accumulator. I recommend to use vector<int> instead.
  2. You access bestLabels with wrong indices. Instead of bestLables.data[y + x*imlab.cols], it should be bestLables.data[y*imlab.cols + x]. That caused your inc < max issue. In the code below I used a vector<int> to contain indices, since it's easier to see the content of the vector. So I access bestLabels a little differently, i.e. bestLables[y*imlab.cols + x] instead of bestLables.data[y*imlab.cols + x], but the result is the same.
  3. You had Point2i shape[2000];. I used a vector<Point>. Note that Point is just a typedef of Point2i. Since you don't know how many points will be there, better use a dynamic array. If you know that there will be, say, 2000 points, you'd better call reserve to avoid reallocations, but that's not mandatory. With Point2i shape[2000]; if you have more than 2000 points you'll go out of bounds, with a vector you're safe. I used emplace_back to avoid a copy when appending the point (just like you did with the initializer list). Note that the contructor of Point is (x,y), not (y,x).
  4. Using vector<Point> you don't need inc, since you append the value at the end. If you need inc to store the number of points in the largest cluster, simply call int inc = shape.size();
  5. You initialized int cluster = 1. That's an error, you should initialize it with the index of the largest cluster, i.e. int cluster = indx;.
  6. You are calling the vector of planes imgRGB, but you're working on Lab. You'd better change the name, but it's not an issue per se. Also, remember that RGB values are stored in OpenCV as BGR, not RGB (reversed order).
  7. I prefer Mat1b, Mat3b, etc... where possible over Mat. It allows easier access and is more readable (in my opinion). That's not an issue, but you'll see that in my code.

Here we go:

#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;
}
Miki
  • 40,887
  • 13
  • 123
  • 202
  • Hey Miki ! sorry for the silence. I really appreciate your answer. In fact, I do not know the size of shapes in advance; I also kept 'imgRGB' so the code I borrowed is easily identified. I'll give it a try and let you know. Thanks!! – Gene Jul 16 '15 at 13:20
  • @Gene let me know if something doesn't work as expected – Miki Jul 16 '15 at 13:22
  • Wow! it worked. I used `max` to reserve space for `shape`. However, I had to revert back to using `Mat` because after converting the original image `img` to `Lab`, I am splitting it to eliminate the `L channel` before clustering (filling it with 0), and I couldn't figure out how to make the L zero. I liked the use of `Mat3b` so I was wondering how to zero the L channel. I am getting another error when splitting `imlab` to `imRGB` but I suspect it is tied to the L elimination issue. – Gene Jul 16 '15 at 18:57
  • If you don't need L channel, just don't insert it ( in the for after the split). I didn't get what you mean with the second part of your comment. But probably using split and setTo(0) should work for you. – Miki Jul 16 '15 at 20:54
  • Everything worked out great. Thanks again for your assistance!! – Gene Jul 16 '15 at 23:17