6

I want to apply k-means clustering on the intensity values of a grayscale image. I'm really confused on how to represent the pixels into a vector. So if my image is H x W pixels, then my vector should be H*W dimensional.

What I've tried is :

int myClass::myFunction(const cv::Mat& img)
{
    cv::Mat grayImg;    
    cvtColor(img, grayImg, CV_RGB2GRAY);    
    cv::Mat bestLabels, centers, clustered;
    cv::Mat p = cv::Mat::zeros(grayImg.cols*grayImg.rows, 1, CV_32F);
    int i = -1;
    for (int c = 0; c<img.cols; c++) {
        for (int r = 0; r < img.rows; r++) {
            i++;
            p.at<float>(i, 0) = grayImg.at<float>(r, c);

        }
    }
// I should have obtained the vector in p, so now I want to supply it to k-means: 
int K = 2;
    cv::kmeans(p, K, bestLabels,
        cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0),
        3, cv::KMEANS_PP_CENTERS, centers);
// Since K=2, I want to obtain a binary image with this, so the same operation needs to be reversed (grayImg -> p , then bestLabels -> binaryImage)
}

However I'm getting an error : Unhandled exception at 0x00007FFD76406C51 (ntdll.dll) in myapp.exe

I'm new to OpenCV so I'm not sure how to use any of these functions. I found this code here. For example, why do we use .at<float>, some other post says that grayscale image pixels are stored as <char>s ?? I'm getting confused more and more, so any help would be appreciated :)

Thanks !

Edit

Thanks to Miki, I found the right way to do it. But one final question, how do I see the contents of cv::Mat1b result? I tried printing them like this :

for (int r = 0; r < result.rows; ++r)
    {
        for (int c = 0; c < result.cols; ++c)
        {
            result(r, c) = static_cast<uchar>(centers(bestLabels(r*grayImg.cols + c)));
            if (result(r, c) != 0) {
                std::cout << "result = " << result(r, c) << " \n";
            }               
        }
    }

But it keeps printing result=0, even though I specifically ask it not to :) How do I access the values?

4
  • 1
    You don't need to convert Mat to InputArray. InputArray is just a wrapper class that accepts cv::Mat and std::vector. So just pass a Mat where it accepts an InputArray and you'll be ok. Regarding the error... let me check Commented Oct 6, 2015 at 17:39
  • can you post the full code? a lot of missing variables here... Commented Oct 6, 2015 at 17:40
  • @Miki, I omitted the earlier parts, sorry for that. Now it should have all the relevant code. I'm sure the input is supplied correctly, I plotted grayImg and it looks as expected. The other 3 variables are initialized as cv::Mat. And thank you for the information that InputArray is not necessary. So I can just use a cv::Mat of size [H*W x 1] (or the transpose), right? Commented Oct 6, 2015 at 17:45
  • posted an answer, it should answer all your questions. let me know. Commented Oct 6, 2015 at 18:19

1 Answer 1

13
  1. You don't need to convert from Mat to InputArray, but you can (and should) just pass a Mat object where an InputArray is requested. See here for a detailed explanation

  2. kmeans accepts an InputArray, that should be an array of N-Dimensional points with float coordinates is needed.

  3. With Mat objects, you need img.at<type>(row, col) to access value of the pixel. You can, however, use Mat_ that is a templated version of Mat where you fix the type, so you can access the value just like img(r,c).

So the final code will be:

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


int main()
{
    Mat1b grayImg = imread("path_to_image", IMREAD_GRAYSCALE);

    Mat1f data(grayImg.rows*grayImg.cols, 1);
    for (int r = 0; r < grayImg.rows; r++)
    {
        for (int c = 0; c < grayImg.cols; c++)
        {
            data(r*grayImg.cols + c) = float(grayImg(r, c));

        }
    }

    // Or, equivalently
    //Mat1f data;
    //grayImg.convertTo(data, CV_32F);
    //data = data.reshape(1, 1).t();


    // I should have obtained the vector in p, so now I want to supply it to k-means: 
    int K = 8;
    Mat1i bestLabels(data.size(), 0); // integer matrix of labels
    Mat1f centers;                    // float matrix of centers
    cv::kmeans(data, K, bestLabels,
        cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0),
        3, cv::KMEANS_PP_CENTERS, centers);


    // Show results
    Mat1b result(grayImg.rows, grayImg.cols);
    for (int r = 0; r < result.rows; ++r)
    {
        for (int c = 0; c < result.cols; ++c)
        {
            result(r, c) = static_cast<uchar>(centers(bestLabels(r*grayImg.cols + c)));
        }
    }

    imshow("Image", grayImg);
    imshow("Result", result);
    waitKey();

    return 0;
}
Sign up to request clarification or add additional context in comments.

5 Comments

Thank you so much ! This works. However I have two questions. 1. How do you decide which one of Mat1b, Mat1f, etc. to use? And my second question is, how can I print the pixels of result? Because I suspect they might have values 1 or 2, whereas I want to convert them into 0 and 1. I'm adding what I tried in the question, I would really appreciate one final help on that one. Thanks again !
You might also be interested in my new question :) stackoverflow.com/questions/32978963/…
1) according to the documentation. It's easier to read the code and you have clean errors code when the type is not correct. You must know the type anyhow when you access a Mat with at<type>()
2) the values in result are already the grayscale values of the cluster centers, so are in [0,255]. You can just use imshow to see the result image.
If you want the image with the labels, you need to create a Mat1i, and put the values from bestLabels .

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.