【问题标题】:Concave Hull implementation凹壳实现
【发布时间】:2017-10-11 22:29:45
【问题描述】:

我正在尝试实现下面http://repositorium.sdum.uminho.pt/bitstream/1822/6429/1/ConcaveHull_ACM_MYS.pdf中描述的算法

我正在使用以下类库。 Loyc 库来自http://core.loyc.net/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Device.Location;
using Loyc.Collections;
using Loyc.Geometry;
using Loyc;

这是基础类

public class Hulls
{
    private static List<Point<double>> KNearestNeighbors(List<Point<double>> points, Point<double> currentPoint, int k, out int kk)
    {
        kk = Math.Min(k, points.Count - 1);
        var ret = points
            .OrderBy(x => PointMath.Length(new Vector<double>(currentPoint.X - x.X, currentPoint.Y - x.Y)))
            .Take(k)
            .ToList();
        return ret;
    }
    private static double Angle(Point<double> a, Point<double> b)
    {
        var ret = -Math.Atan2(b.Y - a.Y, b.X - a.X);
        return NormaliseAngle(ret);
    }
    private static double NormaliseAngle(double a)
    {
        //while (a < b - Math.PI) a += Math.PI * 2;
        //while (b < a - Math.PI) b += Math.PI * 2;
        if (a < 0.0) { return a + Math.PI + Math.PI; }
        return a;
    }
    private static List<Point<double>> SortByAngle(List<Point<double>> kNearest, Point<double> currentPoint, double angle)
    {
        //kNearest
        //    .Sort((v1, v2) => AngleDifference(angle, Angle(currentPoint, v1)).CompareTo(AngleDifference(angle, Angle(currentPoint, v2))));
        //return kNearest.ToList();
        kNearest = kNearest.OrderByDescending(x => NormaliseAngle(Angle(currentPoint, x) - angle)).ToList();
        return kNearest;
    }

    private static bool CCW(Point<double> p1, Point<double> p2, Point<double> p3)
    {
        var cw = ((p3.Y - p1.Y) * (p2.X - p1.X)) - ((p2.Y - p1.Y) * (p3.X - p1.X));
        return cw > 0 ? true : cw < 0 ? false : true; // colinear 
    }

    private static bool _Intersect(LineSegment<double> seg1, LineSegment<double> seg2)
    {
        return CCW(seg1.A, seg2.A, seg2.B) != CCW(seg1.B, seg2.A, seg2.B) && CCW(seg1.A, seg1.B, seg2.A) != CCW(seg1.A, seg1.B, seg2.B);
    }

    private static bool Intersect(LineSegment<double> seg1, LineSegment<double> seg2)
    {
        if ((seg1.A.X == seg2.A.X && seg1.A.Y == seg2.A.Y) 
            || (seg1.B.X == seg2.B.X && seg1.B.Y == seg2.B.Y))
        {
            return false;
        }
        if (_Intersect(seg1, seg2))
        {
            return true;
        }
        return false;
    }

