简体   繁体   中英

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

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 (eg 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.

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.

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

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)

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

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.

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.

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:

在此处输入图像描述

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM