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

﹥>﹥吖頭↗ 提交于 2019-12-18 11:27:42

问题


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:

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 :

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

Epsilon=6

Epsilon=12

Epsilon=24

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

k=53

k=103

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.


回答1:


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.




回答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).




回答3:


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)




回答4:


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).




回答5:


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.




回答6:


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.




回答7:


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:



来源:https://stackoverflow.com/questions/11925777/opencv-how-to-use-arrays-of-points-for-smoothing-and-sampling-contours

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!