2

I have obtained a labeling with the connectedComponents function of C++ OpenCV, which looks like in the picture :

enter image description here

This is the output of the ccLabels variable, which is a cv::Mat of the same size with the original image.

So what I need to do is :

  1. Count the occurences of each number, and select the ones that occur more than N times, which are the "big" ones.
  2. Segment the areas of the "big" components, and then count the number of 4's and 0's inside that area.

My ultimate aim is to count the number of holes in the image, so I aim to infer number of holes from (number of 0's / number of 4's). This is probably not the prettiest way but the images are very uniform in terms of size and illumination, so it will meet my needs.

But I'm new to OpenCV and I don't have much idea how to accomplish this task.

Here is what I've done so far:

cv::Mat1b outImg;
cv::threshold(grayImg, outImg, 150, 255, 0); // Thresholded -binary- image
cv::Mat ccLabels;
cv::connectedComponents(outImg, ccLabels); // Each non-zero pixel is labeled with their connectedComponent ID's
// write the labels to file:
std::ofstream myfile;
    myfile.open("ccLabels.txt");
    cv::Size s = ccLabels.size();
    myfile << "Size: " << s.height << " , " << s.width <<"\n";
    for (int r1 = 0; r1 < s.height; r1++) {
        for (int c1 = 0; c1 < s.height; c1++) {
            myfile << ccLabels.at<int>(r1,c1);
        }
        myfile << "\n";
    }
    myfile.close();

Since I know how to iterate inside the matrix, counting the numbers should be OK, but first I have to separate(eliminate / ignore) the "background" pixels, which are the 0's outside the connected components. Then counting should be easy.

How can I segment these "big" components? Maybe obtaining a mask, and only consider pixels where mask(x,y) = 1?

Thanks for any help !

Edit

This is the thresholded image:

enter image description here

And this is what I get after Canny edge detection :

enter image description here

This is the actual image (thresholded) : enter image description here

jeff
  • 13,055
  • 29
  • 78
  • 136
  • can you provide the original (grayscale) image? – Miki Oct 07 '15 at 16:35
  • It's a dice image taken from top, something that looks like this: http://static.guim.co.uk/sys-images/BOOKS/Pix/pictures/2011/5/24/1306238777181/Dice-007.jpg – jeff Oct 07 '15 at 16:46
  • Here you can simply (basically) threshold and count the black blobs... – Miki Oct 07 '15 at 16:48
  • Well in the real example, background is also dark, let me put the output of the Canny edge detector. – jeff Oct 07 '15 at 16:52
  • Yeah, that why I asked for the original image. Can you post it? – Miki Oct 07 '15 at 16:58
  • @Miki actually I don't want to share the original images. It is a homework, so I'm afraid for plagiarism :) Well, I'm not feeling guilty since I'm trying to do the work myself, but still.. I also shared the thresholded image, which can be supposed as the input to my system. – jeff Oct 07 '15 at 17:05
  • Ok, this one is much better! I don't have time for an answer now, but: 1) findContours with CV_RETR_EXTERNAL 2) for each contour, drawContours CV_FILLED to create the mask 3) XOR thresholded and mask, 4) findContours again and count number of contours 5) you're done – Miki Oct 07 '15 at 17:13
  • Or understand how FindContours hierarchy works, and you're done counting nested (child) contours – Miki Oct 07 '15 at 17:16
  • Thank you so much @Miki. – jeff Oct 07 '15 at 17:21
  • One more question, the contours are 0-indexed, right? – jeff Oct 07 '15 at 19:14
  • everything in C++ is 0-indexed :D – Miki Oct 07 '15 at 19:15

3 Answers3

3

Here a simple procedure to find the number on the dices, starting from your thresholded image

  1. find external contours
  2. for each contour
    • eventually discard small blobs
    • draw the filled mask
    • use AND and XOR to isolate internal holes
    • find contours, again
    • count contours

Result:

Number: 5
Number: 2

Image:

Code:

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

int main(void)
{
    // Grayscale image
    Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);

    // Minimum area of the contour
    double minContourArea = 10;

    // Prepare outpot
    Mat3b result;
    cvtColor(img, result, COLOR_GRAY2BGR);

    // Find contours
    vector<vector<Point>> contours;
    findContours(img.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

    for (int i = 0; i < contours.size(); ++i)
    {
        // Check area
        if (contourArea(contours[i]) < minContourArea) continue;

        // Black mask
        Mat1b mask(img.rows, img.cols, uchar(0));
        // Draw filled contour
        drawContours(mask, contours, i, Scalar(255), CV_FILLED);

        mask = (mask & img) ^ mask;

        vector<vector<Point>> cntrs;
        findContours(mask, cntrs, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

        cout << "Number: " << cntrs.size() << endl;

        // Just for showing results
        drawContours(result, cntrs, -1, Scalar(0,0,255), CV_FILLED);
    }

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

    return 0;
}
Miki
  • 40,887
  • 13
  • 123
  • 202
  • Thank you so much Miki! You are the best :) I obtained the mask succesfully, but the findContours inside is not working properly. It always finds 1 contour. So Number = 1. – jeff Oct 07 '15 at 18:41
  • can you post the other image you tried? the thresholded version – Miki Oct 07 '15 at 18:42
  • I added it in the question. It's the same image but I have cropped it before. Now it's the original size. By the way, I changed minContourArea to be 700 since the dice boundaries are around that value. – jeff Oct 07 '15 at 18:47
  • @halilpazarlama You have a gray border of 3 pixels around the image. You need to remove it. I don't know how you get in a thresholded image, but you can just run another threshold with value 220 (since the largest gray value is 217) – Miki Oct 07 '15 at 18:52
  • yes, it actually creates two contours for the outside border. So I select the one that has a parent (i.e. the inner contour) and then proceed to finding contours inside of the inner one. – jeff Oct 07 '15 at 19:05
  • you shouldn't have the outer gray contour, since the thresholded image should be binary. Also findContours work ok only for (true) binary images. – Miki Oct 07 '15 at 19:07
  • I obtained the thresholded image with: `cv::threshold(grayImg, thresholdedImage, 200, 255, 0);` So I was assuming the values are either 0 or 255. I suppose there are gray elements too. Should I set the 4th parameter to 1? – jeff Oct 07 '15 at 19:52
  • @halilpazarlama last parameter is an enum for threshold type. 0 is THRESH_BINARY, and that's ok. You are either doing something after the threshold, or thresholding a submatrix inside the image, because after threshold with THRESH_BINARY the image is guaranteed to be binary – Miki Oct 07 '15 at 20:30
-1

The easier way is findContours method. You find the inner contours and calculate their area( since the inner contours will be holes) and process this information accordingly.

dnit13
  • 2,478
  • 18
  • 35
  • You should provide some code to explain your solution. – Miki Oct 07 '15 at 16:33
  • But I still need a way to determine which contours are "inside", since it finds both the outer and inner contours. – jeff Oct 07 '15 at 16:35
-1

To solve your 1st problem consider you have a set of values in values.Count the occurences of each number that as appeared.

 int m=0;
    for(int n=0;n<256;n++)
    {
        int c=0;
        for(int q=0;q<values.size();q++)
        {
            if(n==values[q])
            {
                //int c;
            c++;
            m++;
            }
        }

        cout<<n<<"= "<< c<<endl;
    }
    cout<<"Total number of elements "<< m<<endl;

To solve your second problem find the largest contour in the image using findcontours, draw bounding rectangle around it and then crop it. Again use the above code to count the pixel value "4" and "0". You can find the link of it here https://stackoverflow.com/a/32998275/3853072

Community
  • 1
  • 1