oValach commited on
Commit
500eb09
1 Parent(s): 548f77e

Upload 2 files

Browse files
Files changed (2) hide show
  1. TheDistanceAssessor.py +922 -0
  2. app.py +27 -0
TheDistanceAssessor.py ADDED
@@ -0,0 +1,922 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import torch
3
+ import numpy as np
4
+ from skimage import morphology
5
+ import albumentations as A
6
+ import torch.nn.functional as F
7
+ import torch.nn as nn
8
+ from albumentations.pytorch import ToTensorV2
9
+ import matplotlib.pyplot as plt
10
+ from sklearn.linear_model import LinearRegression
11
+ from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
12
+ import matplotlib.path as mplPath
13
+ import matplotlib.patches as patches
14
+ from ultralyticsplus import YOLO
15
+
16
+ def image_morpho(mask_prediction):
17
+ selem2 = morphology.disk(2)
18
+ closed = morphology.closing(mask_prediction, selem2)
19
+
20
+ return closed
21
+
22
+ def get_segformer_img(image_in, input_size=[224,224]):
23
+ transform_img = A.Compose([
24
+ A.Resize(height=input_size[0], width=input_size[1], interpolation=cv2.INTER_NEAREST),
25
+ A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], max_pixel_value=255.0, p=1.0),
26
+ ToTensorV2(p=1.0),
27
+ ])
28
+
29
+ image_in = cv2.resize(image_in, (1920, 1080))
30
+
31
+ image_tr = transform_img(image=image_in)['image']
32
+ image_tr = image_tr.unsqueeze(0)
33
+ image_tr = image_tr.cpu()
34
+
35
+ return image_tr, image_in
36
+
37
+ def load_segformer(path_model):
38
+
39
+ model = torch.load(path_model, map_location=torch.device('cpu'))
40
+ model = model.cpu()
41
+ model.eval()
42
+ return model
43
+
44
+ def load_yolo(PATH_model):
45
+ model = YOLO(PATH_model)
46
+
47
+ model.overrides['conf'] = 0.25 # NMS confidence threshold
48
+ model.overrides['iou'] = 0.45 # NMS IoU threshold
49
+ model.overrides['agnostic_nms'] = False # NMS class-agnostic
50
+ model.overrides['max_det'] = 1000 # maximum number of detections per image
51
+ return model
52
+
53
+ def find_extreme_y_values(arr, values=[0, 6]):
54
+ """
55
+ Optimized function to find the lowest and highest y-values (row indices) in a 2D array where 0 or 6 appears.
56
+
57
+ Parameters:
58
+ - arr: The input 2D NumPy array.
59
+ - values: The values to search for (default is [0, 6]).
60
+
61
+ Returns:
62
+ A tuple (lowest_y, highest_y) representing the lowest and highest y-values. If values are not found, returns None.
63
+ """
64
+ mask = np.isin(arr, values)
65
+ rows_with_values = np.any(mask, axis=1)
66
+
67
+ y_indices = np.nonzero(rows_with_values)[0] # Directly finding non-zero (True) indices
68
+
69
+ if y_indices.size == 0:
70
+ return None, None # Early return if values not found
71
+
72
+ return y_indices[0], y_indices[-1]
73
+
74
+ def find_nearest_pairs(arr1, arr2):
75
+ # Convert lists to numpy arrays for vectorized operations
76
+ arr1_np = np.array(arr1)
77
+ arr2_np = np.array(arr2)
78
+
79
+ # Determine which array is shorter
80
+ if len(arr1_np) < len(arr2_np):
81
+ base_array, compare_array = arr1_np, arr2_np
82
+ else:
83
+ base_array, compare_array = arr2_np, arr1_np
84
+
85
+ paired_base = []
86
+ paired_compare = []
87
+
88
+ # Mask to keep track of paired elements
89
+ paired_mask = np.zeros(len(compare_array), dtype=bool)
90
+
91
+ for item in base_array:
92
+ # Calculate distances from the current item to all items in the compare_array
93
+ distances = np.linalg.norm(compare_array - item, axis=1)
94
+ nearest_index = np.argmin(distances)
95
+ paired_base.append(item)
96
+ paired_compare.append(compare_array[nearest_index])
97
+ # Mark the paired element to exclude it from further pairing
98
+ paired_mask[nearest_index] = True
99
+
100
+ # Check if all elements from the compare_array have been paired
101
+ if paired_mask.all():
102
+ break
103
+
104
+ paired_base = np.array(paired_base)
105
+ paired_compare = compare_array[paired_mask]
106
+
107
+ return (paired_base, paired_compare) if len(arr1_np) < len(arr2_np) else (paired_compare, paired_base)
108
+
109
+ def filter_crossings(image, edges_dict):
110
+ filtered_edges = {}
111
+ for key, values in edges_dict.items():
112
+ merged = [values[0]]
113
+ for start, end in values[1:]:
114
+ if start - merged[-1][1] < 50:
115
+
116
+ key_up = max([0, key-10])
117
+ key_down = min([image.shape[0]-1, key+10])
118
+ if key_up == 0:
119
+ key_up = key+20
120
+ if key_down == image.shape[0]-1:
121
+ key_down = key-20
122
+
123
+ edges_to_test_slope1 = robust_edges(image, [key_up], values=[0, 6], min_width=19)
124
+ edges_to_test_slope2 = robust_edges(image, [key_down], values=[0, 6], min_width=19)
125
+
126
+ values1, edges_to_test_slope1 = find_nearest_pairs(values, edges_to_test_slope1)
127
+ values2, edges_to_test_slope2 = find_nearest_pairs(values, edges_to_test_slope2)
128
+
129
+ differences_y = []
130
+ for i, value in enumerate(values1):
131
+ if start in value:
132
+ idx = list(value).index(start)
133
+ try:
134
+ differences_y.append(abs(start-edges_to_test_slope1[i][idx]))
135
+ except:
136
+ pass
137
+ if merged[-1][1] in value:
138
+ idx = list(value).index(merged[-1][1])
139
+ try:
140
+ differences_y.append(abs(merged[-1][1]-edges_to_test_slope1[i][idx]))
141
+ except:
142
+ pass
143
+ for i, value in enumerate(values2):
144
+ if start in value:
145
+ idx = list(value).index(start)
146
+ try:
147
+ differences_y.append(abs(start-edges_to_test_slope2[i][idx]))
148
+ except:
149
+ pass
150
+ if merged[-1][1] in value:
151
+ idx = list(value).index(merged[-1][1])
152
+ try:
153
+ differences_y.append(abs(merged[-1][1]-edges_to_test_slope2[i][idx]))
154
+ except:
155
+ pass
156
+
157
+ if any(element > 30 for element in differences_y):
158
+ merged[-1] = (merged[-1][0], end)
159
+ else:
160
+ merged.append((start, end))
161
+ else:
162
+ merged.append((start, end))
163
+ filtered_edges[key] = merged
164
+
165
+ return filtered_edges
166
+
167
+ def robust_edges(image, y_levels, values=[0, 6], min_width=19):
168
+
169
+ for y in y_levels:
170
+ row = image[y, :]
171
+ mask = np.isin(row, values).astype(int)
172
+ padded_mask = np.pad(mask, (1, 1), 'constant', constant_values=0)
173
+ diff = np.diff(padded_mask)
174
+ starts = np.where(diff == 1)[0]
175
+ ends = np.where(diff == -1)[0] - 1
176
+
177
+ # Filter sequences based on the minimum width criteria
178
+ filtered_edges = [(start, end) for start, end in zip(starts, ends) if end - start + 1 >= min_width]
179
+ filtered_edges = [(start, end) for start, end in filtered_edges if 0 not in (start, end) and 1919 not in (start, end)]
180
+
181
+ return filtered_edges
182
+
183
+ def find_edges(image, y_levels, values=[0, 6], min_width=19):
184
+ """
185
+ Find start and end positions of continuous sequences of specified values at given y-levels in a 2D array,
186
+ filtering for sequences that meet or exceed a specified minimum width.
187
+
188
+ Parameters:
189
+ - arr: 2D NumPy array to search within.
190
+ - y_levels: List of y-levels (row indices) to examine.
191
+ - values: Values to search for (default is [0, 6]).
192
+ - min_width: Minimum width of sequences to be included in the results.
193
+
194
+ Returns:
195
+ A dict with y-levels as keys and lists of (start, end) tuples for each sequence found in that row that meets the width criteria.
196
+ """
197
+ edges_dict = {}
198
+ for y in y_levels:
199
+ row = image[y, :]
200
+ mask = np.isin(row, values).astype(int)
201
+ padded_mask = np.pad(mask, (1, 1), 'constant', constant_values=0)
202
+ diff = np.diff(padded_mask)
203
+ starts = np.where(diff == 1)[0]
204
+ ends = np.where(diff == -1)[0] - 1
205
+
206
+ # Filter sequences based on the minimum width criteria
207
+ filtered_edges = [(start, end) for start, end in zip(starts, ends) if end - start + 1 >= min_width]
208
+ filtered_edges = [(start, end) for start, end in filtered_edges if 0 not in (start, end) and 1919 not in (start, end)]
209
+
210
+ edges_with_guard_rails = []
211
+ for edge in filtered_edges:
212
+ cutout_left = image[y,edge[0]-50:edge[0]][::-1]
213
+ cutout_right = image[y,edge[1]:edge[1]+50]
214
+
215
+ not_ones = np.where(cutout_left != 1)[0]
216
+ if len(not_ones) > 0 and not_ones[0] > 0:
217
+ last_one_index = not_ones[0] - 1
218
+ edge = (edge[0] - last_one_index,) + edge[1:]
219
+ else:
220
+ last_one_index = None if len(not_ones) == 0 else not_ones[-1] - 1
221
+
222
+ not_ones = np.where(cutout_right != 1)[0]
223
+ if len(not_ones) > 0 and not_ones[0] > 0:
224
+ last_one_index = not_ones[0] - 1
225
+ edge = (edge[0], edge[1] - last_one_index) + edge[2:]
226
+ else:
227
+ last_one_index = None if len(not_ones) == 0 else not_ones[-1] - 1
228
+
229
+ edges_with_guard_rails.append(edge)
230
+
231
+ edges_dict[y] = edges_with_guard_rails
232
+
233
+ edges_dict = {k: v for k, v in edges_dict.items() if v}
234
+
235
+ edges_dict = filter_crossings(image, edges_dict)
236
+
237
+ return edges_dict
238
+
239
+ def find_rails(arr, y_levels, values=[9, 10], min_width=5):
240
+ edges_all = []
241
+ for y in y_levels:
242
+ row = arr[y, :]
243
+ mask = np.isin(row, values).astype(int)
244
+ padded_mask = np.pad(mask, (1, 1), 'constant', constant_values=0)
245
+ diff = np.diff(padded_mask)
246
+ starts = np.where(diff == 1)[0]
247
+ ends = np.where(diff == -1)[0] - 1
248
+
249
+ # Filter sequences based on the minimum width criteria
250
+ filtered_edges = [(start, end) for start, end in zip(starts, ends) if end - start + 1 >= min_width]
251
+ filtered_edges = [(start, end) for start, end in filtered_edges if 0 not in (start, end) and 1919 not in (start, end)]
252
+ edges_all = filtered_edges
253
+
254
+ return edges_all
255
+
256
+ def mark_edges(arr, edges_dict, mark_value=30):
257
+ """
258
+ Marks a 5x5 zone around the edges found in the array with a specific value.
259
+
260
+ Parameters:
261
+ - arr: The original 2D NumPy array.
262
+ - edges_dict: A dictionary with y-levels as keys and lists of (start, end) tuples for edges.
263
+ - mark_value: The value used to mark the edges.
264
+
265
+ Returns:
266
+ The modified array with marked zones.
267
+ """
268
+ marked_arr = np.copy(arr) # Create a copy of the array to avoid modifying the original
269
+ offset = 2 # To mark a 5x5 area, we go 2 pixels in each direction from the center
270
+
271
+ for y, edges in edges_dict.items():
272
+ for start, end in edges:
273
+ # Mark a 5x5 zone around the start and end positions
274
+ for dy in range(-offset, offset + 1):
275
+ for dx in range(-offset, offset + 1):
276
+ # Check array bounds before marking
277
+ if 0 <= y + dy < marked_arr.shape[0] and 0 <= start + dx < marked_arr.shape[1]:
278
+ marked_arr[y + dy, start + dx] = mark_value
279
+ if 0 <= y + dy < marked_arr.shape[0] and 0 <= end + dx < marked_arr.shape[1]:
280
+ marked_arr[y + dy, end + dx] = mark_value
281
+
282
+ return marked_arr
283
+
284
+ def find_rail_sides(img, edges_dict):
285
+ left_border = []
286
+ right_border = []
287
+ for y,xs in edges_dict.items():
288
+ rails = find_rails(img, [y], values=[9,10], min_width=5)
289
+ left_border_actual = [min(xs)[0],y]
290
+ right_border_actual = [max(xs)[1],y]
291
+
292
+ for zone in rails:
293
+ if abs(zone[1]-left_border_actual[0]) < y*0.04: # dynamic treshold
294
+ left_border_actual[0] = zone[0]
295
+ if abs(zone[0]-right_border_actual[0]) < y*0.04:
296
+ right_border_actual[0] = zone[1]
297
+
298
+ left_border.append(left_border_actual)
299
+ right_border.append(right_border_actual)
300
+
301
+ # removing detected uncontioussness
302
+ left_border, flags_l, _ = robust_rail_sides(left_border) # filter outliers
303
+ right_border, flags_r, _ = robust_rail_sides(right_border)
304
+
305
+ return left_border, right_border, flags_l, flags_r
306
+
307
+ def robust_rail_sides(border, threshold=7):
308
+ border = np.array(border)
309
+ if border.size > 0:
310
+ # delete borders found on the bottom side of the image
311
+ border = border[border[:, 1] != 1079]
312
+
313
+ steps_x = np.diff(border[:, 0])
314
+ median_step = np.median(np.abs(steps_x))
315
+
316
+ threshold_step = np.abs(threshold*np.abs(median_step))
317
+ treshold_overcommings = abs(steps_x) > abs(threshold_step)
318
+
319
+ flags = []
320
+
321
+ if True not in treshold_overcommings:
322
+ return border, flags, []
323
+ else:
324
+ overcommings_indices = [i for i, element in enumerate(treshold_overcommings) if element == True]
325
+ if overcommings_indices and np.all(np.diff(overcommings_indices) == 1):
326
+ overcommings_indices = [overcommings_indices[0]]
327
+
328
+ filtered_border = border
329
+
330
+ previously_deleted = []
331
+ for i in overcommings_indices:
332
+ for item in previously_deleted:
333
+ if item[0] < i:
334
+ i -= item[1]
335
+ first_part = filtered_border[:i+1]
336
+ second_part = filtered_border[i+1:]
337
+ if len(second_part)<2:
338
+ filtered_border = first_part
339
+ previously_deleted.append([i,len(second_part)])
340
+ elif len(first_part)<2:
341
+ filtered_border = second_part
342
+ previously_deleted.append([i,len(first_part)])
343
+ else:
344
+ first_b, _, deleted_first = robust_rail_sides(first_part)
345
+ second_b, _, _ = robust_rail_sides(second_part)
346
+ filtered_border = np.concatenate((first_b,second_b), axis=0)
347
+
348
+ if deleted_first:
349
+ for deleted_item in deleted_first:
350
+ if deleted_item[0]<=i:
351
+ i -= deleted_item[1]
352
+
353
+ flags.append(i)
354
+ return filtered_border, flags, previously_deleted
355
+ else:
356
+ return border, [], []
357
+
358
+ def find_dist_from_edges(id_map, image, edges_dict, left_border, right_border, real_life_width_mm, real_life_target_mm, mark_value=30):
359
+ """
360
+ Mark regions representing a real-life distance (e.g., 2 meters) to the left and right from the furthest edges.
361
+
362
+ Parameters:
363
+ - arr: 2D NumPy array representing the id_map.
364
+ - edges_dict: Dictionary with y-levels as keys and lists of (start, end) tuples for edges.
365
+ - real_life_width_mm: The real-world width in millimeters that the average sequence width represents.
366
+ - real_life_target_mm: The real-world distance in millimeters to mark from the edges.
367
+
368
+ Returns:
369
+ - A NumPy array with the marked regions.
370
+ """
371
+ # Calculate the rail widths
372
+ diffs_widths = {k: sum(e-s for s, e in v) / len(v) for k, v in edges_dict.items() if v}
373
+ diffs_width = {k: max(e-s for s, e in v) for k, v in edges_dict.items() if v}
374
+
375
+ # Pixel to mm scale factor
376
+ scale_factors = {k: real_life_width_mm / v for k, v in diffs_width.items()}
377
+ # Converting the real-life target distance to pixels
378
+ target_distances_px = {k: int(real_life_target_mm / v) for k, v in scale_factors.items()}
379
+
380
+ # Mark the regions representing the target distance to the left and right from the furthest edges
381
+ end_points_left = {}
382
+ region_levels_left = []
383
+ for point in left_border:
384
+ min_edge = point[0]
385
+
386
+ # Ensure we stay within the image bounds
387
+ #left_mark_start = max(0, min_edge - int(target_distances_px[point[1]]))
388
+ left_mark_start = min_edge - int(target_distances_px[point[1]])
389
+ end_points_left[point[1]] = left_mark_start
390
+
391
+ # Left region points
392
+ if left_mark_start < min_edge:
393
+ y_values = np.arange(left_mark_start, min_edge)
394
+ x_values = np.full_like(y_values, point[1])
395
+ region_line = np.column_stack((x_values, y_values))
396
+ region_levels_left.append(region_line)
397
+
398
+ end_points_right = {}
399
+ region_levels_right = []
400
+ for point in right_border:
401
+ max_edge = point[0]
402
+
403
+ # Ensure we stay within the image bounds
404
+ right_mark_end = min(id_map.shape[1], max_edge + int(target_distances_px[point[1]]))
405
+ if right_mark_end != id_map.shape[1]:
406
+ end_points_right[point[1]] = right_mark_end
407
+
408
+ # Right region points
409
+ if max_edge < right_mark_end:
410
+ y_values = np.arange(max_edge, right_mark_end)
411
+ x_values = np.full_like(y_values, point[1])
412
+ region_line = np.column_stack((x_values, y_values))
413
+ region_levels_right.append(region_line)
414
+
415
+ return id_map, end_points_left, end_points_right, region_levels_left, region_levels_right
416
+
417
+ def bresenham_line(x0, y0, x1, y1):
418
+ """
419
+ Generate the coordinates of a line from (x0, y0) to (x1, y1) using Bresenham's algorithm.
420
+ """
421
+ line = []
422
+ dx = abs(x1 - x0)
423
+ dy = -abs(y1 - y0)
424
+ sx = 1 if x0 < x1 else -1
425
+ sy = 1 if y0 < y1 else -1
426
+ err = dx + dy # error value e_xy
427
+
428
+ while True:
429
+ line.append((x0, y0)) # Add the current point to the line
430
+ if x0 == x1 and y0 == y1:
431
+ break
432
+ e2 = 2 * err
433
+ if e2 >= dy: # e_xy+e_x > 0
434
+ err += dy
435
+ x0 += sx
436
+ if e2 <= dx: # e_xy+e_y < 0
437
+ err += dx
438
+ y0 += sy
439
+
440
+ return line
441
+
442
+ def interpolate_end_points(end_points_dict, flags):
443
+ line_arr = []
444
+ ys = list(end_points_dict.keys())
445
+ xs = list(end_points_dict.values())
446
+
447
+ if flags and len(flags) == 1:
448
+ pass
449
+ elif flags and np.all(np.diff(flags) == 1):
450
+ flags = [flags[0]]
451
+
452
+ for i in range(0, len(ys) - 1):
453
+ if i in flags:
454
+ continue
455
+ y1, y2 = ys[i], ys[i + 1]
456
+ x1, x2 = xs[i], xs[i + 1]
457
+ line = np.array(bresenham_line(x1, y1, x2, y2))
458
+ if np.any(line[:, 0] < 0):
459
+ line = line[line[:, 0] > 0]
460
+ line_arr = line_arr + list(line)
461
+
462
+ return line_arr
463
+
464
+ def extrapolate_line(pixels, image, min_y=None, extr_pixels=10):
465
+ """
466
+ Extrapolate a line based on the last segment using linear regression.
467
+
468
+ Parameters:
469
+ - pixels: List of (x, y) tuples representing line pixel coordinates.
470
+ - image: 2D numpy array representing the image.
471
+ - min_y: Minimum y-value to extrapolate to (optional).
472
+
473
+ Returns:
474
+ - A list of new extrapolated (x, y) pixel coordinates.
475
+ """
476
+ if len(pixels) < extr_pixels:
477
+ print("Not enough pixels to perform extrapolation.")
478
+ return []
479
+
480
+ recent_pixels = np.array(pixels[-extr_pixels:])
481
+
482
+ X = recent_pixels[:, 0].reshape(-1, 1) # Reshape for sklearn
483
+ y = recent_pixels[:, 1]
484
+
485
+ model = LinearRegression()
486
+ model.fit(X, y)
487
+
488
+ slope = model.coef_[0]
489
+ intercept = model.intercept_
490
+
491
+ extrapolate = lambda x: slope * x + intercept
492
+
493
+ # Calculate direction based on last two pixels
494
+ dx, dy = 0, 0 # Default values
495
+
496
+ x_diffs = []
497
+ y_diffs = []
498
+ for i in range(1,extr_pixels-1):
499
+ x_diffs.append(pixels[-i][0] - pixels[-(i+1)][0])
500
+ y_diffs.append(pixels[-i][1] - pixels[-(i+1)][1])
501
+
502
+ x_diff = x_diffs[np.argmax(np.abs(x_diffs))]
503
+ y_diff = y_diffs[np.argmax(np.abs(y_diffs))]
504
+
505
+ if abs(int(x_diff)) >= abs(int(y_diff)):
506
+ dx = 1 if x_diff >= 0 else -1
507
+ else:
508
+ dy = 1 if y_diff >= 0 else -1
509
+
510
+ last_pixel = pixels[-1]
511
+ new_pixels = []
512
+ x, y = last_pixel
513
+
514
+ min_y = min_y if min_y is not None else image.shape[0] - 1
515
+
516
+ while 0 <= x < image.shape[1] and min_y <= y < image.shape[0]:
517
+ if dx != 0: # Horizontal or diagonal movement
518
+ x += dx
519
+ y = int(extrapolate(x))
520
+ elif dy != 0: # Vertical movement
521
+ y += dy
522
+ # For vertical lines, approximate x based on the last known value
523
+ x = int(x)
524
+
525
+ if 0 <= y < image.shape[0] and 0 <= x < image.shape[1]:
526
+ new_pixels.append((x, y))
527
+ else:
528
+ break
529
+
530
+ return new_pixels
531
+
532
+ def extrapolate_borders(dist_marked_id_map, border_l, border_r, lowest_y):
533
+
534
+ #border_extrapolation_l1 = extrapolate_line(border_l, dist_marked_id_map, lowest_y)
535
+ border_extrapolation_l2 = extrapolate_line(border_l[::-1], dist_marked_id_map, lowest_y)
536
+
537
+ #border_extrapolation_r1 = extrapolate_line(border_r, dist_marked_id_map, lowest_y)
538
+ border_extrapolation_r2 = extrapolate_line(border_r[::-1], dist_marked_id_map, lowest_y)
539
+
540
+ #border_l = border_extrapolation_l2[::-1] + border_l + border_extrapolation_l1
541
+ #border_r = border_extrapolation_r2[::-1] + border_r + border_extrapolation_r1
542
+
543
+ border_l = border_extrapolation_l2[::-1] + border_l
544
+ border_r = border_extrapolation_r2[::-1] + border_r
545
+
546
+ return border_l, border_r
547
+
548
+ def find_zone_border(id_map, image, edges, irl_width_mm=1435, irl_target_mm=1000, lowest_y = 0):
549
+
550
+ left_border, right_border, flags_l, flags_r = find_rail_sides(id_map, edges)
551
+
552
+ dist_marked_id_map, end_points_left, end_points_right, left_region, right_region = find_dist_from_edges(id_map, image, edges, left_border, right_border, irl_width_mm, irl_target_mm)
553
+
554
+ border_l = interpolate_end_points(end_points_left, flags_l)
555
+ border_r = interpolate_end_points(end_points_right, flags_r)
556
+
557
+ border_l, border_r = extrapolate_borders(dist_marked_id_map, border_l, border_r, lowest_y)
558
+
559
+ return [border_l, border_r],[left_region, right_region]
560
+
561
+ def get_clues(segmentation_mask, number_of_clues):
562
+
563
+ lowest, highest = find_extreme_y_values(segmentation_mask)
564
+ if lowest is not None and highest is not None:
565
+ clue_step = int((highest - lowest) / number_of_clues+1)
566
+ clues = []
567
+ for i in range(number_of_clues):
568
+ clues.append(highest - (i*clue_step))
569
+ clues.append(lowest+int(0.5*clue_step))
570
+
571
+ return clues
572
+ else:
573
+ return []
574
+
575
+ def border_handler(id_map, image, edges, target_distances):
576
+
577
+ lowest, _ = find_extreme_y_values(id_map)
578
+ borders = []
579
+ regions = []
580
+ for target in target_distances:
581
+ borders_regions = find_zone_border(id_map, image, edges, irl_target_mm=target, lowest_y = lowest)
582
+ borders.append(borders_regions[0])
583
+ regions.append(borders_regions[1])
584
+
585
+ return borders, id_map, regions
586
+
587
+ def segment(input_image, model_seg, image_size):
588
+ image_norm, image = get_segformer_img(input_image, image_size)
589
+
590
+ outputs = model_seg(image_norm)
591
+
592
+ logits = outputs.logits
593
+ upsampled_logits = nn.functional.interpolate(
594
+ logits,
595
+ size=image_norm.shape[-2:],
596
+ mode="bilinear",
597
+ align_corners=False
598
+ )
599
+
600
+ output = upsampled_logits.float()
601
+
602
+ confidence_scores = F.softmax(output, dim=1).cpu().detach().numpy().squeeze()
603
+ id_map = np.argmax(confidence_scores, axis=0).astype(np.uint8)
604
+ id_map = image_morpho(id_map)
605
+
606
+ id_map = cv2.resize(id_map, [1920,1080], interpolation=cv2.INTER_NEAREST)
607
+ return id_map, image
608
+
609
+ def detect(model_det, image):
610
+
611
+ results = model_det.predict(image)
612
+
613
+ return results, model_det, image
614
+
615
+ def manage_detections(results, model):
616
+ bbox = results[0].boxes.xywh.tolist()
617
+ cls = results[0].boxes.cls.tolist()
618
+ accepted_stationary = np.array([24,25,28,36])
619
+ accepted_moving = np.array([0,1,2,3,7,15,16,17,18,19])
620
+ boxes_moving = {}
621
+ boxes_stationary = {}
622
+ if len(bbox) > 0:
623
+ for xywh, clss in zip(bbox, cls):
624
+ if clss in accepted_moving:
625
+ if clss in boxes_moving.keys() and len(boxes_moving[clss]) > 0:
626
+ boxes_moving[clss].append(xywh)
627
+ else:
628
+ boxes_moving[clss] = [xywh]
629
+ if clss in accepted_stationary:
630
+ if clss in boxes_stationary.keys() and len(boxes_stationary[clss]) > 0:
631
+ boxes_stationary[clss].append(xywh)
632
+ else:
633
+ boxes_stationary[clss] = [xywh]
634
+
635
+ return boxes_moving, boxes_stationary
636
+
637
+ def compute_detection_borders(borders, output_dims=[1080,1920]):
638
+ det_height = output_dims[0]-1
639
+ det_width = output_dims[1]-1
640
+
641
+ for i,border in enumerate(borders):
642
+ border_l = np.array(border[0])
643
+
644
+ if list(border_l):
645
+ pass
646
+ else:
647
+ border_l=np.array([[0,0],[0,0]])
648
+
649
+ endpoints_l = [border_l[0],border_l[-1]]
650
+
651
+ border_r = np.array(border[1])
652
+ if list(border_r):
653
+ pass
654
+ else:
655
+ border_r=np.array([[0,0],[0,0]])
656
+
657
+ endpoints_r = [border_r[0],border_r[-1]]
658
+
659
+ if np.array_equal(np.array([[0,0],[0,0]]), endpoints_l):
660
+ endpoints_l = [[0,endpoints_r[0][1]],[0,endpoints_r[1][1]]]
661
+
662
+ if np.array_equal(np.array([[0,0],[0,0]]), endpoints_r):
663
+ endpoints_r = [[det_width,endpoints_l[0][1]],[det_width,endpoints_l[1][1]]]
664
+
665
+ interpolated_top = bresenham_line(endpoints_l[1][0],endpoints_l[1][1],endpoints_r[1][0],endpoints_r[1][1])
666
+
667
+ zero_range = [0,1,2,3]
668
+ height_range = [det_height,det_height-1,det_height-2,det_height-3]
669
+ width_range = [det_width,det_width-1,det_width-2,det_width-3]
670
+
671
+ if (endpoints_l[0][0] in zero_range and endpoints_r[0][1] in height_range):
672
+ y_values = np.arange(endpoints_l[0][1], det_height)
673
+ x_values = np.full_like(y_values, 0)
674
+ bottom1 = np.column_stack((x_values, y_values))
675
+
676
+ x_values = np.arange(0, endpoints_r[0][0])
677
+ y_values = np.full_like(x_values, det_height)
678
+ bottom2 = np.column_stack((x_values, y_values))
679
+
680
+ interpolated_bottom = np.vstack((bottom1, bottom2))
681
+
682
+ elif (endpoints_l[0][1] in height_range and endpoints_r[0][0] in width_range):
683
+ y_values = np.arange(endpoints_r[0][1], det_height)
684
+ x_values = np.full_like(y_values, det_width)
685
+ bottom1 = np.column_stack((x_values, y_values))
686
+
687
+ x_values = np.arange(endpoints_l[0][0], det_width)
688
+ y_values = np.full_like(x_values, det_height)
689
+ bottom2 = np.column_stack((x_values, y_values))
690
+
691
+ interpolated_bottom = np.vstack((bottom1, bottom2))
692
+
693
+ elif endpoints_l[0][0] in zero_range and endpoints_r[0][0] in width_range:
694
+ y_values = np.arange(endpoints_l[0][1], det_height)
695
+ x_values = np.full_like(y_values, 0)
696
+ bottom1 = np.column_stack((x_values, y_values))
697
+
698
+ y_values = np.arange(endpoints_r[0][1], det_height)
699
+ x_values = np.full_like(y_values, det_width)
700
+ bottom2 = np.column_stack((x_values, y_values))
701
+
702
+ bottom3_mid = bresenham_line(bottom1[-1][0],bottom1[-1][1],bottom2[-1][0],bottom2[-1][1])
703
+
704
+ interpolated_bottom = np.vstack((bottom1, bottom2, bottom3_mid))
705
+
706
+
707
+ else:
708
+ interpolated_bottom = bresenham_line(endpoints_l[0][0],endpoints_l[0][1],endpoints_r[0][0],endpoints_r[0][1])
709
+
710
+ borders[i].append(interpolated_bottom)
711
+ borders[i].append(interpolated_top)
712
+
713
+ return borders
714
+
715
+ def get_bounding_box_points(cx, cy, w, h):
716
+ top_left = (cx - w / 2, cy - h / 2)
717
+ top_right = (cx + w / 2, cy - h / 2)
718
+ bottom_right = (cx + w / 2, cy + h / 2)
719
+ bottom_left = (cx - w / 2, cy + h / 2)
720
+
721
+ corners = [top_left, top_right, bottom_right, bottom_left]
722
+
723
+ def interpolate(point1, point2, fraction):
724
+ """Interpolate between two points at a given fraction of the distance."""
725
+ return (point1[0] + fraction * (point2[0] - point1[0]),
726
+ point1[1] + fraction * (point2[1] - point1[1]))
727
+
728
+ points = []
729
+ for i in range(4):
730
+ next_i = (i + 1) % 4
731
+ points.append(corners[i])
732
+ points.append(interpolate(corners[i], corners[next_i], 1 / 3))
733
+ points.append(interpolate(corners[i], corners[next_i], 2 / 3))
734
+
735
+ return points
736
+
737
+ def classify_detections(boxes_moving, boxes_stationary, borders, img_dims, output_dims=[1080,1920]):
738
+ img_h, img_w, _ = img_dims
739
+ img_h_scaletofullHD = output_dims[1]/img_w
740
+ img_w_scaletofullHD = output_dims[0]/img_h
741
+ colors = ["yellow","orange","red","green","blue"]
742
+
743
+ borders = compute_detection_borders(borders,output_dims)
744
+
745
+ boxes_info = []
746
+
747
+ if boxes_moving or boxes_stationary:
748
+ if boxes_moving:
749
+ for item, coords in boxes_moving.items():
750
+ for coord in coords:
751
+ x = coord[0]*img_w_scaletofullHD
752
+ y = coord[1]*img_h_scaletofullHD
753
+ w = coord[2]*img_w_scaletofullHD
754
+ h = coord[3]*img_h_scaletofullHD
755
+
756
+ points_to_test = get_bounding_box_points(x, y, w, h)
757
+
758
+ complete_border = []
759
+ criticality = -1
760
+ color = None
761
+ for i,border in enumerate(reversed(borders)):
762
+ border_nonempty = [np.array(arr) for arr in border if np.array(arr).size > 0]
763
+ complete_border = np.vstack((border_nonempty))
764
+ instance_border_path = mplPath.Path(np.array(complete_border))
765
+
766
+ is_inside_borders = False
767
+ for point in points_to_test:
768
+ is_inside = instance_border_path.contains_point(point)
769
+ if is_inside:
770
+ is_inside_borders = True
771
+
772
+ if is_inside_borders:
773
+ criticality = i
774
+ color = colors[i]
775
+
776
+ if criticality == -1:
777
+ color = colors[3]
778
+
779
+ boxes_info.append([item, criticality, color, [x, y], [w, h], 1])
780
+
781
+ if boxes_stationary:
782
+ for item, coords in boxes_stationary.items():
783
+ for coord in coords:
784
+ x = coord[0]*img_w_scaletofullHD
785
+ y = coord[1]*img_h_scaletofullHD
786
+ w = coord[2]*img_w_scaletofullHD
787
+ h = coord[3]*img_h_scaletofullHD
788
+
789
+ points_to_test = get_bounding_box_points(x, y, w, h)
790
+
791
+ complete_border = []
792
+ criticality = -1
793
+ color = None
794
+ is_inside_borders = 0
795
+ for i,border in enumerate(reversed(borders), start=len(borders) - 1):
796
+ border_nonempty = [np.array(arr) for arr in border if np.array(arr).size > 0]
797
+ complete_border = np.vstack(border_nonempty)
798
+ instance_border_path = mplPath.Path(np.array(complete_border))
799
+
800
+ is_inside_borders = False
801
+ for point in points_to_test:
802
+ is_inside = instance_border_path.contains_point(point)
803
+ if is_inside:
804
+ is_inside_borders = True
805
+
806
+ if is_inside_borders:
807
+ criticality = i
808
+ color = colors[4]
809
+
810
+ if criticality == -1:
811
+ color = colors[3]
812
+
813
+ boxes_info.append([item, criticality, color, [x, y], [w, h], 0])
814
+
815
+ return boxes_info
816
+
817
+ else:
818
+ print("No accepted detections in this image.")
819
+ return []
820
+
821
+ def draw_classification(classification, id_map):
822
+ if classification:
823
+ for box in classification:
824
+ x,y = box[3]
825
+ mark_value = 30
826
+
827
+ x_start = int(max(x - 2, 0))
828
+ x_end = int(min(x + 3, id_map.shape[1]))
829
+ y_start = int(max(y - 2, 0))
830
+ y_end = int(min(y + 3, id_map.shape[0]))
831
+
832
+ id_map[y_start:y_end, x_start:x_end] = mark_value
833
+ else:
834
+ return
835
+
836
+ def get_result(classification, id_map, names, borders, image, regions):
837
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
838
+ image = cv2.resize(image, (id_map.shape[1], id_map.shape[0]), interpolation = cv2.INTER_LINEAR)
839
+ fig = plt.figure(figsize=(16, 9), dpi=100)
840
+ plt.imshow(image, cmap='gray')
841
+
842
+ if classification:
843
+ for box in classification:
844
+
845
+ boxes = True
846
+ cx,cy = box[3]
847
+ name = names[box[0]]
848
+ if boxes:
849
+ w,h = box[4]
850
+ x = cx - w / 2
851
+ y = cy - h / 2
852
+ rect = patches.Rectangle((x, y), w, h, linewidth=2, edgecolor=box[2], facecolor='none')
853
+
854
+ ax = plt.gca()
855
+ ax.add_patch(rect)
856
+ plt.text(x, y-17, name, color='black', fontsize=10, ha='center', va='center', fontweight='bold', bbox=dict(facecolor=box[2], edgecolor='none', alpha=1))
857
+ else:
858
+ plt.imshow(id_map, cmap='gray')
859
+ plt.text(cx, cy+10, name, color=box[2], fontsize=10, ha='center', va='center', fontweight='bold')
860
+
861
+ for region in regions:
862
+ for side in region:
863
+ for line in side:
864
+ line = np.array(line)
865
+ plt.plot(line[:,1], line[:,0] ,'-', color='lightgrey', marker=None, linewidth=0.5)
866
+ plt.ylim(0, 1080)
867
+ plt.xlim(0, 1920)
868
+ plt.gca().invert_yaxis()
869
+
870
+ colors = ['yellow','orange','red']
871
+ borders.reverse()
872
+ for i,border in enumerate(borders):
873
+ for side in border:
874
+ side = np.array(side)
875
+ if side.size > 0:
876
+ plt.plot(side[:,0],side[:,1] ,'-', color=colors[i], marker=None, linewidth=0.6) #color=colors[i]
877
+ plt.ylim(0, 1080)
878
+ plt.xlim(0, 1920)
879
+ plt.gca().invert_yaxis()
880
+
881
+ #plt.show()
882
+ canvas = FigureCanvas(fig)
883
+ canvas.draw()
884
+ width, height = fig.get_size_inches() * fig.get_dpi()
885
+ image = np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3)
886
+
887
+ plt.close(fig) # Close the figure to free memory
888
+
889
+ return image
890
+
891
+ def run(input_image, model_seg, model_det, image_size, target_distances, num_ys = 10):
892
+
893
+ segmentation_mask, image = segment(input_image, model_seg, image_size)
894
+
895
+ # Border search
896
+ clues = get_clues(segmentation_mask, num_ys)
897
+ edges = find_edges(segmentation_mask, clues, min_width=0)
898
+ borders, id_map, regions = border_handler(segmentation_mask, image, edges, target_distances)
899
+
900
+ # Detection
901
+ results, model, image = detect(model_det, input_image)
902
+ boxes_moving, boxes_stationary = manage_detections(results, model)
903
+
904
+ classification = classify_detections(boxes_moving, boxes_stationary, borders, image.shape, output_dims=segmentation_mask.shape)
905
+
906
+ output_image = get_result(classification, id_map, model.names, borders, image, regions)
907
+
908
+ return output_image
909
+
910
+ if __name__ == "__main__":
911
+
912
+ image_size = [1024,1024]
913
+ target_distances = [650,1000,2000]
914
+ num_ys = 10
915
+
916
+ PATH_model_seg = 'SegFormer_B3_1024_finetuned.pth'
917
+ PATH_model_det = 'yolov8s.pt'
918
+ input_image = cv2.imread('rs00006.jpg') #TO CO VLOZI UZIVATEL
919
+ model_seg = load_segformer(PATH_model_seg)
920
+ model_det = load_yolo(PATH_model_det)
921
+ image = run(input_image, model_seg, model_det, image_size, target_distances, num_ys=num_ys)
922
+
app.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from TheDistanceAssessor import run, load_segformer, load_yolo
3
+
4
+ def process_image(input_image):
5
+ image_size = [1024,1024]
6
+ target_distances = [650,1000,2000]
7
+ num_ys = 10
8
+
9
+ PATH_model_seg = 'SegFormer_B3_1024_finetuned.pth'
10
+ PATH_model_det = 'yolov8s.pt'
11
+ model_seg = load_segformer(PATH_model_seg)
12
+ model_det = load_yolo(PATH_model_det)
13
+ output_image = run(input_image, model_seg, model_det, image_size, target_distances, num_ys = num_ys)
14
+ return output_image
15
+
16
+ # Create the Gradio interface
17
+ iface = gr.Interface(
18
+ fn=process_image, # The function to be called
19
+ inputs=gr.Image(type="pil"), # Input type
20
+ outputs=gr.Image(type="numpy"), # Output type
21
+ title="Image Processor", # Title of the interface
22
+ description="Upload an image and get a processed image as output." # Description of the interface
23
+ )
24
+
25
+ # Launch the interface
26
+ if __name__ == "__main__":
27
+ iface.launch()