ランダムに配置された線分から可能なすべての四辺形を検出して完成させたいです!
添付の写真は一例です。線は常に非常に異なる場所に表示される場合があります。
誰でもこれに適したアルゴリズムを指摘できますか?
- 線分はopencv2.4.2を使用したハフ変換の出力であることに注意してください
解決策は、黄色の四辺形を検出して予測することです
ランダムに配置された線分から可能なすべての四辺形を検出して完成させたいです!
添付の写真は一例です。線は常に非常に異なる場所に表示される場合があります。
誰でもこれに適したアルゴリズムを指摘できますか?
解決策は、黄色の四辺形を検出して予測することです
11の線分の場合、4つの線を選択する330の方法があります。各組み合わせが四辺形になる可能性を判断し、そのように評価することができます。
ハフ変換で線以外のフォームを検出することは可能ですが、アキュムレータ空間には2次元以上が必要になるため、視覚化が難しくなります。円は3次元(midX、midY、radius)で、楕円は4次元(私は信じています)で見つけることができます。四辺形をモデル化するために必要なパラメーターが正確にいくつあるかはわかりません。3次元より高くなると、ハフ変換のパフォーマンスが低下し始めると思います。アキュムレータスペースが大きくなり、ノイズ比が大幅に増加します。
ここにあなたのためにいくつかの興味深い答えがあるかもしれない関連する質問があります。
乗り方を教えてください!
私は今日この問題を突き止め、解決策をGitHubにアップロードしました。ここに投稿するにはコードが多すぎます。
これが出力を示すスクリーンショットです:
私が取った解決策は、基本的に、この編集の前に上で説明したものです。
評価は、大まかなエラースコアを計算することによって機能します。これは、2つの異なるタイプのエラーの合計です。
2番目のタイプのエラーは、より堅牢な方法で判別できる可能性があります。サンプルデータセットの解決策を見つける必要がありました。
私は他のデータセットを試していません。より堅牢にするために、多少の調整が必要になる場合があります。特定の環境に簡単に適応できるように、あまり多くのパラメーターを使用しないようにしました。たとえば、サンプル画像に見られるように、オクルージョンに対する感度を制御します。
それは私のラップトップで約160msで解決策を見つけます。ただし、パフォーマンスの最適化は行っていません。コンピュータービジョンの実験でよくあることですが、これをリアルタイムに近づける必要がある場合は、組み合わせ/順列を見つける方法を大幅に最適化できると思います。
角度などに制約を課さなければ、四辺形を形成するために約4本の線を完成させることができます。
四辺形が間違っている可能性のある画像:
おそらく、私の例で示した黄色のような四辺形を含めたくないでしょう。角度、最小/最大サイズ、アスペクト比、および許可される完成度に制約を設ける必要があります。完全な四辺形を形成するために線の90%を追加する必要がある場合、これはおそらくあまり良い候補ではありません。
線の可能なすべての組み合わせをテストし、それらにヒューリスティックを適用してポイントを与える必要があるのではないかと心配しています。90度に近い角度(必要な場合は長方形)、完全性、予想されるアスペクト比に近いアスペクト比などの多くのポイント。
アップデート
ポイントシステムを使用すると、厳密なルールを適用するよりも利点があります。
(擬似コードで)厳密なルールがあるとしましょう:
(angles == 90 +/- 10 degrees) && (line_completeness>50%)
これは機能しますが、のような状況につながる可能性がありangles == 90 +/- 1 degree) && (line_completeness == 45%)
ます。規則によれば、この四辺形は、ラインの完全性が低いために通過しません。ただし、角度の品質は並外れており、それでも非常に優れた候補となっています。
ポイントを与える方が良いです。たとえば、正確に90度の角度の場合は20ポイント、90 +/- 15度の角度の場合は0ポイント、完全な線の場合は10ポイント、完全な線の場合は25%だけです。これにより、線の完全性よりも角度が重要になり、絶対的なルールがない問題に対してよりソフトな条件が作成されます。
私はC#を使用していないので、コードを翻訳する必要があります。次のコードはJavaです。付属のテストケースでテストしました。stackoverflowに添付ファイルを追加する方法がまだわからないため、実際のコードをここに含めます。
4つのクラス(ShapeFinder、Line、Point、およびQuadrilateral)と1つのテストクラス(ShapeFinderTest)があります。
ShapeFinderクラス:
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());
}
}
}
例から、質問は、各辺に完全に含まれる線を持つすべての四辺形を検索する線に沿っていると思います。これは、提供された説明からはまったく明らかではありません。
以下は、かなり簡単に実装できる擬似コードです。ここで、O(N ^ 4)の複雑さを防ぐための効率的なデータ構造を作成します。たぶん、位置やグラデーションで線を並べ替えます。
i、j、k、lは次のとおりです。
l
|---|
j| |k
|---|
i
extendIntersect
は、2本の線を無限大(または選択した境界)に拡張し、それらが交差する点を返す関数であり、数学的に簡単に実行できます。
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が提案したような、ある種のエラーチェックも良い考えかもしれません。
解決策1:
これは、OpenCV2.4とSympyを使用してPython2.7.xで記述された完全な解決策です。
D.Noakesのデータ(線分)を使用しましたが、別のアプローチを取りました。
問題の定義:
一連の線分について、セグメントが四角形のエッジの内側に収まる可能性のあるすべての四角形の形状を見つけます。
方法:
結果:
このメソッドは、画像内の4つの四角形の形状を検出します
アニメーションGIFを参照してください:https ://ibb.co/4Rv9rJW
コード:https ://pastiebin.com/5f3836269f7e5
#!/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()