C# 四边形求形算法

C# 四边形求形算法,c#,algorithm,image-processing,opencv,artificial-intelligence,C#,Algorithm,Image Processing,Opencv,Artificial Intelligence,我想从随机定位的线段中检测并完成所有可能的四边形 附上的照片就是一个例子,线条可能总是出现在非常不同的位置 有人能指出这方面的好算法吗 注意,线段是使用opencv 2.4.2的Hough变换的输出 解决方案是检测和预测黄色四边形 对于11条线段,您有330种选择四条线段的方法。您可以确定每个组合形成四边形的可能性,并以此方式进行评分 尽管由于累加器空间需要两个以上的维度,因此很难可视化,但是可以使用Hough变换检测线以外的形状。圆可以在三维空间中找到(中点、中点、半径),椭圆可以在四个

我想从随机定位的线段中检测并完成所有可能的四边形

附上的照片就是一个例子,线条可能总是出现在非常不同的位置

有人能指出这方面的好算法吗

  • 注意,线段是使用opencv 2.4.2的Hough变换的输出

解决方案是检测预测黄色四边形


对于11条线段,您有330种选择四条线段的方法。您可以确定每个组合形成四边形的可能性,并以此方式进行评分

尽管由于累加器空间需要两个以上的维度,因此很难可视化,但是可以使用Hough变换检测线以外的形状。圆可以在三维空间中找到(中点、中点、半径),椭圆可以在四个空间中找到(我相信)。我不确定建立四边形模型所需的参数到底有多少,而且我相信当你达到三维以上时,Hough变换的性能开始下降。累加器空间变大,噪声比显著增加

下面是一个可能会给你一些有趣答案的例子

让我们知道你进展如何


编辑 我今天尝试了一下这个问题,然后。这里的代码太多,无法发布

以下是显示输出的屏幕截图:

我采取的解决方案基本上就是我在本次编辑之前所描述的

  • 找到四行的所有组合
  • 找到这四行的所有排列
  • 评估这四条线形成四边形的可能性
  • 拿最好的比赛
  • 评估通过计算粗略的错误分数来进行。这是两种不同类型错误的总和:

  • 每个角与90度的偏差(我使用所有四个角的平方误差之和)
  • 当线段在线段内相交时,它可能不是有效的角点
  • 第二类错误可能以更稳健的方式确定。有必要为示例数据集找到解决方案

    我没有尝试过其他数据集。它可能需要一些调整,使其更加健壮。我试图避免使用太多的参数,以便可以直接调整到特定的环境。例如,控制对遮挡的敏感性,如示例图像中所示


    它在我的笔记本电脑上大约160毫秒就能找到解决方案。但是,我没有进行任何性能优化。我希望,如果您需要更接近实时的方式来寻找组合/排列的方法可以得到显著优化,就像计算机视觉实验中经常出现的情况一样。

    如果您不对角度等施加约束,任何四条线都可以形成四边形

    具有潜在错误四边形的图像:

    可能您不想包括我的示例中所示的黄色四边形。您应该对角度、最小/最大尺寸、纵横比和允许的完成程度进行限制。如果为了形成一个完整的四边形,必须添加90%的直线,那么这可能不是一个很好的选择

    我担心你将不得不测试每一种可能的线的组合,并对它们进行测试以给它们打分。对于接近90度的角度(如果您想要的是矩形),对于完整性,对于接近预期的高宽比,等等,有很多点


    更新

    使用计分制比只应用严格的规则更有优势

    • 点系统允许您评估四边形的质量,选择最好的或完全拒绝四边形
    • 一处房产的优良品质可能会超过另一处房产的劣质
    • 它允许您为不同的属性赋予不同的权重
    假设您有一个严格的规则(在伪代码中):

    这将起作用,但可能会导致类似于
    角度==90+/-1度)和&(线条完整性==45%)
    。根据规则,这个四边形不会通过,因为线的完整性差;然而,角度的质量是异常的,仍然使它成为一个非常好的候选者


    最好给分。例如,如果角度正好为90度,则为20点;如果角度为90+/-15度,则为0点;如果角度为25%,则为完整直线,则为10点;如果角度为25%,则为0点。这使得角度比线的完整性更重要,也为没有绝对规则的问题创造了更为宽松的条件。

    从示例中,我假设问题更像是沿着查找所有四边形,这些四边形的每边都包含一条线。从提供的解释来看,这一点一点也不清楚

    下面是一些相当容易实现的伪代码。现在只需要创建一个有效的数据结构来防止O(N^4)复杂性。也许可以按位置或梯度对线条进行排序

    i、 j、k、l如下:

       l
     |---|
    j|   |k
     |---|
       i
    
    extendIntersect
    仅仅是一个函数,它将两条直线扩展到无穷远(或您选择的任意边界),并返回它们相交的点,这在数学上很容易做到

    onLine
    如果点位于直线上,则返回true

    onSameSide
    如果两个点位于直线的同一侧,则返回true

    for (Line i = lines[0]:lines[lineCount])
      for (Line j = lines[1]:lines[lineCount])
        Point ijIntersect = extendIntersect(i, j)
        if (ijIntersect == NULL || onLine(ijIntersect, i) || onLine(ijIntersect, j))
          continue;
        for (Line k = lines[2]:lines[lineCount])
          Point ikIntersect = extendIntersect(i, k)
          if (ikIntersect == NULL || onLine(ikIntersect, i) || onLine(ikIntersect, k) ||
              onSameSide(ijIntersect, ikIntersect, i)) continue
          for (Line l = lines[3]:lines[lineCount])
            Point jlIntersect = extendIntersect(j, l)
            Point klIntersect = extendIntersect(k, l)
            if (jlIntersect == NULL || onLine(jlIntersect, j) || onLine(jlIntersect, l) ||
                klIntersect == NULL || onLine(klIntersect, k) || onLine(klIntersect, l) ||
                onSameSide(jlIntersect, ijIntersect, j) ||
                onSameSide(klIntersect, ikIntersect, k)) continue
            printQuad(ijIntersect, ikIntersect, klIntersect, jlIntersect)
    
    Drew Noakes建议的某种错误检查也可能是一个好主意。

    我不使用C,因此您必须翻译代码。下面的代码是用Java编写的。我用附带的测试用例测试了它。我还不知道如何向stackoverflow添加附件,所以我在这里包含了实际的代码

    有四个c
    for (Line i = lines[0]:lines[lineCount])
      for (Line j = lines[1]:lines[lineCount])
        Point ijIntersect = extendIntersect(i, j)
        if (ijIntersect == NULL || onLine(ijIntersect, i) || onLine(ijIntersect, j))
          continue;
        for (Line k = lines[2]:lines[lineCount])
          Point ikIntersect = extendIntersect(i, k)
          if (ikIntersect == NULL || onLine(ikIntersect, i) || onLine(ikIntersect, k) ||
              onSameSide(ijIntersect, ikIntersect, i)) continue
          for (Line l = lines[3]:lines[lineCount])
            Point jlIntersect = extendIntersect(j, l)
            Point klIntersect = extendIntersect(k, l)
            if (jlIntersect == NULL || onLine(jlIntersect, j) || onLine(jlIntersect, l) ||
                klIntersect == NULL || onLine(klIntersect, k) || onLine(klIntersect, l) ||
                onSameSide(jlIntersect, ijIntersect, j) ||
                onSameSide(klIntersect, ikIntersect, k)) continue
            printQuad(ijIntersect, ikIntersect, klIntersect, jlIntersect)
    
    package stackoverflow;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class ShapeFinder {
    
      private List<Line> lines;
      private List<Quadrilateral> allQuadrilaterals;
    
      /*
       * I am assuming your segments are in a list of arrays:
       * [{{x1,y1,},{x2,y2}}, {{x1,y1,},{x2,y2}}, {{x1,y1,},{x2,y2}}]
       * You can change this.
       *
       * So basically you call ShapeFinder with a list of your line segments.
       */
      public ShapeFinder(List<Double[][]> allSegments) {
        lines = new ArrayList<Line>(allSegments.size());
        allQuadrilaterals = new ArrayList<Quadrilateral>();
        for (Double[][] segment : allSegments) {
          addSlopeInterceptForm(segment);
        }
      }
    
      /**
       * You call this function to compute all possible quadrilaterals for you.
       */
      public List<Quadrilateral> completeQuadrilaterals() {
        for (int w = 0; w < lines.size(); w++) {
          for (int x = w + 1; x < lines.size(); x++) {
            for (int y = x + 1; y < lines.size(); y++) {
              for (int z = y + 1; z < lines.size(); z++) {
                addQuadrilateral(w, x, y, z);
              }
            }
          }
        }
        return allQuadrilaterals;
      }
    
      //assume {{x1,y1,},{x2,y2}}
      private void addSlopeInterceptForm(Double[][] s) {
        double x1 = s[0][0];
        double y1 = s[0][1];
        double x2 = s[1][0];
        double y2 = s[1][1];
        double m = (y1 - y2) / (x1 - x2);
        double b = y2 - m * x2;
    
        if (isInfinityOrNaN(m)) {
          m = Double.NaN;
          b = x1;
        }
    
        lines.add(new Line(m, b));
      }
    
      /*
       * Given four lines, this function creates a quadrilateral if possible
       */
      private void addQuadrilateral(int w, int x, int y, int z) {
        Point wx = intersect(w, x);
        Point wy = intersect(w, y);
        Point wz = intersect(w, z);
        Point xy = intersect(x, y);
        Point xz = intersect(x, z);
        Point yz = intersect(y, z);
    
        if (notNull(wx) && notNull(xy) && notNull(yz) && notNull(wz) && isNull(wy) && isNull(xz)) {
          allQuadrilaterals.add(new Quadrilateral(wx, xy, yz, wz));
        }
      }
    
      private Point intersect(int c, int d) {
        double m1 = lines.get(c).slope;
        double b1 = lines.get(c).intercept;
        double m2 = lines.get(d).slope;
        double b2 = lines.get(d).intercept;
    
        double xCor, yCor;
        if ((isInfinityOrNaN(m1) && !isInfinityOrNaN(m2)) || (!isInfinityOrNaN(m1) && isInfinityOrNaN(m2))) {
          xCor = isInfinityOrNaN(m1) ? b1 : b2;
          yCor = isInfinityOrNaN(m1) ? m2 * xCor + b2 : m1 * xCor + b1;;
        } else {
          xCor = (b2 - b1) / (m1 - m2);
          yCor = m1 * xCor + b1;
        }
    
        if (isInfinityOrNaN(xCor) || isInfinityOrNaN(yCor)) {
          return null;
        }
        return new Point(xCor, yCor);
      }
    
      private boolean isInfinityOrNaN(double d){
        return Double.isInfinite(d)||Double.isNaN(d);
      }
    
      private boolean notNull(Point p) {
        return null != p;
      }
    
      private boolean isNull(Point p) {
        return null == p;
      }
    }
    
    package stackoverflow;
    
    public class Line {
    
      double slope;
      double intercept;
    
      public Line(double slope, double intercept) {
        this.slope = slope;
        this.intercept = intercept;
      }
    }
    
    package stackoverflow;
    
    class Point {
    
      double xCor;
      double yCor;
    
      public Point(double xCor, double yCor) {
        this.xCor = xCor;
        this.yCor = yCor;
      }
    
      public String toString(){
        return "("+xCor+","+yCor+")";
      }
    }
    
    package stackoverflow;
    
    public class Quadrilateral {
    
      private Point w, x, y, z;
    
      public Quadrilateral(Point w, Point x, Point y, Point z) {
        this.w = w;
        this.x = x;
        this.y = y;
        this.z = z;
      }
    
      public String toString() {
        return "[" + w.toString() + ", " + x.toString() + ", " + y.toString() + ", " + z.toString() + "]";
      }
    }
    
    package stackoverflow;
    
    import java.util.ArrayList;
    import java.util.List;
    import org.junit.Test;
    
    public class ShapeFinderTest {
    
      @Test
      public void testCompleteQuadrilaterals() {
        List<Double[][]> lines = new ArrayList<>();
        lines.add(new Double[][]{{2., 5.}, {6., 5.}});
        lines.add(new Double[][]{{2., 1.}, {2., 5.}});
        lines.add(new Double[][]{{2., 1.}, {6., 1.}});
        lines.add(new Double[][]{{6., 5.}, {6., 1.}});
        lines.add(new Double[][]{{0., 0.}, {5., 1.}});
        lines.add(new Double[][]{{5., 5.}, {10., 25.}});
        ShapeFinder instance = new ShapeFinder(lines);
        List<Quadrilateral> result = instance.completeQuadrilaterals();
    
        for (Quadrilateral q : result) {
          System.out.println(q.toString());
        }
      }
    }
    
    #!/usr/bin/env python
    
    """
    Find Quads:
    
    For a set of line segments, find all the possible
    quadrilateral shapes where the segments fit
    inside the edges of the quad.
    
    
    Dependencies:
    Sympy is used for geometry primitives.
    sudo pip install sympy
    """
    
    import numpy as np
    import cv2
    import itertools # combinations, product
    from sympy import Point, Line, Segment, convex_hull
    import sys
    
    
    input_image = cv2.imread("detected_lines.jpg")
    
    
    #------------------------------------------------------------------------------#
    
    def checkPointInImage(point, image_width, image_height):
        """
        Check if a Sympy Point2D is within the bounds of an OpenCV image.
        """
        pt_x = int(round(point.x))
        pt_y = int(round(point.y))
        if (pt_x >= 0) and (pt_x < image_width) and (pt_y >= 0) and (pt_y < image_height):
            return True
        # Point is outside the image boundary
        return False
    
    
    def checkPointsInImage(points, image_width, image_height):
        """
        Check if a set of Sympy Point2D are all within the bounds of an OpenCV image.
        """
        for point in points:
            if not checkPointInImage(point, image_width, image_height):
                return False
        # All points are within the image boundary
        return True
    
    
    def getUniquePairs(segments, image_dims):
        """
        Get all the possible pairs of line segments.
        (the unique combinations of 2 lines)
        Note: this doesn't check for duplicate elements, it works
        only on the position in the list.
        """
    
        # Check that a pair of segments are not intersecting
        check_segments_dont_intersect = True
    
        # Check that the endpoint of one segment
        # does not touch the other segment (within 10 pixels)
        check_segment_endpoints = True
        endpoint_min_separation = 10
    
        # Project the segments and check if the intersection
        # point is within the image
        check_projected_segments_dont_intersect = True
    
        pairs = list(itertools.combinations(segments, 2)) # a list of tuple
    
        image_width, image_height = image_dims
    
        filtered_pairs = []
        for pair in pairs:
            segment1 = pair[0]
            segment2 = pair[1]
    
            if check_segments_dont_intersect:
                if bool(len(segment1.intersection(segment2))):
                    # Discard this pair.
                    # The pair of segments intersect each other.
                    continue
    
            if check_segment_endpoints or check_projected_segments_dont_intersect:
                line1 = Line(segment1)
                line2 = Line(segment2)
                intersection_points = line1.intersection(line2)
                intersects = bool(len(intersection_points))
    
                if intersects:
                    intersection_point = intersection_points[0]
    
                    if check_segment_endpoints:
                # Measure the distance from the endpoint of each segment
                        # to the intersection point.
                        d1 = float(segment1.points[0].distance(intersection_point))
                        d2 = float(segment1.points[1].distance(intersection_point))
                        d3 = float(segment2.points[0].distance(intersection_point))
                        d4 = float(segment2.points[1].distance(intersection_point))
                        d = np.array([d1,d2,d3,d4]) 
                        if (d < float(endpoint_min_separation)).any():
                            # Discard this pair.
                            # One segment is (almost) touching the other.
                            continue 
    
                    if check_projected_segments_dont_intersect:
                        if checkPointInImage(intersection_point, image_width, image_height):
                            # Discard this pair.
                            # After projecting the segments as lines,
                            # they intersect somewhere on the image.
                            continue
    
            filtered_pairs.append(pair)
    
        return filtered_pairs
    
    
    def getCombinationsOfTwoLists(list1, list2):
        """
        For two sets of Line Segment pairs,
        generate all possible combinations.
        """
        return list(itertools.product(list1, list2))
    
    
    def getIntersectionLineSegments(segment1, segment2):
        """
        Find the intersection of two line segments,
        by extending them into infinite lines.
        """
        line1 = Line(segment1)
        line2 = Line(segment2)
        intersection_points = line1.intersection(line2)
        intersects = bool(len(intersection_points))
        if intersects:
            intersection_point = intersection_points[0]
            return intersection_point
        # Error, lines do not intersect
        print("WARNING: Horizontal and vertical line segments do not intersect.")
        print("This should not happen!")
        return None
    
    
    def checkLineSegmentIsAbove(segment1, segment2):
        """
        Check if one line segment is above the other.
        (this assumes the segments are not intersecting)
        """
    
        # In image coordinates, (+x,+y) is bottom-right corner.
        if (segment1.points[0].y > segment2.points[0].y): return False
        if (segment1.points[0].y > segment2.points[1].y): return False
        if (segment1.points[1].y > segment2.points[0].y): return False
        if (segment1.points[1].y > segment2.points[1].y): return False
    
        return True
    
    
    def checkLineSegmentOnLeft(segment1, segment2):
        """
        Check if one line segment is on the left side of the other.
        (this assumes the segments are not intersecting)
        """
    
        # In image coordinates, (+x,+y) is bottom-right corner.
        if (segment1.points[0].x > segment2.points[0].x): return False
        if (segment1.points[0].x > segment2.points[1].x): return False
        if (segment1.points[1].x > segment2.points[0].x): return False
        if (segment1.points[1].x > segment2.points[1].x): return False
    
        return True
    
    
    def getConvexIntersectionPoints_method2(horizontal_segment1, horizontal_segment2, vertical_segment1, vertical_segment2):
        """
        For two pairs of line segments, treat them as
        infinite lines and find the intersection points.
    
        These 4 points are in a clockwise order that
        represents a convex quadrilateral.
        """
    
        # Sort the segments in clockwise order
        top_segment = None
        right_segment = None
        bottom_segment = None
        left_segment = None
        if checkLineSegmentIsAbove(horizontal_segment1, horizontal_segment2):
            top_segment = horizontal_segment1
            bottom_segment = horizontal_segment2
        else:
            top_segment = horizontal_segment2
            bottom_segment = horizontal_segment1
        if checkLineSegmentOnLeft(vertical_segment1, vertical_segment2):
            left_segment = vertical_segment1
            right_segment = vertical_segment2
        else:
            left_segment = vertical_segment2
            right_segment = vertical_segment1
    
        corner_pt1 = getIntersectionLineSegments(left_segment, top_segment)
        corner_pt2 = getIntersectionLineSegments(top_segment, right_segment)
        corner_pt3 = getIntersectionLineSegments(right_segment, bottom_segment)
        corner_pt4 = getIntersectionLineSegments(bottom_segment, left_segment)
    
        quad_points = [corner_pt1, corner_pt2, corner_pt3, corner_pt4]
        sorted_segments = [top_segment, right_segment, bottom_segment, left_segment]
    
        return (quad_points, sorted_segments)
    
    
    def checkSegmentsOnQuad_method2(sorted_segments, corners):
        """ 
        Check if all 4 line segments are within
        the edges of a quadrilateral.
    
        This assumes that the inputs are already matched.
        """
    
        if (len(sorted_segments) != 4) or (len(corners) != 4):
           print("ERROR: Expected 4 segments and 4 corners in checkSegmentsOnQuad_method2()")
           sys.exit()
    
        # Get the 4 edges
        edges = []
        for i in range(3):
            p1 = corners[i]
            p2 = corners[i+1]
            edges.append(Segment(p1, p2))
        p1 = corners[3]
        p2 = corners[0]
        edges.append(Segment(p1, p2))
    
        for i in range(4):
            if not edges[i].contains(sorted_segments[i]):
                return False
        return True
    
    
    def getQuads(sets_of_four_segments, image_dims):
        """
        Find quadrilateral shapes.
        """
    
        image_width, image_height = image_dims
    
        quads = []
        for i in range(len(sets_of_four_segments)):
    
            # Determine if 4 line segments represent
            # a valid quadrilateral shape:
    
            segments = sets_of_four_segments[i]
            horizontal_segment1 = segments[0][0]
            horizontal_segment2 = segments[0][1]
            vertical_segment1 = segments[1][0]
            vertical_segment2 = segments[1][1]
    
            quad_points, sorted_segments = getConvexIntersectionPoints_method2(horizontal_segment1, horizontal_segment2, vertical_segment1, vertical_segment2)
    
            if not checkPointsInImage(quad_points, image_width, image_height):
                print("  Bad quad, an intersection point (one corner of the quad) is outside image!")
    
                # Save debug image
                img = np.copy(input_image)
                drawCrosshairs(img, quad_points)
                drawQuad(img, quad_points)
                suffix = str(i).zfill(2)
                cv2.imwrite("candidate_quad_"+suffix+".jpg", img)
    
                # Discard this quad.
                # A corner point is outside the image boundary.
                continue
    
            # Check if each line segment is within one side of the quad.
            #  - The segments can not intersect each other.
            #  - The end of a segment can not extend out past the quad.
            #  - All segments must be contained within one edge of the shape.
            if checkSegmentsOnQuad_method2(sorted_segments, quad_points):
                print("  Good")
                quads.append(quad_points)
            else:
                print("  Bad quad, a line segment is not within the quad")
    
            # Save debug image
            img = np.copy(input_image)
            drawCrosshairs(img, quad_points)
            drawQuad(img, quad_points)
            suffix = str(i).zfill(2)
            cv2.imwrite("candidate_quad_"+suffix+".jpg", img)
            #cv2.imshow("Quad corners", img)
            #cv2.waitKey()
    
        return quads
    
    
    #------------------------------------------------------------------------------#
    
    # Drawing functions:
    
    
    def drawSegment(image, segment, color):
        """
        Draw a Sympy Line Segment on an OpenCV image.
        """
        thickness = 2
        x1 = int(segment.points[0].x) # should already be int
        y1 = int(segment.points[0].y)
        x2 = int(segment.points[1].x)
        y2 = int(segment.points[1].y)
        cv2.line(image, (x1,y1), (x2,y2), color, thickness)
    
    
    def drawSegments(image, segments, color=(0,0,255)):
        """
        Draw lines on an OpenCV image.
    
        Default color is red.
        """
        for segment in segments:
            drawSegment(image, segment, color)
    
    
    def drawCrosshair(image, point):
        """
        Draw a Sympy Point2D on an OpenCV image
        with a cross marker.
        """
        pt_x = int(round(point.x))
        pt_y = int(round(point.y))
        length = 5
        thickness = 2
        color = (255,0,255) # magenta
        cv2.line(image, (pt_x, pt_y-length), (pt_x, pt_y+length), color, thickness)
        cv2.line(image, (pt_x-length, pt_y), (pt_x+length, pt_y), color, thickness)
    
    
    def drawCrosshairs(image, points):
        """
        Draw marks on an OpenCV image.
        """
        for point in points:
            drawCrosshair(image, point)
    
    
    def drawQuad(image, corners, color=(0,255,0)):
        """
        Draw a quadrilateral shape.
        The 4 corner points are Sympy Point2D.
        """
        for i in range(len(corners)-1):
            p1 = corners[i]
            p2 = corners[i+1]
            segment = Segment(p1, p2)
            drawSegment(image, segment, color)
        # Close the polygon
        p1 = corners[len(corners)-1]
        p2 = corners[0]
        segment = Segment(p1, p2)
        drawSegment(image, segment, color)
    
    
    #------------------------------------------------------------------------------#
    
    
    if input_image == None:
        print("ERROR: Can't find input image")
        sys.exit()
    
    #cv2.imshow("input_image", input_image)
    #cv2.waitKey()
    
    
    # Line segments sample data
    segment1  = Segment(Point(335,120), Point(517,144))
    segment2  = Segment(Point(287, 604), Point(558, 619))
    segment3  = Segment(Point(323, 131), Point(275, 587))
    segment4  = Segment(Point(589, 473), Point(580, 606))
    segment5  = Segment(Point(368, 39), Point(489, 108))
    segment6  = Segment(Point(53, 286), Point(293, 406))
    segment7  = Segment(Point(299, 347), Point(214, 538))
    segment8  = Segment(Point(200, 370), Point(149, 528))
    segment9  = Segment(Point(6, 446), Point(68, 449))
    segment10 = Segment(Point(66, 444), Point(150, 525))
    segment11 = Segment(Point(389, 514), Point(518, 644))
    segments = [segment1, segment2, segment3, segment4, segment5, segment6, segment7, segment8, segment9, segment10, segment11]
    
    
    image_width = input_image.shape[1]
    image_height = input_image.shape[0]
    image_dims = (image_width, image_height)
    
    
    input_image_with_segments = np.copy(input_image)
    drawSegments(input_image_with_segments, segments)
    cv2.imshow("input_image_with_segments", input_image_with_segments)
    cv2.waitKey()
    
    
    # Sort the line segments into 2 groups:
    horizontal_segments = []
    vertical_segments   = []
    image_width = input_image.shape[1]
    x_axis = Line((0, 0), (image_width, 0))
    for segment in segments:
        # Compute the angle of each line segment.
        # Angle is w.r.t. the top edge of the image
        # in a clockwise direction.
        angle = float(x_axis.angle_between(segment))
    
        # Check 315 to 360 degrees
        if (angle >= 2.0*np.pi-np.pi/4.0) and (angle <= 2.0*np.pi):
            horizontal_segments.append(segment)
        # Check 0 to 45 degrees
        elif (angle >= 0.0) and (angle < np.pi/4.0):
            horizontal_segments.append(segment)
        # Check 135 to 225 degrees
        elif (angle > np.pi-np.pi/4.0) and (angle < np.pi+np.pi/4.0):
            horizontal_segments.append(segment)
        else:
            vertical_segments.append(segment)
    
    
    # Save debug images
    input_image_with_horizontal_segments = np.copy(input_image)
    drawSegments(input_image_with_horizontal_segments, horizontal_segments)
    cv2.imwrite("segments_horizontal.jpg", input_image_with_horizontal_segments)
    input_image_with_vertical_segments = np.copy(input_image)
    drawSegments(input_image_with_vertical_segments, vertical_segments)
    cv2.imwrite("segments_vertical.jpg", input_image_with_vertical_segments)
    
    
    # Get all the possible pairs of horizontal line segments:
    pairs_of_horizontal_line_segments = getUniquePairs(horizontal_segments, image_dims)
    print("Got %d pairs of horizontal line segments" % len(pairs_of_horizontal_line_segments)) # 15 pairs, 10 after filtering
    
    # Get all the pairs of vertical line segments:
    pairs_of_vertical_line_segments = getUniquePairs(vertical_segments, image_dims)
    print("Got %d pairs of vertical line segments" % len(pairs_of_vertical_line_segments)) # 10 pairs, 6 after filtering
    
    
    # Save debug images
    for i in range(len(pairs_of_horizontal_line_segments)):
        pair = pairs_of_horizontal_line_segments[i]
        segments = [pair[0], pair[1]]
        img = np.copy(input_image)
        drawSegments(img, segments)
        suffix = str(i).zfill(2)
        cv2.imwrite("segment_pairs_horizontal_"+suffix+".jpg", img)
        #cv2.imshow("Pair of segments", img)
        #cv2.waitKey()
    for i in range(len(pairs_of_vertical_line_segments)):
        pair = pairs_of_vertical_line_segments[i]
        segments = [pair[0], pair[1]]
        img = np.copy(input_image)
        drawSegments(img, segments)
        suffix = str(i).zfill(2)
        cv2.imwrite("segment_pairs_vertical_"+suffix+".jpg", img)
        #cv2.imshow("Pair of segments", img)
        #cv2.waitKey()
    
    
    # Get all combinations of 4 line segments:
    sets_of_four_line_segments = getCombinationsOfTwoLists(pairs_of_horizontal_line_segments, pairs_of_vertical_line_segments)
    print("Got %d potential quadrilaterals" % len(sets_of_four_line_segments)) # = 60
    
    
    # Find the valid quadrilateral shapes:
    quads = getQuads(sets_of_four_line_segments, image_dims)
    print("Got %d valid quads" % len(quads))
    for i in range(len(quads)):
        img = np.copy(input_image)
        drawQuad(img, quads[i])
    
        # Save result images
        suffix = str(i).zfill(2)
        cv2.imwrite("quad_"+suffix+".jpg", img)
    
        title = "Candidate Quad " + str(i)
        cv2.imshow(title, img)
        cv2.waitKey()