Siromanec commited on
Commit
e3b8726
1 Parent(s): c9513f4

algorithm update: in order to be fit, the detected line is required to be in the same direction as proposed one

Browse files
Files changed (1) hide show
  1. handcrafted_solution.py +90 -48
handcrafted_solution.py CHANGED
@@ -104,45 +104,38 @@ def get_vertices_and_edges_from_segmentation(gest_seg_np, edge_th=50.0):
104
  color_range = 4.
105
  connections = []
106
  edge_th = edge_th ** 2
 
107
 
108
  apex_centroids, eave_end_point_centroids, apex_mask, eave_end_point_mask = get_vertices(gest_seg_np)
109
 
110
- apex_pts = np.concatenate([apex_centroids, eave_end_point_centroids])
111
 
112
- # vertex_mask = np.zeros_like(apex_mask)
113
- # for i in apex_centroids:
114
- # cv2.circle(vertex_mask, np.round(i).astype(np.uint32), 20, (255,), 40, -1)
115
- # for i in eave_end_point_centroids:
116
- # cv2.circle(vertex_mask, np.round(i).astype(np.uint32), 15, (255,), 30, -1)
117
- # vertex_mask = np.bitwise_not(vertex_mask)
118
 
119
  scale = 1
120
- vertex_size = np.zeros(apex_pts.shape[0])
121
- for i, coords in enumerate(apex_pts):
122
  # coords = np.round(coords).astype(np.uint32)
123
  radius = 25#np.clip(int(max_depth//2 + depth_np[coords[1], coords[0]]), 10, 30)#int(np.clip(max_depth - depth_np[coords[1], coords[0]], 10, 20))
124
  vertex_size[i] = (scale*radius) ** 2 # because we are using squared distances
125
- # cv2.circle(vertex_mask, coords, radius - 5, (255, ), 2 * (radius - 5), -1)
126
- # vertex_size *= scale
127
  for edge_class in ['eave', 'ridge', 'rake', 'valley', 'flashing', 'step_flashing']:
128
- if len(apex_pts) < 2:
129
  break
130
  edge_color = np.array(gestalt_color_mapping[edge_class])
131
 
132
  mask = cv2.inRange(gest_seg_np,
133
  edge_color-color_range,
134
  edge_color+color_range)
135
- # mask = cv2.bitwise_and(mask, vertex_mask)
136
-
137
  mask = cv2.morphologyEx(mask,
138
  cv2.MORPH_DILATE, np.ones((3, 3)), iterations=1)
139
- # if edge_class == "ridge":
140
- # mask = cv2.morphologyEx(mask,
141
- # cv2.MORPH_DILATE, np.ones((11, 11)), iterations=1)
142
 
143
 
144
  if np.any(mask):
145
 
 
 
 
146
  rho = 1 # distance resolution in pixels of the Hough grid
147
  theta = np.pi / 180 # angular resolution in radians of the Hough grid
148
  threshold = 20 # minimum number of votes (intersections in Hough grid cell)
@@ -155,59 +148,108 @@ def get_vertices_and_edges_from_segmentation(gest_seg_np, edge_th=50.0):
155
  lines = cv2.HoughLinesP(mask, rho, theta, threshold, np.array([]),
156
  min_line_length, max_line_gap)
157
 
 
158
  edges = []
159
 
160
- for line in lines if lines is not None else []:
 
 
 
 
161
  for x1,y1,x2,y2 in line:
162
- extend = 40
163
  if x1 < x2:
164
  x1, y1, x2, y2 = x2, y2, x1, y1
165
- # cv2.line(mask_copy,(x1,y1),(x2,y2),(255,),10, cv2.LINE_AA)
166
  direction = (np.array([x2 - x1, y2 - y1]))
167
- direction = extend * direction / np.linalg.norm(direction)
 
 
 
168
 
169
- # norm = extend * np.linalg.norm(np.array([y2 - y1, x1 - x2]))
170
  x1,y1 = (-direction + (x1, y1)).astype(np.int32)
171
  x2,y2 = (+ direction + (x2, y2)).astype(np.int32)
172
 
173
-
174
  edges.append((x1, y1, x2, y2))
175
 
176
- edges = np.array(edges)
177
- if len(edges) < 1:
178
- continue
179
 
180
- begin_distances = cdist(apex_pts, edges[:, :2], metric="sqeuclidean")
181
- end_distances = cdist(apex_pts, edges[:, 2:], metric="sqeuclidean")
182
 
