Search code examples
c++opencvimage-segmentationwatershed

OpenCV Watershed segmentation miss some objects


My code is the same as this tutorial. When I see the result image after using cv::watershed(), there is a object(upper-right) that I want to find out, but it's missing. There are indeed six marks in image after using cv::drawContours(). Is this normal because the inaccuracy of the watershed algorithm exist?

Here is part of my code:

Mat src = imread("result01.png");

Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);

Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

// noise removal
Mat kernel = Mat::ones(3, 3, CV_8UC1);
Mat opening;
morphologyEx(thresh, opening, MORPH_OPEN, kernel, Point(-1, -1), 2);

// Perform the distance transform algorithm
Mat dist_transform;
distanceTransform(opening, dist_transform, CV_DIST_L2, 5);

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

// Threshold to obtain the peaks
// This will be the markers for the foreground objects
Mat dist_thresh;
threshold(dist_transform, dist_thresh, 0.5, 1., CV_THRESH_BINARY);

Mat dist_8u;
dist_thresh.convertTo(dist_8u, CV_8U);

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

// Create the marker image for the watershed algorithm
Mat markers = Mat::zeros(dist_thresh.size(), CV_32SC1);

// Draw the foreground markers
for (size_t i = 0; i < contours.size(); i++)
    drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i)+1), -1);

// Perform the watershed algorithm
watershed(src, markers);

Original image:

enter image description here

Result after watershed:

enter image description here

You can find original, intermediate and result image here:

Result images after specific process


Solution

  • In your example, what you consider background is given the same label (5) as the "missing" object.

    You can easily adjust this by setting a label (>0) to background, too. You can find what is for sure background dilating and negating the thresh image. Then, when creating a marker, you define the labels as:

    • 0: unknown
    • 1: background
    • >1 : your objects

    In your output image, markers will have:

    • -1 : the edges between objects
    • 0: the background (as intended by watershed)
    • 1: the background (as you defined)
    • >1 : your objects.

    This code should help:

    #include <opencv2\opencv.hpp>
    #include <vector>
    
    using namespace std;
    using namespace cv;
    
    int main()
    {
        Mat3b src = imread("path_to_image");
    
        Mat1b gray;
        cvtColor(src, gray, COLOR_BGR2GRAY);
    
        Mat1b thresh;
        threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
    
        // noise removal
        Mat1b kernel = getStructuringElement(MORPH_RECT, Size(3,3));
        Mat1b opening;
        morphologyEx(thresh, opening, MORPH_OPEN, kernel, Point(-1, -1), 2);
    
        Mat1b kernelb = getStructuringElement(MORPH_RECT, Size(21, 21));
        Mat1b background;
        morphologyEx(thresh, background, MORPH_DILATE, kernelb);
        background = ~background;
    
        // Perform the distance transform algorithm
        Mat1f dist_transform;
        distanceTransform(opening, dist_transform, CV_DIST_L2, 5);
    
        // Normalize the distance image for range = {0.0, 1.0}
        // so we can visualize and threshold it
        normalize(dist_transform, dist_transform, 0, 1., NORM_MINMAX);
    
        // Threshold to obtain the peaks
        // This will be the markers for the foreground objects
        Mat1f dist_thresh;
        threshold(dist_transform, dist_thresh, 0.5, 1., CV_THRESH_BINARY);
    
        Mat1b dist_8u;
        dist_thresh.convertTo(dist_8u, CV_8U);
    
        // Find total markers
        vector<vector<Point> > contours;
        findContours(dist_8u, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
    
        // Create the marker image for the watershed algorithm
        Mat1i markers(dist_thresh.rows, dist_thresh.cols, int(0));
    
        // Background as 1
        Mat1i one(markers.rows, markers.cols, int(1));
        bitwise_or(one, markers, markers, background);
    
        // Draw the foreground markers (from 2 up)
        for (int i = 0; i < int(contours.size()); i++)
            drawContours(markers, contours, i, Scalar(i+2), -1);
    
        // Perform the watershed algorithm
        Mat3b dbg;
        cvtColor(opening, dbg, COLOR_GRAY2BGR);
        watershed(dbg, markers);
    
        Mat res;
        markers.convertTo(res, CV_8U);
        normalize(res, res, 0, 255, NORM_MINMAX);
    
        return 0;
    }
    

    Result:

    enter image description here