【问题标题】:OpenCV, how to use arrays of points for smoothing and sampling contours?OpenCV,如何使用点数组来平滑和采样轮廓?
【发布时间】:2012-08-09 04:17:51
【问题描述】:

在 OpenCV (C++ API) 中,我很难理解平滑和采样轮廓。 假设我已经从cv::findContours 检索到点序列(例如应用于此图像:

最终,我想要

  1. 使用不同的内核平滑一系列点。
  2. 使用不同类型的插值调整序列大小。

平滑后,我希望有这样的结果:

我还考虑在cv::Mat 中绘制我的轮廓,过滤 Mat(使用模糊或形态学操作)并重新找到轮廓,但速度很慢且次优。所以,理想情况下,我可以只使用点序列来完成这项工作。

我阅读了一些关于它的帖子,并天真地认为我可以简单地将std::vectorcv::Point)转换为cv::Mat,然后像模糊/调整大小这样的OpenCV函数就可以为我完成这项工作......但他们没有。

这是我尝试过的:

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;
}

谁能解释一下如何做到这一点?

此外,由于我可能使用更小的轮廓,我想知道这种方法将如何处理边界效果(例如,在平滑时,由于轮廓是圆形的,因此必须使用序列的最后一个元素来计算第一个元素的新值...)

非常感谢您的建议,

编辑:

我也尝试过cv::approxPolyDP(),但正如您所见,它倾向于保留极值点(我想删除):

Epsilon=0

Epsilon=6

Epsilon=12

Epsilon=24

编辑 2: 正如 Ben 所建议的,似乎不支持 cv::GaussianBlur(),但支持 cv::blur()。它看起来非常接近我的预期。这是我使用它的结果:

k=13

k=53

k=103

为了绕过边框效果,我做了:

    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]);

我仍在寻找对我的轮廓进行插值/采样的解决方案。

【问题讨论】:

  • 任何想法将轮廓调整为固定长度?

标签: c++ opencv contour sampling


【解决方案1】:

您的高斯模糊不起作用,因为您在列方向上模糊,但只有一列。尝试将向量复制回cv::Mat 时,使用GaussianBlur() 会导致OpenCV 中出现“功能未实现”错误(这可能就是为什么你的代码中有这个奇怪的resize()),但使用@987654325 一切正常@,不用resize()。例如,尝试大小(0,41)。使用cv::BORDER_WRAP 解决边界问题似乎也不起作用,但here 是另一个找到解决方法的人的线索。

哦……还有一件事:你说你的轮廓可能要小得多。以这种方式平滑你的轮廓会缩小它。极端情况是k = size_of_contour,这会导致单点。所以不要选择你的 k 太大。

【讨论】:

  • 非常感谢,我现在就试试看!调整大小适用于我的问题的 2.“使用不同类型的插值调整序列大小”。例如,如果我有一个包含 1000 个点的轮廓,我想对其进行采样,使其具有 500 个点或 2000 个点(例如使用线性插值)。我以为我可以在 smoothCont 上应用 resize 来做到这一点......如果不能,你认为我能做到。
  • 我实施了您的建议(请参阅我的编辑)。模糊似乎“足够好”。如果没有人很快提供采样点序列的解决方案,我会接受你的回答。再次感谢你:)
  • 有没有办法调整轮廓大小?
【解决方案2】:

另一种可能性是使用 openFrameworks 使用的算法:

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

它遍历轮廓并使用其周围的点应用低通滤波器。应该以低开销和(没有理由对本质上只是轮廓的图像进行大过滤)做你想要的。

【解决方案3】:

approxPolyDP()怎么样?

它使用this 算法来“平滑”轮廓(基本上摆脱了轮廓的大部分点,并留下那些代表轮廓的良好近似值的点)

【讨论】:

  • 谢谢,根据文档,approxPolyDP() 正在实现 Ramer–Douglas–Peucker 算法。据我所知,它通过拒绝点来简化曲线,这可能导致曲线实际上比原始曲线更清晰。
  • 平滑度取决于 epsilon 值。如果该值非常大,您的轮廓可能会退化为三角形。如果它很小,您只需消除噪音并基本保持轮廓的形状。我不知道你到底需要什么,但如果你想让它非常平滑,你可以用 approxPoly() 来近似你的轮廓,并将剩余的点用作贝塞尔曲线的控制点。
  • 嘿,我刚刚用图片编辑了我的问题,以解释为什么使用此功能进行平滑会给我带来问题。
【解决方案4】:

来自 2.1 OpenCV 文档部分Basic Structures

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

您可能希望将第二个参数设置为true

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

再试一次(这样cv::GaussianBlur应该可以修改数据了)。

【讨论】:

  • 谢谢,我刚刚尝试了你的建议,但是从轮廓 [0] 中硬拷贝数据并没有帮助。行为没有改变(没有 GaussianBlur 的影响并且无法调整大小)。
  • 稍后我会运行一些代码来查看问题。顺便说一句,对于您图像中的效果,我认为“侵蚀和膨胀”(打开和关闭)可能会起作用,至少在某些情况下,具有良好的参数。 (或在“approxPolyDP”之前使用以去除细线的伪影)
  • 非常感谢,谢谢。至于侵蚀/膨胀,它会起作用,但就我而言,我必须 1:找到轮廓。 2:计算我想要平滑它们的程度(从它们的大小、面积、周长......)。 3:画出来。 4:进行形态学运算。 5:在我可以直接计算平滑轮廓时再次找到它们。
【解决方案5】:

我知道这是很久以前写的,但是您是否尝试过大腐蚀,然后大扩张(开口),然后找到计数?它看起来是一个简单而快速的解决方案,但我认为它至少在某种程度上是可行的。

【讨论】:

    【解决方案6】:

    基本上,轮廓的突然变化对应于高频内容。平滑轮廓的一种简单方法是找到傅立叶系数,假设坐标形成复平面 x + iy,然后消除高频系数。

    【讨论】:

      【解决方案7】:

      我的看法...多年后...!

      也许有两种简单的方法:

      • 使用扩张、模糊、腐蚀循环几次。并找到该更新形状的轮廓。我发现 6-7 次效果很好。
      • 创建轮廓的边界框,并在有界矩形内绘制一个椭圆。

      在下面添加可视化结果:

      【讨论】:

        【解决方案8】:

        这适用于我。边缘比以前更平滑:

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

        这是 opencv4android 代码。

        【讨论】:

          猜你喜欢
          • 2016-09-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-26
          • 2013-06-11
          相关资源
          最近更新 更多