如何使用代表点对复杂形状进行建模?

时间:2017-05-26 10:22:44

标签: opencv image-processing connected-components

我想将此图像中的白色像素数量减少到输出图像中的某些候选点或代表点(目标是为不同类型的形状建模)

Input Image

Output Image

如果您只是将输出图像中的灰点连接在一起,则路径相同但白色像素较少。此路径应该只有一个起点和一个终点,并涵盖从开始到结束的所有路径。

我可以使用CCA(连通分量分析)和一些其他规则来解决它!但似乎很慢。

我需要这个算法来减少描述形状所需的像素数量。

这里最快,最准确的算法是什么?

我也欢迎那些可以通过增加候选点来提高形状建模精度的方法。

3 个答案:

答案 0 :(得分:5)

  • 对形状进行骨架化以获得单像素宽的路径(请参阅https://en.wikipedia.org/wiki/Topological_skeleton

  • 将路径表示为像素链。

  • 沿路径选择多个像素,有规律地间隔。

  • 通过这些点生成一个Cardinal样条线(参见https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline)。也可以使用三次样条。

  • 对于样条曲线的每个部分,估计图像中的路径与曲线之间的偏差。这可以通过沿曲线采样几个点并找到路径部分上的最近点(通过尝试所有像素)来完成。

  • 当偏差过大时,请在该部分添加一个或多个像素。

  • 重新计算整个样条曲线并重复,直到您不再需要插入点。

    [1]: https://i.stack.imgur.com/1IA enter image description here

通过调整偏差阈值,您可以交换曲线平滑度以获得匹配精度。

可以避免重新计算没有发生点插入的曲线,但这需要一些小心。

答案 1 :(得分:4)

我尝试使用approxPolyDP用折线近似轮廓。当形状闭合时,这很简单并且始终产生良好的近似。但是当形状打开时,就像所讨论的样本图像一样,有时不保持近似点的排序(我在下面给出了一个例子)。很难描述该方法的每个细节,所以我尽可能地评论了c++代码。希望这会有所帮助。

折线近似点处的渐变方向(红点是折线近似点,在相反的渐变方向上偏移一些量,蓝点在渐变方向上):

grad

最终近似点(红色斑点内的一个点)和导航顺序(蓝色箭头)

approx

示例形状和渐变方向。注意闭合形状和开放形状之间的区别。对于闭合形状,红点都位于笔划的一侧。对于开放形状,红点位于笔划的两侧。

misc

左上角的形状导致导航顺序错误,因为轮廓不是从形状的尖端开始。但是,它有一个很好的逐点逼近。

shape approx

int _tmain(int argc, _TCHAR* argv[])
{
    Mat im = imread("1.png", 0);
    Mat roi = Mat::zeros(im.size(), CV_8UC1);
    /* find the gradient at every point */
    Mat dx, dy;
    Sobel(im, dx, CV_32F, 1, 0, 7);
    Sobel(im, dy, CV_32F, 0, 1, 7);
    /* take the distance transform */
    Mat dist;
    distanceTransform(im, dist, CV_DIST_L2, 5);
    /* max stroke radius */
    double th;
    minMaxLoc(dist, NULL, &th);

    /* for display/debug purposes */
    Mat rgb = Mat::zeros(im.size(), CV_8UC3);
    Mat rgb2;
    cvtColor(im, rgb2, CV_GRAY2BGR);
    Mat rgb3 = Mat::zeros(im.size(), CV_8UC3);

    Mat tmp = im.clone();
    // find contours, get every point with CV_CHAIN_APPROX_NONE
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(tmp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE, Point(0, 0));

    for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
    {
        /* draw contours */
        drawContours(rgb, contours, idx, Scalar(0, 255, 255), 2, 8);
        drawContours(rgb2, contours, idx, Scalar(0, 255, 255), 1, 8);
        drawContours(rgb3, contours, idx, Scalar(0, 255, 255), 1, 8);

        /* polyline approximztion of the contour */
        vector<Point> poly;
        approxPolyDP(contours[idx], poly, th, false);
        /* 
            now we'll sample the gradient along the points in the polyline, 
            find another opposite point in the coitour in the gradient direction, 
            then find the peak location in the distance image (looks like the
            mid point should also work, but I didn't try it).
        */
        for (Point& pt: poly)
        {
            /* sample dx, dy at our point of interest */
            float x = dx.at<float>(pt);
            float y = dy.at<float>(pt);
            float n = sqrtf(x*x + y*y);
            /*  
                select another point in the direction of the gradient that intersects the stroke:
                by choosing a point that's around 2.5 times the max stroke radius, we hope
                to cross the stroke with this line 
            */
            Point pt2(pt.x + 2.5*th*x/n, pt.y + 2.5*th*y/n);
            /* offset the first point a bit in the opposite gradient direction */
            Point pt1(pt.x - .5*th*x/n, pt.y - .5*th*y/n);
            /* draw a thick line */
            line(roi, pt1, pt2, Scalar(255, 255, 255), 2, 8);
            /*
                display the points
            */
            line(rgb3, pt1, pt2, Scalar(255, 255, 255), 2, 8);
            line(rgb2, pt1, pt2, Scalar(0, 255, 255), 2, CV_AA);
            circle(rgb2, pt1, 3, Scalar(0, 0, 255), -1, CV_AA);
            circle(rgb2, pt2, 3, Scalar(255, 0, 0), -1, CV_AA);
        }   

        /* dilate the lines so that nearby lines can merge */
        Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
        morphologyEx(roi, roi, CV_MOP_DILATE, kernel, Point(-1, -1), 1);
        /* only for debug */
        morphologyEx(rgb3, rgb3, CV_MOP_DILATE, kernel, Point(-1, -1), 1);
        /* we are interested in lines segments that are within the shape */
        roi &= im;
        /* collect all these line segments */
        vector<vector<Point>> regions;
        findContours(roi, regions, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
        /* 
            now that we have all the info about lines segments, 
            we can use the image for other purposes such as a mask
        */
        roi.setTo(Scalar(0, 0, 0));
        /* our points of interest when we approximate the shape */
        vector<Point> shapeApprox;
        /*
            for each point on the shape contour, see if it is within a line segment
            that we found using gradients. if so, find the peak location from the distance image.
            it is a point in the skeleton
        */
        for (Point& pt: contours[idx])
        {
            for (size_t i = 0; i < regions.size(); i++)
            {
                if (-1 != pointPolygonTest(regions[i], pt, false))
                {
                    /* using roi as a mask to find the peak location from distance image */
                    drawContours(roi, regions, i, Scalar(255, 255, 255), -1);
                    double mx;
                    Point mxLoc;
                    minMaxLoc(dist, NULL, &mx, NULL, &mxLoc, roi);
                    /* 
                        if this point is not already in the list, add it. 
                        as the gradient line can intersect the shape contour at two
                        points most of the time, we'll find the same peak twice
                    */
                    if (shapeApprox.end() == find(shapeApprox.begin(), shapeApprox.end(), mxLoc))
                    {
                        //cout << mx << " @ " << mxLoc << endl;
                        shapeApprox.push_back(mxLoc);
                    }
                    /* no need to test other gradient lines */
                    break;
                }
            }
            /* reset the mask */
            roi.setTo(Scalar(0, 0, 0));
        }
        /* draw the (possibly merged) gradient line segments */
        drawContours(rgb, regions, -1, Scalar(0, 0, 255), -1);
        /* draw the shape approximation */
        for (size_t i = 1; i < shapeApprox.size(); i++)
        {
            arrowedLine(rgb, shapeApprox[i-1], shapeApprox[i], Scalar(255, 0, 0), 2, CV_AA, 0, .1);
            //imshow("approx", rgb);
            //waitKey(0);
        }
    }

    imshow("debug", rgb3);
    imshow("points", rgb2);
    imshow("approx", rgb);
    waitKey(0);

    return 0;
}

答案 2 :(得分:0)

你检查了骨架化技术吗?有一些可用的例子......例如,检查https://sites.google.com/site/rameyarnaud/research/c/voronoi