183
- begin_closest_points = np.argmin(begin_distances, axis=0) # index of the closest point for each edge end point
184
- end_closest_points = np.argmin(end_distances, axis=0)
185
 
186
- begin_closest_point_distances = begin_distances[begin_closest_points, np.arange(len(begin_closest_points))]
187
- end_closest_point_distances = end_distances[end_closest_points, np.arange(len(end_closest_points))]
 
 
 
 
188
 
 
 
189
 
 
 
190
 
191
- begin_in_range_mask = begin_closest_point_distances < vertex_size[begin_closest_points]
192
- end_in_range_mask = end_closest_point_distances < vertex_size[end_closest_points]
193
 
194
  # where both ends are in range
195
- in_range_connected_mask = np.logical_and(begin_in_range_mask, end_in_range_mask)
196
-
197
- edge_idxs = np.where(in_range_connected_mask)[0]
198
-
199
- edges = np.array([begin_closest_points[edge_idxs], end_closest_points[edge_idxs]]).T
200
- if len(edges) < 1:
201
- continue
202
- edges = np.sort(edges, axis=1)
203
- unique_edges = np.unique(edges, axis=0)
204
-
205
- unique_edges = unique_edges[unique_edges[:, 0] != unique_edges[:, 1]] # remove self loops
206
-
207
- if len(unique_edges) < 1:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  continue
209
- connections.extend(unique_edges)
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
  vertices = [{"xy": v, "type": "apex"} for v in apex_centroids]
213
  vertices += [{"xy": v, "type": "eave_end_point"} for v in eave_end_point_centroids]
 
104
  color_range = 4.
105
  connections = []
106
  edge_th = edge_th ** 2
107
+ deviation_threshold = np.cos(np.deg2rad(5))
108
 
109
  apex_centroids, eave_end_point_centroids, apex_mask, eave_end_point_mask = get_vertices(gest_seg_np)
110
 
111
+ vertices = np.concatenate([apex_centroids, eave_end_point_centroids])
112
 
 
 
 
 
 
 
113
 
114
  scale = 1
115
+ vertex_size = np.zeros(vertices.shape[0])
116
+ for i, coords in enumerate(vertices):
117
  # coords = np.round(coords).astype(np.uint32)
118
  radius = 25#np.clip(int(max_depth//2 + depth_np[coords[1], coords[0]]), 10, 30)#int(np.clip(max_depth - depth_np[coords[1], coords[0]], 10, 20))
119
  vertex_size[i] = (scale*radius) ** 2 # because we are using squared distances
120
+
 
121
  for edge_class in ['eave', 'ridge', 'rake', 'valley', 'flashing', 'step_flashing']:
122
+ if len(vertices) < 2:
123
  break
124
  edge_color = np.array(gestalt_color_mapping[edge_class])
125
 
126
  mask = cv2.inRange(gest_seg_np,
127
  edge_color-color_range,
128
  edge_color+color_range)
 
 
129
  mask = cv2.morphologyEx(mask,
130
  cv2.MORPH_DILATE, np.ones((3, 3)), iterations=1)
131
+
 
 
132
 
133
 
134
  if np.any(mask):
135
 
136
+
137
+
138
+
139
  rho = 1 # distance resolution in pixels of the Hough grid
140
  theta = np.pi / 180 # angular resolution in radians of the Hough grid
141
  threshold = 20 # minimum number of votes (intersections in Hough grid cell)
 
148
  lines = cv2.HoughLinesP(mask, rho, theta, threshold, np.array([]),
149
  min_line_length, max_line_gap)
150
 
151
+
152
  edges = []
153
 
154
+ if lines is None:
155
+ continue
156
+
157
+ line_directions = np.zeros((len(lines), 2))
158
+ for line_idx, line in enumerate(lines):
159
  for x1,y1,x2,y2 in line:
160
+ extend = 35
161
  if x1 < x2:
162
  x1, y1, x2, y2 = x2, y2, x1, y1
 
163
  direction = (np.array([x2 - x1, y2 - y1]))
164
+ direction = direction / np.linalg.norm(direction)
165
+ line_directions[line_idx] = direction
166
+
167
+ direction = extend * direction
168
 
 
169
  x1,y1 = (-direction + (x1, y1)).astype(np.int32)
170
  x2,y2 = (+ direction + (x2, y2)).astype(np.int32)
171
 
 
172
  edges.append((x1, y1, x2, y2))
173
 
 
 
 
174
 
 
 
175
 
 
 
176
 
