5

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?

Community
  • 1
  • 1
jeff
  • 13,055
  • 29
  • 78
  • 136
  • 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 – Miki Oct 06 '15 at 17:39
  • can you post the full code? a lot of missing variables here... – Miki Oct 06 '15 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? – jeff Oct 06 '15 at 17:45
  • posted an answer, it should answer all your questions. let me know. – Miki Oct 06 '15 at 18:19

1 Answers1

12
  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;
}
Community
  • 1
  • 1
Miki
  • 40,887
  • 13
  • 123
  • 202
  • 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 ! – jeff Oct 06 '15 at 19:45
  • You might also be interested in my new question :) http://stackoverflow.com/questions/32978963/how-to-use-staticsaliencyspectralresidual-class-of-opencv-in-c – jeff Oct 06 '15 at 20:19
  • 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() – Miki Oct 06 '15 at 21:47
  • 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. – Miki Oct 06 '15 at 21:49
  • If you want the image with the labels, you need to create a Mat1i, and put the values from bestLabels . – Miki Oct 06 '15 at 21:51