Ask Your Question
1

Efficient way to remove holes in an object

asked 2015-10-26 06:35:32 -0600

vitruvius gravatar image

updated 2015-10-26 07:22:58 -0600

Hello,

I would like to remove holes in an object after I apply threshold function. Right now, I am applying dilate function a few times, then the erode function. It does okay, but in some cases, it changes the shape of the object.

I also tried to use the MORPH_CLOSE, but I guess it is no good against big holes.

I tried with different element size and shape but none of them helped.

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

using namespace cv;
using namespace std;

Mat src, srcGray, srcSmooth, srcThresh;

int morpho_elem = 1; // 0: MORPH_RECT, 1: MORPH_CROSS, 2: MORPH_ELLIPSE
int morpho_size = 3;
int morpho_qty = 9;

int const smooth_kernel = 11;

int main()
{   
    src = imread("source.png", 1);

    cvtColor(src, srcGray, CV_BGR2GRAY);

    for (int i = 1; i < smooth_kernel; i = i + 2)
    {
        medianBlur(srcGray, srcSmooth, i);
    }

    threshold(srcSmooth, srcThresh, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

    int dilation_type;
    if (morpho_elem == 0){ dilation_type = MORPH_RECT; }
    else if (morpho_elem == 1){ dilation_type = MORPH_CROSS; }
    else if (morpho_elem == 2) { dilation_type = MORPH_ELLIPSE; }

    Mat dilationDst, erosionDst;

    Mat element = getStructuringElement(dilation_type,
        Size(2 * morpho_size + 1, 2 * morpho_size + 1),
        Point(morpho_size, morpho_size));

    // Apply the dilation operation
    dilate(srcThresh, dilationDst, element);
    for (int i = 1; i < morpho_qty; i++)
    {
        dilate(dilationDst, dilationDst, element);
    }

    // Apply the erosion operation
    erode(dilationDst, erosionDst, element);
    for (int i = 1; i < morpho_qty; i++)
    {
        erode(erosionDst, erosionDst, element);
    }

    imshow("Final", erosionDst);

    cvWaitKey(0);

    return 0;
}

Source image:

image description

Binary image with holes: image description

Output of my attempt: image description

edit retag flag offensive close merge delete

Comments

i wonder, what is your expectation finally. do you want to detect that there is two rectangular 2d object. and their corner points? also you can try the code here

sturkmen gravatar imagesturkmen ( 2015-10-26 06:42:12 -0600 )edit

@sturkmen In the end, I would like to detect objects in my picture and find their features like length, area etc.

vitruvius gravatar imagevitruvius ( 2015-10-26 07:10:07 -0600 )edit

3 answers

Sort by ยป oldest newest most voted
3

answered 2015-10-26 10:14:41 -0600

theodore gravatar image

updated 2015-10-26 10:22:28 -0600

besides @LorenaGdL 's approach what you could do in a simpler way is after you have your binary image apply floodFill() with a mask check the following code:

Mat bin;
threshold(gray_image, bin, 50, 255, THRESH_BINARY | THRESH_OTSU);

// A image with size greater than the present object is created, it is needed from floodFill()
cv::Mat mask = cv::Mat::zeros(src.rows + 2, src.cols + 2, CV_8U);

cv::floodFill(bin, mask, cv::Point(0,0), 255, 0, cv::Scalar(), cv::Scalar(), 4 + (255 << 8) + cv::FLOODFILL_MASK_ONLY);
//NOTE Since the mask is larger than the filled image, a pixel  (x, y) in image corresponds to the pixel (x+1, y+1) in the mask .

//remove the extra rows/cols added earlier in the initialization of the mask, if you want of course it is just "optional"
Mat dst;
mask(Range(1, mask.rows - 1), Range(1, mask.cols-1)).copyTo(dst);

Result:

image description

edit flag offensive delete link more

Comments

This is indeed simpler yet effective. Thank you.

vitruvius gravatar imagevitruvius ( 2015-10-26 15:47:29 -0600 )edit

But in this case, you need to "manually" set the seed point

LorenaGdL gravatar imageLorenaGdL ( 2015-10-27 04:26:54 -0600 )edit

for that reason you are creating the mask larger than the source image, and then you can remove these extra rows/cols.

theodore gravatar imagetheodore ( 2015-10-27 05:10:00 -0600 )edit

Oh I see, interesting

LorenaGdL gravatar imageLorenaGdL ( 2015-10-27 05:18:56 -0600 )edit
4

answered 2015-10-26 07:16:08 -0600

LorenaGdL gravatar image

updated 2015-10-26 07:17:05 -0600

If you're sure you'll only have inner holes inside a well defined external object contour, then one option is to make use of findcontours(), taking advantage of hierarchy:

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

using namespace cv;
using namespace std;

int const smooth_kernel = 11;

int main()
{
    Mat src, srcGray, srcSmooth, srcThresh;
    src = imread("source.png", 1);

    cvtColor(src, srcGray, CV_BGR2GRAY);

    for (int i = 1; i < smooth_kernel; i = i + 2)
    {
        medianBlur(srcGray, srcSmooth, i);
    }

    threshold(srcSmooth, srcThresh, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

    //retrieve only external contours (also you can retrieve list and use hierarchy)
    vector<vector<Point>> contours;
    findContours(srcThresh, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
    Mat result(src.rows, src.cols, CV_8UC1, Scalar(0));
    drawContours(result, contours, 0, Scalar(255), -1);     //draw with filling

    imshow("Final", result);
    waitKey(0);

    return 0;
}

image description

edit flag offensive delete link more

Comments

I can't be sure about that, as there will be different background and objects, but this is a nice approach. It will probably work for my application. So I accept it as an answer :)

vitruvius gravatar imagevitruvius ( 2015-10-26 07:34:00 -0600 )edit

As long as you do the threshold for binarization, too, it should be the same idea... so +1

thdrksdfthmn gravatar imagethdrksdfthmn ( 2015-10-26 07:52:45 -0600 )edit
0

answered 2015-10-26 10:14:10 -0600

updated 2015-10-26 10:16:08 -0600

i think an addition to @LorenaGdL 's answer may be good. It is squares.cpp. you can try finding many squares objects.

Altough no need to any modification to find squares objects on your image, I made a small modification as below to get this Image :

image description

hope it helps.

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <iostream>

using namespace cv;
using namespace std;

int thresh = 50, N = 1;
const char* wndname = "Object Detection Demo";

// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2
static double angle( Point pt1, Point pt2, Point pt0 )
{
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

// returns sequence of squares detected on the image.
// the sequence is stored in the specified memory storage
static void findSquares( const Mat& image, vector<vector<Point> >& squares )
{
    squares.clear();

    Mat pyr, timg, gray0(image.size(), CV_8U), gray;

    // down-scale and upscale the image to filter out the noise
    pyrDown(image, pyr, Size(image.cols/2, image.rows/2));
    pyrUp(pyr, timg, image.size());
    vector<vector<Point> > contours;

    // find squares in every color plane of the image
    for( int c = 0; c < 3; c++ )
    {
        int ch[] = {c, 0};
        mixChannels(&timg, 1, &gray0, 1, ch, 1);

        // try several threshold levels
        for( int l = 0; l < N; l++ )
        {
            // hack: use Canny instead of zero threshold level.
            // Canny helps to catch squares with gradient shading
            if( l == 0 )
            {
                // apply Canny. Take the upper threshold from slider
                // and set the lower to 0 (which forces edges merging)
                Canny(gray0, gray, 0, thresh, 5);
                // dilate canny output to remove potential
                // holes between edge segments
                dilate(gray, gray, Mat(), Point(-1,-1));
            }
            else
            {
                // apply threshold if l!=0:
                //     tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
                gray = gray0 >= (l+1)*255/N;
            }

            // find contours and store them all as a list
            findContours(gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);

            vector<Point> approx;

            // test each contour
            for( size_t i = 0; i < contours.size(); i++ )
            {
                // approximate contour with accuracy proportional
                // to the contour perimeter
                approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);

                // square contours should have 4 vertices after approximation
                // relatively large area (to filter out noisy contours)
                // and be convex.
                // Note: absolute value of an area is used because
                // area may be positive or negative - in accordance with the
                // contour orientation
                if( approx.size() == 4 &&
                    fabs(contourArea(Mat(approx))) > 1000 &&
                    isContourConvex(Mat(approx)) )
                {
                    double maxCosine = 0;

                    for( int j = 2; j < 5; j++ )
                    {
                        // find the maximum cosine of the angle between joint edges
                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                        maxCosine = MAX(maxCosine, cosine);
                    }

                    // if cosines of all angles are small
                    // (all angles are ~90 degree) then write quandrange
                    // vertices to resultant sequence
                    if( maxCosine < 0.3 )
                        squares.push_back(approx);
                }
            }
        }
    }
}


// the function draws all the squares in ...
(more)
edit flag offensive delete link more

Question Tools

1 follower

Stats

Asked: 2015-10-26 06:35:32 -0600

Seen: 7,856 times

Last updated: Oct 26 '15