最小面积四边形算法

时间:2010-01-12 09:58:26

标签: algorithm language-agnostic math graphics geometry

有一些算法可以找到包含给定(凸)多边形的最小边界矩形。

有人知道找到最小区域边界四边形(任何四边形,而不仅仅是矩形)的算法吗?

我现在已经在互联网上搜索了好几个小时,但是虽然我发现了一些关于这个问题的理论论文,但我没有找到一个实现...

编辑:Mathoverflow的人员向我指出了一篇带有数学解决方案(my post there)的文章,但我没有找到实际的实现。我决定采用卡尔的蒙特卡罗方法,但是当我有时间的时候,我会潜入论文并报告......

全部谢谢!

7 个答案:

答案 0 :(得分:5)

蒙特卡洛方法

感谢有关问题的澄清评论。我已经拿走了所需要的不是数学上正确的结果,而是“适合”,这比其他形状的任何类似配合都要好。

我不会在问题上倾注大量的算法脑力,而是让计算机担心它。生成4个随机点组;检查由凸起连接这4个点形成的四边形是否与多边形不相交,并计算四边形的面积。重复100万次,检索面积最小的四边形。

您可以应用一些约束来使您的点不是完全随机的;这可以大大改善收敛。


蒙特卡洛,改进了

我一直相信,即使对于蛮力解决方案,在飞机上随机投掷4点也是一个非常低效的开始。因此,进行了以下改进:

  • 对于每个试验,随机选择 p 不同顶点和 q 多边形的不同边,使 p + q = 4。
  • 对于每个 q 边,构造一条穿过该边的端点的线。
  • 对于每个 p 顶点,构造一条穿过该顶点并具有随机指定斜率的直线。
  • 验证4条线确实形成四边形,并且该四边形包含(并且不相交!)多边形。如果这些测试失败,请不要再进行此迭代了。
  • 如果此四边形区域是目前所见区域的最小值,请记住四边形顶点的面积和坐标。
  • 重复任意次数,并返回找到的“最佳”四边形。

与始终需要8个随机数(4个点中的每一个的x和y坐标)相反,此解决方案仅需要(4 + p )随机数。而且,产生的线不是在平面中盲目地挣扎,而是每个都接触多边形。这确保了四边形从一开始就至少非常接近多边形。

答案 1 :(得分:4)

吝啬算法

从凸多边形开始。虽然有超过4个点,但找到最便宜的一对相邻点进行合并,并合并它们,将多边形中的点数减少1。

通过“巩固”,我只是意味着延伸两边的两条边,直到它们在某一点相遇,然后用这一点来代替两条边。

“最便宜”,我指的是合并为多边形添加最小面积的对。

Before:                       After consolidating P and Q:

                                    P'
                                   /\
   P____Q                         /  \
   /    \                        /    \
  /      \                      /      \
 /        \                    /        \

以O(n log n)运行。但这只会产生近似值,我并不完全满意。算法在生成一个漂亮的正五边形时效果越好,最后一次整合必须消耗的区域越多。

答案 2 :(得分:0)

我认为定向边界框应该非常接近(尽管它实际上是一个矩形)。以下是有关定向边界框的标准参考文件:Gottschalk's paper(PDF)

请看第3节(安装OBB)。

答案 3 :(得分:0)

我认为围绕积分的2D OBB是一个很好的起点。这可能会给人一种“好”(但不是最好)的契合;如果你发现你仍然需要更严格的约束,你可以将拟合扩展到四边形。

首先,您应该计算输入点的凸包。这样可以减少处理的次数,并且根本不会改变答案。

我会远离其他地方Gottschalk论文中引用的基于协方差的方法。这并不总能给出最佳拟合,并且对于非常简单的输入(例如正方形)可能会出错。

在2D中,http://cgm.cs.mcgill.ca/~orm/maer.html中描述的“旋转卡尺”方法应该给出最精确的最佳OBB。这个问题也很容易被认为是一维最小化问题:

  1. 对于给定角度,将x和y轴旋转此角度。这为您提供了两个正交向量。
  2. 对于每个点,投影到两个轴上。跟踪每个轴上的最小和最大投影。
  3. OBB的区域是(max1-min1)*(max2-min2),您可以从角度以及最小和最大投影轻松计算OBB的点数。
  4. 重复,根据需要对间隔[0,pi / 2]进行精细采样,并跟踪最佳角度。
  5. John Ratcliffe的博客(http://codesuppository.blogspot.com/2006/06/best-fit-oriented-bounding-box.html)有一些代码在3D中采用这种抽样方法;如果卡住,你应该能够适应你的2D情况。 警告:John有时会在他的博客帖子上发布温和的NSFW图片,但这个特定链接没问题。

    如果你在完成这项工作后仍对结果不满意,你可以稍微修改一下这个方法;我能想到两个改进:

    1. 您可以选择两个非正交轴,而不是如上所述选择正交轴。这会给你最合适的菱形。为此,您基本上在[0,pi] x [0,pi]上进行2D搜索。
    2. 使用最适合的OBB作为更详细搜索的起点。例如,您可以从OBB中取4个点,移动其中一个直到线接触到一个点,然后重复其他点。
    3. 希望有所帮助。对不起信息超载:))

答案 4 :(得分:0)

我相信最小区域边界四边形的每一边都会穿过多边形的至少2个顶点。如果这个假设成立,那么应该很容易对解决方案进行强力搜索。

  • 生成由2个顶点定义且不与多边形相交的唯一线条集。
  • 检查每组4条线以确定哪条线产生最小区域边界四边形。

