Ask Your Question
0

Count number of colored objects in image

asked 2015-12-17 11:06:58 -0600

shaun gravatar image

updated 2015-12-31 06:45:17 -0600

****** EDIT-2 *******

Based on the comment from Tetragramm, I have simplified the connect_components function. However, I am still getting the same exception:

3, 0
8UC3
OpenCV Error: Assertion failed (L.channels() == 1 && I.channels() == 1) in connectedComponents_sub1, file /source/opencv-3.0.0/modules/imgproc/src/connectedcomponents.cpp, line 341
Exception caught
/source/opencv-3.0.0/modules/imgproc/src/connectedcomponents.cpp:341: error: (-215) L.channels() == 1 && I.channels() == 1 in function connectedComponents_sub1

Maybe I shouldn't use the connectedComponenets function and go at it by the old way (which I'm not sure how to do, so an example would be great!)?

****** EDIT-1 *******

Sorry for the delayed response. Being new to OpenCV I did some research and found out that a connected components function was introduced in OpenCV 3.0 and it is only available in C++ (AFAIK) as shown here. So, I decided to to write my code in C++. I have also updated the test image (I would like to make it work on this before I got to video stream). Here is my code:

#include <iostream>
#include <vector>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;   


cv::Mat connect_components(cv::Mat img, int threshval)
{
    cv::Mat bw = threshval < 128 ? (img < threshval) : (img > threshval);
    cv::Mat labelImage(img.size(), CV_16U);
    auto nLabels = cv::connectedComponents(bw, labelImage, 8);
    bw = labelImage >= 1;
    cv::Mat bwArray[3] = {bw, bw, bw};
    cv::Mat dst(img.size(), CV_8UC3);
    cv::merge(bwArray, 3, dst);
    return dst;
}

int main(int argc, char **argv)
{
    cv::VideoCapture webcam(0);

    if (!webcam.isOpened()) {
        cerr << "Cannot open webcam" << endl;
        exit(-1);
    }

    // Define range for blue and red color in HSV
    const cv::Scalar blue_range[] = { {110, 50, 50}, {130, 255, 255} };
    const cv::Scalar red_range[] = { {0, 200, 50}, {10, 255, 150} };

    cout << CV_MAJOR_VERSION << ", " << CV_MINOR_VERSION << endl;

    cv::Mat original_frame, color_mask[2], final_mask;
    while (true) {
        try {
            webcam.read(original_frame); // grab each frame
            cv::Mat frame_hsv;
            cv::cvtColor(original_frame, frame_hsv, CV_BGR2HSV); // convert BGR to HSV

            // threshold HSV image to get only defined range colors
            cv::inRange(frame_hsv, blue_range[0], blue_range[1], color_mask[0]); 
            cv::inRange(frame_hsv, red_range[0], red_range[1], color_mask[1]);

            // add the masks
            final_mask = color_mask[0] + color_mask[1];

            // Bitwise-AND full mask and original image
            cv::Mat colored_boxes;
            cv::bitwise_and(original_frame, original_frame, colored_boxes, final_mask);

            // Apply morphological closing
            cv::Mat morphed_boxes;
            cv::morphologyEx(colored_boxes, morphed_boxes, cv::MORPH_CLOSE, cv::Mat::ones(10, 10, CV_8UC1));

            // Connected componenets
            auto labeled_boxes = connect_components(colored_boxes, 62);

            // Display the image
            //cv::imshow("Webcam", original_frame);
            cv::imshow("Boxes", morphed_boxes);

            // quit progrem is user inputs ESC or q
            auto user_input = (cv::waitKey(5) & 0xFF);
            if ((user_input == 27) || (user_input == 113)) {
                break;
            }
        }
        catch (cv::Exception &e) {
            cout << "Exception caught" << endl;
            cout << e.what();
            break;
        }
    }

    return 0;
}

The connected_components function is adopted from here. When I run this, I get the following exception:

3, 0
8UC3
OpenCV Error: Assertion failed (L.channels() == 1 && I.channels() == 1) in connectedComponents_sub1, file /source/opencv-3.0.0/modules/imgproc/src/connectedcomponents.cpp, line 341
Exception caught
/source/opencv-3.0.0/modules/imgproc/src/connectedcomponents.cpp ...
(more)
edit retag flag offensive close merge delete

Comments

What you can do is change the type of labelImage

cv::Mat labelImage(img.size(), CV_8S);

to

cv::Mat labelImage(img.size(), CV_16U);

You can also simplifiy your code a bit. I'm assuming you had more functionality there and moved it out of the function as opposed to preparing to add more. If you're adding more, with varying colors and such, just ignore this part.

cv::Mat connect_components(cv::Mat img, int threshval)
{
    cv::Mat bw = threshval < 128 ? (img < threshval) : (img > threshval);
    cv::Mat labelImage(img.size(), CV_16U);
    int nLabels = cv::connectedComponents(bw, labelImage, 8);
    bw = labelImage >= 1;
    cv::Mat bwArray[3] = {bw, bw, bw};
    cv::Mat dst(img.size(), CV_8UC3);
    cv::merge(bwArray, 3, dst);
    return dst;
}
Tetragramm gravatar imageTetragramm ( 2015-12-31 05:16:20 -0600 )edit

@Tetragramm, thank you for your help. I have updated my OP.

shaun gravatar imageshaun ( 2015-12-31 06:45:53 -0600 )edit

Ah, I see now. You're passing in img as a CV_8UC3. Sorry, I read this just after waking up.

So at some point you need to flatten that to one channel. I suggest just passing the final_mask to the morphology operation, and the output from that into connected components.

Inside connect_components, modify it so it removes components that don't meet your criteria (too small, bad aspect ratio, whatever) before you create the three channel mask. Then do the bitwise and with the original image.

Tetragramm gravatar imageTetragramm ( 2015-12-31 10:53:34 -0600 )edit

1 answer

Sort by ยป oldest newest most voted
0

answered 2015-12-17 17:54:57 -0600

Tetragramm gravatar image

Ok, can you guarantee they are above a certain size? Can you guarantee that they are distinct and well separated, like they are in that image? Then it's easy.

  • Take your mask, and do a morphological Close. This will fill in a lot of the holes in the boxes. The idea is to make each of them one connected blob. It doesn't look like any of them are split, but it's not impossible with the noise I see in that image.
  • Perform Connected Components. Now you have another mask, with numeric labels.
  • Get rid of any below an appropriate size. Just a guess, but 10x10 bounding box should cover it.
  • Count remaining. Either do the above twice, once on red and once on blue, or compare your final results with the individual ones.

If you have a red and a blue touching, you should do it twice, to separate the two when you do connected components.

Note: If you do connectedcomponentswithstats, you get the bounding box and area as a return value and you don't even need to look at the map.

If the assumptions don't hold, it gets more complicated, but based on your sample image, this should work. If you need it to run really fast, there are possibly some shortcuts to take. But this is a good start, see if it works.

edit flag offensive delete link more

Question Tools

1 follower

Stats

Asked: 2015-12-17 11:06:58 -0600

Seen: 2,970 times

Last updated: Dec 31 '15