My task is to find coordinates of lines (startX, startY, endX, endY) and rectangles (4 lines). Here is input file:
I use the next code:
JavaScript
x
13
13
1
img = cv.imread(image_src)
2
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
3
ret, thresh1 = cv.threshold(gray,127,255,cv.THRESH_BINARY)
4
5
edges = cv.Canny(thresh1,50,150,apertureSize = 3)
6
7
minLineLength = 100
8
maxLineGap = 10
9
lines = cv.HoughLinesP(edges,1,np.pi/180,10,minLineLength,maxLineGap)
10
print(len(lines))
11
for line in lines:
12
cv.line(img,(line[0][0],line[0][1]),(line[0][2],line[0][3]),(0,0,255),6)
13
From the last image you can see big amount of small red lines.
Questions:
- What is the best way to merge small lines?
- Why there are a lot of small portions that are not detected by HoughLinesP?
Advertisement
Answer
I have finally completed the pipeline:
- fixed incorrect parameters (as were suggested by Dan)
- developed my own ‘merging line segments’ algorithm. I had bad results when I implemented TAVARES and PADILHA algorithm (as were suggested by Andrew).
- I have skipped Canny and got better results (as were suggested by Alexander)
Please find the code and results:
JavaScript
1
214
214
1
def get_lines(lines_in):
2
if cv.__version__ < '3.0':
3
return lines_in[0]
4
return [l[0] for l in lines_in]
5
6
7
def process_lines(image_src):
8
img = mpimg.imread(image_src)
9
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
10
11
ret, thresh1 = cv.threshold(gray,127,255,cv.THRESH_BINARY)
12
13
thresh1 = cv.bitwise_not(thresh1)
14
15
edges = cv.Canny(thresh1, threshold1=50, threshold2=200, apertureSize = 3)
16
17
lines = cv.HoughLinesP(thresh1, rho=1, theta=np.pi/180, threshold=50,
18
minLineLength=50, maxLineGap=30)
19
20
# l[0] - line; l[1] - angle
21
for line in get_lines(lines):
22
leftx, boty, rightx, topy = line
23
cv.line(img, (leftx, boty), (rightx,topy), (0,0,255), 6)
24
25
# merge lines
26
27
#------------------
28
# prepare
29
_lines = []
30
for _line in get_lines(lines):
31
_lines.append([(_line[0], _line[1]),(_line[2], _line[3])])
32
33
# sort
34
_lines_x = []
35
_lines_y = []
36
for line_i in _lines:
37
orientation_i = math.atan2((line_i[0][1]-line_i[1][1]),(line_i[0][0]-line_i[1][0]))
38
if (abs(math.degrees(orientation_i)) > 45) and abs(math.degrees(orientation_i)) < (90+45):
39
_lines_y.append(line_i)
40
else:
41
_lines_x.append(line_i)
42
43
_lines_x = sorted(_lines_x, key=lambda _line: _line[0][0])
44
_lines_y = sorted(_lines_y, key=lambda _line: _line[0][1])
45
46
merged_lines_x = merge_lines_pipeline_2(_lines_x)
47
merged_lines_y = merge_lines_pipeline_2(_lines_y)
48
49
merged_lines_all = []
50
merged_lines_all.extend(merged_lines_x)
51
merged_lines_all.extend(merged_lines_y)
52
print("process groups lines", len(_lines), len(merged_lines_all))
53
img_merged_lines = mpimg.imread(image_src)
54
for line in merged_lines_all:
55
cv.line(img_merged_lines, (line[0][0], line[0][1]), (line[1][0],line[1][1]), (0,0,255), 6)
56
57
58
cv.imwrite('prediction/lines_gray.jpg',gray)
59
cv.imwrite('prediction/lines_thresh.jpg',thresh1)
60
cv.imwrite('prediction/lines_edges.jpg',edges)
61
cv.imwrite('prediction/lines_lines.jpg',img)
62
cv.imwrite('prediction/merged_lines.jpg',img_merged_lines)
63
64
return merged_lines_all
65
66
def merge_lines_pipeline_2(lines):
67
super_lines_final = []
68
super_lines = []
69
min_distance_to_merge = 30
70
min_angle_to_merge = 30
71
72
for line in lines:
73
create_new_group = True
74
group_updated = False
75
76
for group in super_lines:
77
for line2 in group:
78
if get_distance(line2, line) < min_distance_to_merge:
79
# check the angle between lines
80
orientation_i = math.atan2((line[0][1]-line[1][1]),(line[0][0]-line[1][0]))
81
orientation_j = math.atan2((line2[0][1]-line2[1][1]),(line2[0][0]-line2[1][0]))
82
83
if int(abs(abs(math.degrees(orientation_i)) - abs(math.degrees(orientation_j)))) < min_angle_to_merge:
84
#print("angles", orientation_i, orientation_j)
85
#print(int(abs(orientation_i - orientation_j)))
86
group.append(line)
87
88
create_new_group = False
89
group_updated = True
90
break
91
92
if group_updated:
93
break
94
95
if (create_new_group):
96
new_group = []
97
new_group.append(line)
98
99
for idx, line2 in enumerate(lines):
100
# check the distance between lines
101
if get_distance(line2, line) < min_distance_to_merge:
102
# check the angle between lines
103
orientation_i = math.atan2((line[0][1]-line[1][1]),(line[0][0]-line[1][0]))
104
orientation_j = math.atan2((line2[0][1]-line2[1][1]),(line2[0][0]-line2[1][0]))
105
106
if int(abs(abs(math.degrees(orientation_i)) - abs(math.degrees(orientation_j)))) < min_angle_to_merge:
107
#print("angles", orientation_i, orientation_j)
108
#print(int(abs(orientation_i - orientation_j)))
109
110
new_group.append(line2)
111
112
# remove line from lines list
113
#lines[idx] = False
114
# append new group
115
super_lines.append(new_group)
116
117
118
for group in super_lines:
119
super_lines_final.append(merge_lines_segments1(group))
120
121
return super_lines_final
122
123
def merge_lines_segments1(lines, use_log=False):
124
if(len(lines) == 1):
125
return lines[0]
126
127
line_i = lines[0]
128
129
# orientation
130
orientation_i = math.atan2((line_i[0][1]-line_i[1][1]),(line_i[0][0]-line_i[1][0]))
131
132
points = []
133
for line in lines:
134
points.append(line[0])
135
points.append(line[1])
136
137
if (abs(math.degrees(orientation_i)) > 45) and abs(math.degrees(orientation_i)) < (90+45):
138
139
#sort by y
140
points = sorted(points, key=lambda point: point[1])
141
142
if use_log:
143
print("use y")
144
else:
145
146
#sort by x
147
points = sorted(points, key=lambda point: point[0])
148
149
if use_log:
150
print("use x")
151
152
return [points[0], points[len(points)-1]]
153
154
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html
155
# https://stackoverflow.com/questions/32702075/what-would-be-the-fastest-way-to-find-the-maximum-of-all-possible-distances-betw
156
def lines_close(line1, line2):
157
dist1 = math.hypot(line1[0][0] - line2[0][0], line1[0][0] - line2[0][1])
158
dist2 = math.hypot(line1[0][2] - line2[0][0], line1[0][3] - line2[0][1])
159
dist3 = math.hypot(line1[0][0] - line2[0][2], line1[0][0] - line2[0][3])
160
dist4 = math.hypot(line1[0][2] - line2[0][2], line1[0][3] - line2[0][3])
161
162
if (min(dist1,dist2,dist3,dist4) < 100):
163
return True
164
else:
165
return False
166
167
def lineMagnitude (x1, y1, x2, y2):
168
lineMagnitude = math.sqrt(math.pow((x2 - x1), 2)+ math.pow((y2 - y1), 2))
169
return lineMagnitude
170
171
#Calc minimum distance from a point and a line segment (i.e. consecutive vertices in a polyline).
172
# https://nodedangles.wordpress.com/2010/05/16/measuring-distance-from-a-point-to-a-line-segment/
173
# http://paulbourke.net/geometry/pointlineplane/
174
def DistancePointLine(px, py, x1, y1, x2, y2):
175
#http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/source.vba
176
LineMag = lineMagnitude(x1, y1, x2, y2)
177
178
if LineMag < 0.00000001:
179
DistancePointLine = 9999
180
return DistancePointLine
181
182
u1 = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1)))
183
u = u1 / (LineMag * LineMag)
184
185
if (u < 0.00001) or (u > 1):
186
#// closest point does not fall within the line segment, take the shorter distance
187
#// to an endpoint
188
ix = lineMagnitude(px, py, x1, y1)
189
iy = lineMagnitude(px, py, x2, y2)
190
if ix > iy:
191
DistancePointLine = iy
192
else:
193
DistancePointLine = ix
194
else:
195
# Intersecting point is on the line, use the formula
196
ix = x1 + u * (x2 - x1)
197
iy = y1 + u * (y2 - y1)
198
DistancePointLine = lineMagnitude(px, py, ix, iy)
199
200
return DistancePointLine
201
202
def get_distance(line1, line2):
203
dist1 = DistancePointLine(line1[0][0], line1[0][1],
204
line2[0][0], line2[0][1], line2[1][0], line2[1][1])
205
dist2 = DistancePointLine(line1[1][0], line1[1][1],
206
line2[0][0], line2[0][1], line2[1][0], line2[1][1])
207
dist3 = DistancePointLine(line2[0][0], line2[0][1],
208
line1[0][0], line1[0][1], line1[1][0], line1[1][1])
209
dist4 = DistancePointLine(line2[1][0], line2[1][1],
210
line1[0][0], line1[0][1], line1[1][0], line1[1][1])
211
212
213
return min(dist1,dist2,dist3,dist4)
214
There are still 572 lines. After my “merging line segments” we have only 89 lines