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

8

14

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.

Reckless answered 12/8, 2012 at 20:48 Comment(1)
Any idea with resizing the contour to fixed length?Tsingyuan
S
2

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.

Susannsusanna answered 13/8, 2012 at 13:0 Comment(3)
Thank you very much, I will give that a go now ! The resize is for the 2. "To resize the sequence using different types of interpolations" of my question. For instance, if I have a contour that has 1000 points, I would like to sample it so it has 500 points or 2000 points (using linear interpolation for instance). I thought I could do that applying resize on smoothCont... If not, how do you think I could do.Reckless
I implemented your suggestion (see my edit). Blur seems to be "good-enough". If no one comes with a solution to sample sequences of points soon, I will accept you answer. Thank you again :)Reckless
Is there any solution for resizing the contour?Tsingyuan
C
2

Another possibility is to use the algorithm openFrameworks uses:

https://github.com/openframeworks/openFrameworks/blob/master/libs/openFrameworks/graphics/ofPolyline.cpp#L416-459

It traverses the contour and essentially applies a low-pass filter using the points around it. Should do exactly what you want with low overhead and (there's no reason to do a big filter on an image that's essentially just a contour).

Chantel answered 6/1, 2015 at 18:36 Comment(1)
Here you go! github.com/openframeworks/openFrameworks/blob/master/libs/…Chantel
S
1

How about approxPolyDP()?

It uses this algorithm to 'smooth' a contour (basically gettig rid of most of the contour's points and leave the ones that represent a good approximation of your contour)

Susannsusanna answered 13/8, 2012 at 9:30 Comment(3)
Thank you, according to the doc, approxPolyDP() is implementing Ramer–Douglas–Peucker algorithm. As far as I am aware, it simplifies the curve by rejecting points which can result in a curve that is actually sharper that the original.Reckless
The smoothness depends on the epsilon value. If the value is very big, your contour might degenerate to a triangle. If it is small, you just get rid of the noise and basically keep the contour's shape. I don't know what you need exactly, but if you want it very smooth, you could approximate your contour with approxPoly() and use the remaining points as control points for a bezier curve.Susannsusanna
Hey, I just edited my question with pictures to explains why smoothing using this function causes problem to me.Reckless
P
0

From 2.1 OpenCV doc section Basic Structures:

template<typename T>
explicit Mat::Mat(const vector<T>& vec, bool copyData=false)

You probably want to set 2nd param to true in:

smoothCont = cv::Mat(contours[0]);

and try again (this way cv::GaussianBlur should be able to modify the data).

Paff answered 13/8, 2012 at 9:24 Comment(3)
Thank you, I just tried you suggestion, but hard-copying the data from contours[0] did not help. The behaviour is unchanged (no effect of GaussianBlur and cannot resize).Reckless
I will get some code running later to see the problem. BTW, for the effect in your image, I think "erosion and dilation" (opening and closing) might work, at least for some case, with good params. (or be used before "approxPolyDP" to remove the artifact of that thin line)Paff
I would appreciate this a lot, thank you. As for erosion / dilatation, it would will work, but in my case, I would have to 1: find contours. 2: calculate how much I want to smooth them (from their size, area, perimeter, ...). 3: draw them. 4: perform morphological operations. 5: find them again while I could just calculate a smooth contour directly.Reckless
A
0

I know this was written a long time ago, but did you tried a big erode followed by a big dilate (opening), and then find the countours? It looks like a simple and fast solution, but I think it could work, at least to some degree.

Arabist answered 11/5, 2013 at 18:26 Comment(0)
D
0

Basically the sudden changes in contour corresponds to high frequency content. An easy way to smooth your contour would be to find the fourier coefficients assuming the coordinates form a complex plane x + iy and then by eliminating the high frequency coefficients.

Dulciedulcify answered 26/8, 2015 at 14:23 Comment(0)
O
0

My take ... many years later ...!

Maybe two easy ways to do it:

  • loop a few times with dilate,blur,erode. And find the contours on that updated shape. I found 6-7 times gives good results.
  • create a bounding box of the contour, and draw an ellipse inside the bounded rectangle.

Adding the visual results below:

enter image description here

Olia answered 25/7, 2017 at 6:21 Comment(0)
A
0

This applies to me. The edges are smoother than before:

medianBlur(mat, mat, 7)
morphologyEx(mat, mat, MORPH_OPEN, getStructuringElement(MORPH_RECT, Size(12.0, 12.0)))
val contours = getContours(mat)

This is opencv4android code.

Ammamaria answered 24/12, 2019 at 12:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.