177
+ edges = np.array(edges)
178
+ if len(edges) <1:
179
+ continue
180
+ # calculate the distances between the vertices and the edge ends
181
+ begin_distances = cdist(vertices, edges[:, :2], metric="sqeuclidean")
182
+ end_distances = cdist(vertices, edges[:, 2:], metric="sqeuclidean")
183
 
184
+ begin_in_range_mask = begin_distances < vertex_size[:, np.newaxis]
185
+ end_in_range_mask = end_distances < vertex_size[:, np.newaxis]
186
 
187
+ in_range_connected_mask = np.logical_and(np.any(begin_in_range_mask, axis=0),
188
+ np.any(end_in_range_mask, axis=0))
189
 
 
 
190
 
191
  # where both ends are in range
192
+ begin_in_range_mask = np.logical_and(begin_in_range_mask, in_range_connected_mask)
193
+ end_in_range_mask = np.logical_and(end_in_range_mask, in_range_connected_mask)
194
+
195
+ begin_candidates = np.array(np.where(begin_in_range_mask))
196
+ end_candidates = np.array(np.where(end_in_range_mask))
197
+
198
+ # sort the candidates by line index; required for the seamlessnes np.split
199
+ sorted_begin_indices = np.argsort(begin_candidates[1])
200
+ sorted_end_indices = np.argsort(end_candidates[1])
201
+ begin_candidates = begin_candidates[:, sorted_begin_indices]
202
+ end_candidates = end_candidates[:, sorted_end_indices]
203
+
204
+
205
+ # create all possible connections between begin and end candidates that correspond to a line
206
+ grouped_begins = np.split(begin_candidates[0], np.unique(begin_candidates[1], return_index=True)[1][1:])
207
+ grouped_ends = np.split(end_candidates[0], np.unique(end_candidates[1], return_index=True)[1][1:])
208
+ line_indices = np.unique(begin_candidates[1])
209
+
210
+ # create all possible connections between begin and end candidates that correspond to a line
211
+ begin_vertex_list = []
212
+ end_vertex_list = []
213
+ line_idx_list = []
214
+ for begin_vertex, end_vertex, line_idx in zip(grouped_begins, grouped_ends, line_indices):
215
+ begin_vertex, end_vertex = np.meshgrid(begin_vertex, end_vertex)
216
+ begin_vertex_list.extend(begin_vertex.flatten())
217
+ end_vertex_list.extend(end_vertex.flatten())
218
+ line_idx_list.extend([line_idx] * len(begin_vertex.flatten()))
219
+
220
+ line_idx_list = np.array(line_idx_list)
221
+ all_connections = np.array([begin_vertex_list, end_vertex_list])
222
+
223
+ # decrease the number of possible connections to reduce number of calculations
224
+ possible_connections = np.unique(all_connections, axis=1)
225
+ possible_connections = np.sort(possible_connections, axis=0)
226
+ possible_connections = np.unique(possible_connections, axis=1)
227
+ possible_connections = possible_connections[:, possible_connections[0, :] != possible_connections[1, :]]
228
+
229
+ if possible_connections.shape[1] < 1:
230
  continue
 
231
 
232
+ # precalculate the possible direction vectors
233
+ possible_direction_vectors = vertices[possible_connections[0]] - vertices[possible_connections[1]]
234
+ possible_direction_vectors = possible_direction_vectors / np.linalg.norm(possible_direction_vectors, axis=1)[:, np.newaxis]
235
+
236
+ owned_lines_per_possible_connections = [list() for i in range(possible_connections.shape[1])]
237
+
238
+ # assign lines to possible connections
239
+ for line_idx, i,j in zip(line_idx_list, begin_vertex_list, end_vertex_list):
240
+ if i == j:
241
+ continue
242
+ i, j = min(i, j), max(i, j)
243
+ for connection_idx, connection in enumerate(possible_connections.T):
244
+ if np.all((i, j) == connection):
245
+ owned_lines_per_possible_connections[connection_idx].append(line_idx)
246
+ break
247
+
248
+ # check if the lines are in the same direction as the possible connection
249
+ for fitted_line_idx, owned_lines_per_possible_connection in enumerate(owned_lines_per_possible_connections):
250
+ line_deviations = np.abs(np.dot(line_directions[owned_lines_per_possible_connection], possible_direction_vectors[fitted_line_idx]))
251
+ if np.any(line_deviations > deviation_threshold):
252
+ connections.append(possible_connections[:, fitted_line_idx])
253
 
254
  vertices = [{"xy": v, "type": "apex"} for v in apex_centroids]
255
  vertices += [{"xy": v, "type": "eave_end_point"} for v in eave_end_point_centroids]