    public IListSource<Point<double>> KNearestConcaveHull(List<Point<double>> points, int k)
    {
        points.Sort((a, b) => a.Y == b.Y ? (a.X > b.X ? 1 : -1) : (a.Y >= b.Y ? 1 : -1));
        Console.WriteLine("Starting with size {0}", k.ToString());

        DList<Point<double>> hull = new DList<Point<double>>();
        var len = points.Count;

        if (len < 3) { return null; }
        if (len == 3) { return hull; }

        var kk = Math.Min(Math.Max(k, 3), len);

        var dataset = new List<Point<double>>();
        dataset.AddRange(points.Distinct());

        var firstPoint = dataset[0];
        hull.PushFirst(firstPoint);

        var currentPoint = firstPoint;
        dataset.RemoveAt(0);

        double previousAngle = 0;
        int step = 2;
        int i;
        while ((currentPoint != firstPoint || step == 2) && dataset.Count > 0)
        {
            if (step == 5) { dataset.Add(firstPoint); }
            var kNearest = KNearestNeighbors(dataset, currentPoint, k, out kk);
            var cPoints = SortByAngle(kNearest, currentPoint, previousAngle);
            var its = true;
            i = 0;
            while (its == true && i < cPoints.Count)
            {
                i++;
                int lastPoint = 0;
                if (cPoints[i - 1] == firstPoint)
                {
                    lastPoint = 1;
                }
                int j = 2;
                its = false;
                while (its == false && j < hull.Count - lastPoint)
                {
                    LineSegment<double> line1 = new LineSegment<double>(hull[step - 2], cPoints[i - 1]);
                    LineSegment<double> line2 = new LineSegment<double>(hull[step - 2 - j], hull[step - 1 - j]);

                    //its = LineMath.ComputeIntersection(line1, line2, out pfrac, LineType.Segment);
                    its = Intersect(line1, line2);
                    j++;
                }
            }
            if (its == true)
            {
                return KNearestConcaveHull(points, kk + 1);
            }
            currentPoint = cPoints[i - 1];
            hull.PushLast(currentPoint);
            previousAngle = Angle(hull[step - 1], hull[step - 2]);
            dataset.Remove(currentPoint); 
            step++;
        }
        bool allInside = true;
        i = dataset.Count;
        while (allInside == true && i > 0)
        {
            allInside = PolygonMath.IsPointInPolygon(hull, dataset[i - 1]);
            i--;
        }
        if (allInside == false) { return KNearestConcaveHull(points, kk + 1); }
        return hull;
    }
}

上面的内容应该基于从前一个边缘绕逆时针设置的点向右转最远的位置为边界选择一个新边缘。该代码似乎从具有最低 y 值的初始顶点中选择了正确的第一条边,但是当偏移角不为零时,它没有正确选择下一条边。我认为问题是 SortByAngle 或 Angle。 -atan2 会返回顺时针方向,对吗?可能我应该添加偏移角度?

编辑(解决方案):在遵循 Eric 在对该问题的第一条评论中提供的有用建议后发现了该问题。是 SortByAngle 和 Angle:

private static double Angle(Point<double> a, Point<double> b)
    {
        var ret = Math.Atan2(b.Y - a.Y, b.X - a.X);
        return NormaliseAngle(ret);
    }

    private static double NormaliseAngle(double a)
    {
        if (a < 0.0) { return a + Math.PI + Math.PI; }
        return a;
    }

    private static List<Point<double>> SortByAngle(List<Point<double>> kNearest, Point<double> currentPoint, double angle)
    {
        //kNearest = kNearest.OrderByDescending(x => NormaliseAngle(Angle(currentPoint, x) - angle)).ToList();
        kNearest.Sort((a, b) => NormaliseAngle(Angle(currentPoint, a) - angle) > NormaliseAngle(Angle(currentPoint, b) - angle) ? 1 : -1);
        return kNearest;
    }

【问题讨论】:

  • 调试程序并让我们及时了解调试会话的状态并不是 StackOverflow 的有效使用。这不是用于查找错误的服务。如果您想知道某个方法是否正确,请为该方法编写测试用例。如果您想获得更多关于如何调试您刚刚编写的小错误程序的好的建议,请参阅ericlippert.com/2014/03/05/how-to-debug-small-programs
  • @EricLippert,感谢您的建议。我只是想提供尽可能多的信息,但感谢您所说的。我目前正在编写单元测试。之后我会相应地编辑我的问题。
  • 任何方式 - 使用 Theraot.Core 使您必须使用“外部别名”并为该 dll 提供别名,否则您会发现自己与 System.Linq 函数有模棱两可的调用......他们在同一个命名空间中构建该 dll。
  • 嗨,你有这个代码的更新版本吗?

标签: c# computational-geometry


【解决方案1】:

你有一些错误:

var kNearest = KNearestNeighbors(dataset, currentPoint, k, out kk);

将 kk 更改为只是一些 var。你覆盖了那个“kk”值的增量,然后你得到了 StackOverflow 异常。

将您的代码更改为以下内容:

int someVal;    
var kNearest = KNearestNeighbors(dataset, currentPoint, k, out someVal);

【讨论】:

    猜你喜欢
    • 2014-05-21
    • 2011-03-27
    • 1970-01-01
    • 2015-06-11
    • 2018-04-30
    • 2015-02-19
    • 2017-03-16
    • 2015-07-11
    • 2011-12-22
    相关资源
    最近更新 更多