Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

A basic idea based on morphological operations and distance transformation can be seen in the example below. For sure you will need to test it further in order to see how much it fits to all of your use cases. My basic assumption is that in order to extract an object you need to use a feature that is quite significant in each object. The feature that I used is the trunk of each object which I find it to be the strongest. Moreover, you can use it for getting the orientation of each object as well. So, at the end you do not need to detect the full object in order to obtain the information that you want, but only part of it.

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
    // Load your image
    cv::Mat src = cv::imread("blobs.png");

    // Check if everything was fine
    if (!src.data)
        return -1;

    // Show source image
    cv::imshow("src", src);

image description

    // Create binary image from source image
    cv::Mat gray;
    cv::cvtColor(src, gray, CV_BGR2GRAY);
//    cv::imshow("gray", gray);

    // Obtain binary image
    Mat bw;
    cv::threshold(gray, bw, 40, 255, CV_THRESH_BINARY_INV | CV_THRESH_OTSU);
    cv::imshow("bin", bw);

image description

    // Erode a bit
    Mat kernel = Mat::ones(3, 3, CV_8UC1);
    erode(bw, bw, kernel);
//    imshow("erode", bw);

    // Perform the distance transform algorithm
    Mat dist;
    distanceTransform(bw, dist, CV_DIST_L2, 5);

    // Normalize the distance image for range = {0.0, 1.0}
    // so we can visualize and threshold it
    normalize(dist, dist, 0, 1., NORM_MINMAX);
    imshow("distTransf", dist);

image description

    // Threshold to obtain the peaks
    // This will be the markers for the foreground objects
    threshold(dist, dist, .5, 1., CV_THRESH_BINARY);

    // Dilate a bit the dist image, this can be optional since in
    // other use case might cause problems. Here though it works quite well
    Mat kernel1 = Mat::ones(5, 5, CV_8UC1);
    dilate(dist, dist, kernel1, Point(-1, -1), 2);
    imshow("peaks", dist);

image description

    // Create the CV_8U version of the distance image
    // It is needed for findContours()
    Mat dist_8u;
    dist.convertTo(dist_8u, CV_8U);

    // Find total markers
    vector<Vec4i> hierarchy;
    vector<vector<Point> > contours;
    findContours(dist_8u, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);

    // Find the rotated rectangles
    vector<RotatedRect> minRect( contours.size() );

    for( size_t i = 0; i < contours.size(); i++ )
    {
        minRect[i] = minAreaRect( Mat(contours[i]) );
    }

    RNG rng(12345);
    for( size_t i = 0; i< contours.size(); i++ )
    {
        Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
        // contour
        drawContours( src, contours, static_cast<int>(i), color, 1, 8, vector<Vec4i>(), 0, Point() );
        // rotated rectangle
        Point2f rect_points[4]; minRect[i].points( rect_points );
        for( int j = 0; j < 4; j++ )
            line( src, rect_points[j], rect_points[(j+1)%4], color, 1, 8 );
    }

/* From here you can extract the orientation of each object by using
 * the information that you can extract from the contours and the
 * rotate rectangles. For example, the center point, rectange angle, etc...
 */

    cv::imshow("result", src);

image description

    waitKey(0);
    return 0;
}

I do not know how close is this to what you want but I hope that it could help. For the orientation you can also use the information from the contours in addition with a PCA (i.e. Principal Component Analysis) procedure as it is described here.