更新:边界四边形的每一边将通过至少2个顶点的假设是假的,但是相关的一组线可以提供解决方案的基础。至少,边界四边形的每一边都将通过一个顶点,用于定义由2个顶点定义并且不与多边形相交的唯一线条集合。

答案 5 :(得分:0)

这是一个导致蒙特卡罗算法改进的观察结果,也可能导致直接回答。

引理:如果最佳四边形的边缘仅在一个点处触及多边形,则它会触及该边缘的中点。

证明:将X和Y定义为触摸点两侧的两个段的长度。想象一下,将单个触点周围的边缘旋转一个无限小的角度A.旋转会影响四边形的大小,方法是将其增加XA / 2并将其减小YA / 2,反之亦然。如果X!= Y,则两个旋转方向中的一个导致较小的四边形。由于四边形是最小的,我们必须有X = Y.

要使用这个事实,请注意,如果我们选择多边形接触四边形的一些边和点,并且我们不连续选择两个点,我们可以通过选择每个点的边来唯一地确定四边形使该点成为边缘的中点(如果不可能,则拒绝选择的配置)。在蒙特卡罗算法中,这减少了说我们不必为这个边缘选择斜率 - 它可以明确地确定。

我们仍然有两个相邻点被选中的情况 - 希望我激励别人来这里接受......

答案 6 :(得分:0)

我有同样的问题要解决,我使用的代码实际上是用一个额外的矩形来实现 Jason Orendorff 的想法,它是流程的边界并使结果更像方形。最后,这是一个很好的启发式方法,在我的情况下效果很好。我希望其他人也能从这段代码中受益:

import java.util.ArrayList;
import java.util.List;

import org.opencv.core.Point;
import org.opencv.core.Rect;


public class Example {
    
    
    public static Point[] getMinimalQuadrilateral(Point[] convexPolygon, Rect boundingRec) {
        
        if (convexPolygon.length <= 4) {
            throw new IllegalStateException();
        }
        
        //Create list with all entries
        List<ListItem<Point>> all_init_list = new ArrayList<ListItem<Point>>();
        for (int i = 0; i < convexPolygon.length; i++) {
            ListItem<Point> cur = new ListItem<Point>();
            cur.value = convexPolygon[i];
            all_init_list.add(cur);
        }
        
        //Link the list
        for (int i = 0; i < all_init_list.size() - 1; i++) {
            all_init_list.get(i).next = all_init_list.get(i + 1);
        }
        //Make it cyclic
        all_init_list.get(all_init_list.size() - 1).next = all_init_list.get(0);
        
        
        int countOfPoints = all_init_list.size();
        ListItem<Point> start = all_init_list.get(0);
        
        while (countOfPoints > 4) {
            
            //System.out.println("countOfPoints=" + countOfPoints);
            
            double minTriangleArea = Double.MAX_VALUE;
            ListItem<Point> best = null;
            ListItem<Point> best_intersection = new ListItem<Point>();
            ListItem<Point> cur = start;
            do {
                Point p1 = cur.value;
                Point p2 = cur.next.value;
                Point p3 = cur.next.next.value;
                Point p4 = cur.next.next.next.value;
                
                //Do work
                Point intersection = findIntersection(p1, p2, p4, p3);
                if (intersection != null && boundingRec.contains(intersection)) {
                    double cur_area = triangleArea(p2, intersection, p3);
                    if (cur_area < minTriangleArea) {
                        minTriangleArea = cur_area;
                        best = cur;
                        best_intersection.value = intersection;
                        //System.out.println("minTriangleArea=" + minTriangleArea);
                    }
                }
                
                cur = cur.next;
            } while (cur != start);
            
            //If there is best than remove 2 points and put their intersection instead
            if (best == null) {
                break;
            }
            best_intersection.next = best.next.next.next;
            best.next = best_intersection;
            countOfPoints--;
            start = best;
        }
        
        //Compose result
        Point[] result = new Point[countOfPoints];
        while (countOfPoints > 0) {
            result[countOfPoints - 1] = start.value;
            start = start.next;
            countOfPoints--;
        }
        
        return result;
    }
    
    
    public static double triangleArea(Point A, Point B, Point C) {
        double area = (A.x * (B.y - C.y) + B.x * (C.y - A.y) + C.x * (A.y - B.y)) / 2.0;
        return Math.abs(area);
    }
    
    
    public static Point findIntersection(Point l1s, Point l1e, Point l2s, Point l2e) {
        
        double a1 = l1e.y - l1s.y;
        double b1 = l1s.x - l1e.x;
        double c1 = a1 * l1s.x + b1 * l1s.y;
        
        double a2 = l2e.y - l2s.y;
        double b2 = l2s.x - l2e.x;
        double c2 = a2 * l2s.x + b2 * l2s.y;
        
        double delta = a1 * b2 - a2 * b1;
        if (delta == 0) {
            return null;
        }
        
        return new Point((b2 * c1 - b1 * c2) / delta, (a1 * c2 - a2 * c1) / delta);
    }
    
    
    private static final class ListItem<T> {
        public T value;
        public ListItem<T> next;
    }
}

如果过程无法找到解决方案,可以通过例如从小的边界矩形开始并按顺序增加它来进一步改进算法。 在实践中,我使用的边界矩形比最小边界矩形大 5%。