OpenCV, how to use arrays of points for smoothing and sampling contours?

Quentin Geissmann picture Quentin Geissmann · Aug 12, 2012 · Viewed 15.9k times · Source

I have a problem to get my head around smoothing and sampling contours in OpenCV (C++ API). Lets say I have got sequence of points retrieved from cv::findContours (for instance applied on this this image:

enter image description here

Ultimately, I want

  1. To smooth a sequence of points using different kernels.
  2. To resize the sequence using different types of interpolations.

After smoothing, I hope to have a result like :

enter image description here

I also considered drawing my contour in a cv::Mat, filtering the Mat (using blur or morphological operations) and re-finding the contours, but is slow and suboptimal. So, ideally, I could do the job using exclusively the point sequence.

I read a few posts on it and naively thought that I could simply convert a std::vector(of cv::Point) to a cv::Mat and then OpenCV functions like blur/resize would do the job for me... but they did not.

Here is what I tried:

int main( int argc, char** argv ){

    cv::Mat conv,ori;
    ori=cv::imread(argv[1]);
    ori.copyTo(conv);
    cv::cvtColor(ori,ori,CV_BGR2GRAY);

    std::vector<std::vector<cv::Point> > contours;
    std::vector<cv::Vec4i > hierarchy;

    cv::findContours(ori, contours,hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);

    for(int k=0;k<100;k += 2){
        cv::Mat smoothCont;

        smoothCont = cv::Mat(contours[0]);
        std::cout<<smoothCont.rows<<"\t"<<smoothCont.cols<<std::endl;
        /* Try smoothing: no modification of the array*/
//        cv::GaussianBlur(smoothCont, smoothCont, cv::Size(k+1,1),k);
        /* Try sampling: "Assertion failed (func != 0) in resize"*/
//        cv::resize(smoothCont,smoothCont,cv::Size(0,0),1,1);
        std::vector<std::vector<cv::Point> > v(1);
        smoothCont.copyTo(v[0]);
        cv::drawContours(conv,v,0,cv::Scalar(255,0,0),2,CV_AA);
        std::cout<<k<<std::endl;
        cv::imshow("conv", conv);
        cv::waitKey();
    }
    return 1;
}

Could anyone explain how to do this ?

In addition, since I am likely to work with much smaller contours, I was wondering how this approach would deal with border effect (e.g. when smoothing, since contours are circular, the last elements of a sequence must be used to calculate the new value of the first elements...)

Thank you very much for your advices,

Edit:

I also tried cv::approxPolyDP() but, as you can see, it tends to preserve extremal points (which I want to remove):

Epsilon=0

enter image description here

Epsilon=6

enter image description here

Epsilon=12

enter image description here

Epsilon=24

enter image description here

Edit 2: As suggested by Ben, it seems that cv::GaussianBlur() is not supported but cv::blur() is. It looks very much closer to my expectation. Here are my results using it:

k=13

enter image description here

k=53

enter image description here

k=103

enter image description here

To get around the border effect, I did:

    cv::copyMakeBorder(smoothCont,smoothCont, (k-1)/2,(k-1)/2 ,0, 0, cv::BORDER_WRAP);
    cv::blur(smoothCont, result, cv::Size(1,k),cv::Point(-1,-1));
    result.rowRange(cv::Range((k-1)/2,1+result.rows-(k-1)/2)).copyTo(v[0]);

I am still looking for solutions to interpolate/sample my contour.

Answer

Ben picture Ben · Aug 13, 2012

Your Gaussian blurring doesn't work because you're blurring in column direction, but there is only one column. Using GaussianBlur() leads to a "feature not implemented" error in OpenCV when trying to copy the vector back to a cv::Mat (that's probably why you have this strange resize() in your code), but everything works fine using cv::blur(), no need to resize(). Try Size(0,41) for example. Using cv::BORDER_WRAP for the border issue doesn't seem to work either, but here is another thread of someone who found a workaround for that.

Oh... one more thing: you said that your contours are likely to be much smaller. Smoothing your contour that way will shrink it. The extreme case is k = size_of_contour, which results in a single point. So don't choose your k too big.