nishanth-saka commited on
Commit
1d74031
·
verified ·
1 Parent(s): 049d3fd

Auto lane detection (#12)

Browse files

- Auto lane detection (813043571749baabd651b27e6c2c8834fd75bccf)

Files changed (1) hide show
  1. app.py +45 -29
app.py CHANGED
@@ -2,6 +2,7 @@ import gradio as gr
2
  import numpy as np, cv2, json, tempfile, os
3
  from sklearn.cluster import KMeans
4
 
 
5
  # ============================================================
6
  # 🧩 1. Compute motion vectors from trajectory JSON
7
  # ============================================================
@@ -13,54 +14,60 @@ def extract_motion_vectors(data):
13
  continue
14
  diffs = np.diff(pts, axis=0)
15
  for d in diffs:
16
- if np.linalg.norm(d) > 1: # ignore jitter / static points
17
  vectors.append(d)
18
  return np.array(vectors)
19
 
20
 
21
  # ============================================================
22
- # 🧮 2. Auto-Adaptive Dominant Flow Clustering (Cosine-based)
23
  # ============================================================
24
  def learn_flows_auto(vectors, normalize=True, max_clusters=2):
25
  """
26
- Automatically determines whether there's 1 or 2 dominant flows:
27
- - Computes angular spread of all motion vectors.
28
- - If spread < 15°, uses 1 cluster (single-lane one-way).
29
- - Otherwise, uses 2 clusters (two-way or opposing flows).
30
  """
31
  if len(vectors) < 3:
32
- return None, None
33
 
34
- # (1) Normalize to unit direction
35
  norms = np.linalg.norm(vectors, axis=1, keepdims=True)
36
  dirs = vectors / (norms + 1e-6)
37
  valid = (norms[:, 0] > 1.5)
38
  dirs = dirs[valid]
39
  if len(dirs) < 3:
40
- return None, None
41
 
42
- # (2) Compute angular spread in degrees
43
  angles = np.degrees(np.arctan2(dirs[:, 1], dirs[:, 0]))
44
  angles = (angles + 360) % 360
45
- spread = np.ptp(angles) # peak-to-peak range
46
  n_clusters = 1 if spread < 15 else max_clusters
47
 
48
- # (3) Run KMeans with chosen cluster count
49
  kmeans = KMeans(n_clusters=n_clusters, n_init=20, random_state=42)
50
  kmeans.fit(dirs)
 
51
  centers = kmeans.cluster_centers_
52
 
 
53
  centers = centers / (np.linalg.norm(centers, axis=1, keepdims=True) + 1e-6)
54
- sims = np.dot(dirs, centers.T)
55
- labels = np.argmax(sims, axis=1)
56
 
57
- return labels, centers
 
 
 
 
 
 
 
 
58
 
59
 
60
  # ============================================================
61
- # 🎨 3. Visualization Utility (Scaled-up Arrows)
62
  # ============================================================
63
- def draw_flow_overlay(vectors, labels, centers, bg_img=None):
64
  if bg_img and os.path.exists(bg_img):
65
  bg = cv2.imread(bg_img)
66
  if bg is None:
@@ -71,9 +78,11 @@ def draw_flow_overlay(vectors, labels, centers, bg_img=None):
71
  overlay = bg.copy()
72
  colors = [(0, 0, 255), (255, 255, 0), (255, 0, 255)]
73
 
 
74
  norms = np.linalg.norm(vectors, axis=1, keepdims=True)
75
  vectors = np.divide(vectors, norms + 1e-6) * 10
76
 
 
77
  for i, ((vx, vy), lab) in enumerate(zip(vectors, labels)):
78
  if i % 15 != 0:
79
  continue
@@ -82,20 +91,27 @@ def draw_flow_overlay(vectors, labels, centers, bg_img=None):
82
  end = (int(start[0] + vx), int(start[1] + vy))
83
  cv2.arrowedLine(overlay, start, end, colors[lab % len(colors)], 1, tipLength=0.3)
84
 
85
- # --- main dominant arrows ---
86
  h, w = overlay.shape[:2]
87
  scale = 300
88
  center_pt = (w // 2, h // 2)
89
 
90
- for i, c in enumerate(centers):
91
  c = c / (np.linalg.norm(c) + 1e-6)
92
  end = (int(center_pt[0] + c[0] * scale),
93
  int(center_pt[1] + c[1] * scale))
94
  offset = (i - 0.5) * 40
95
  start = (center_pt[0], int(center_pt[1] + offset))
96
  cv2.arrowedLine(overlay, start, end, (0, 255, 0), 4, tipLength=0.4)
97
- cv2.putText(overlay, f"Flow {i+1}", (end[0] + 10, end[1]),
98
- cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
 
 
 
 
 
 
 
99
 
100
  combined = cv2.addWeighted(bg, 0.6, overlay, 0.4, 0)
101
  out_path = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False).name
@@ -116,17 +132,18 @@ def process_json(json_file, background=None):
116
  if len(vectors) == 0:
117
  return None, {"error": "No motion vectors found."}
118
 
119
- labels, centers = learn_flows_auto(vectors)
120
  if labels is None:
121
  return None, {"error": "Insufficient data for clustering."}
122
 
123
  centers = centers / (np.linalg.norm(centers, axis=1, keepdims=True) + 1e-6)
124
- img_path = draw_flow_overlay(vectors, labels, centers, background)
125
 
126
  stats = {
127
  "num_vectors": int(len(vectors)),
128
  "dominant_flows": int(len(centers)),
129
- "flow_centers": centers.tolist()
 
130
  }
131
  return img_path, stats
132
 
@@ -135,10 +152,9 @@ def process_json(json_file, background=None):
135
  # 🖥️ 5. Gradio Interface
136
  # ============================================================
137
  description_text = """
138
- ### 🧭 Dominant Flow Learning (Stage 2 Auto-Adaptive)
139
- Automatically decides if traffic flow is **one-way** or **two-way**
140
- based on motion-vector angular spread.
141
- Upload the **Stage 1 trajectories JSON**, and optionally a background frame.
142
  """
143
 
144
  example_json = "trajectories_sample.json" if os.path.exists("trajectories_sample.json") else None
@@ -154,7 +170,7 @@ demo = gr.Interface(
154
  gr.Image(label="Dominant Flow Overlay"),
155
  gr.JSON(label="Flow Stats")
156
  ],
157
- title="🚗 Dominant Flow Learning – Stage 2 (Auto-Adaptive)",
158
  description=description_text,
159
  examples=[[example_json, example_bg]] if example_json else None,
160
  )
 
2
  import numpy as np, cv2, json, tempfile, os
3
  from sklearn.cluster import KMeans
4
 
5
+
6
  # ============================================================
7
  # 🧩 1. Compute motion vectors from trajectory JSON
8
  # ============================================================
 
14
  continue
15
  diffs = np.diff(pts, axis=0)
16
  for d in diffs:
17
+ if np.linalg.norm(d) > 1: # ignore jitter / static
18
  vectors.append(d)
19
  return np.array(vectors)
20
 
21
 
22
  # ============================================================
23
+ # 🧮 2. Auto-Adaptive Dominant Flow Clustering (with dominance sort)
24
  # ============================================================
25
  def learn_flows_auto(vectors, normalize=True, max_clusters=2):
26
  """
27
+ Automatically chooses 1 or 2 flow clusters depending on angular spread.
28
+ Also returns cluster membership counts to rank dominance.
 
 
29
  """
30
  if len(vectors) < 3:
31
+ return None, None, None
32
 
33
+ # Normalize
34
  norms = np.linalg.norm(vectors, axis=1, keepdims=True)
35
  dirs = vectors / (norms + 1e-6)
36
  valid = (norms[:, 0] > 1.5)
37
  dirs = dirs[valid]
38
  if len(dirs) < 3:
39
+ return None, None, None
40
 
41
+ # Angular spread check
42
  angles = np.degrees(np.arctan2(dirs[:, 1], dirs[:, 0]))
43
  angles = (angles + 360) % 360
44
+ spread = np.ptp(angles)
45
  n_clusters = 1 if spread < 15 else max_clusters
46
 
47
+ # Cluster
48
  kmeans = KMeans(n_clusters=n_clusters, n_init=20, random_state=42)
49
  kmeans.fit(dirs)
50
+ labels = kmeans.labels_
51
  centers = kmeans.cluster_centers_
52
 
53
+ # Normalize centers again
54
  centers = centers / (np.linalg.norm(centers, axis=1, keepdims=True) + 1e-6)
 
 
55
 
56
+ # --- Dominance sorting by cluster size ---
57
+ counts = np.bincount(labels)
58
+ order = np.argsort(-counts) # descending
59
+ centers = centers[order]
60
+ # remap labels to new dominance order
61
+ remap = {old: new for new, old in enumerate(order)}
62
+ labels = np.array([remap[l] for l in labels])
63
+
64
+ return labels, centers, counts[order]
65
 
66
 
67
  # ============================================================
68
+ # 🎨 3. Visualization Utility
69
  # ============================================================
70
+ def draw_flow_overlay(vectors, labels, centers, counts, bg_img=None):
71
  if bg_img and os.path.exists(bg_img):
72
  bg = cv2.imread(bg_img)
73
  if bg is None:
 
78
  overlay = bg.copy()
79
  colors = [(0, 0, 255), (255, 255, 0), (255, 0, 255)]
80
 
81
+ # normalize arrow lengths
82
  norms = np.linalg.norm(vectors, axis=1, keepdims=True)
83
  vectors = np.divide(vectors, norms + 1e-6) * 10
84
 
85
+ # small random field arrows
86
  for i, ((vx, vy), lab) in enumerate(zip(vectors, labels)):
87
  if i % 15 != 0:
88
  continue
 
91
  end = (int(start[0] + vx), int(start[1] + vy))
92
  cv2.arrowedLine(overlay, start, end, colors[lab % len(colors)], 1, tipLength=0.3)
93
 
94
+ # --- Main dominant arrows ---
95
  h, w = overlay.shape[:2]
96
  scale = 300
97
  center_pt = (w // 2, h // 2)
98
 
99
+ for i, (c, count) in enumerate(zip(centers, counts)):
100
  c = c / (np.linalg.norm(c) + 1e-6)
101
  end = (int(center_pt[0] + c[0] * scale),
102
  int(center_pt[1] + c[1] * scale))
103
  offset = (i - 0.5) * 40
104
  start = (center_pt[0], int(center_pt[1] + offset))
105
  cv2.arrowedLine(overlay, start, end, (0, 255, 0), 4, tipLength=0.4)
106
+ cv2.putText(
107
+ overlay,
108
+ f"Flow {i+1} ({count} vecs)",
109
+ (end[0] + 10, end[1]),
110
+ cv2.FONT_HERSHEY_SIMPLEX,
111
+ 0.7,
112
+ (0, 255, 0),
113
+ 2,
114
+ )
115
 
116
  combined = cv2.addWeighted(bg, 0.6, overlay, 0.4, 0)
117
  out_path = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False).name
 
132
  if len(vectors) == 0:
133
  return None, {"error": "No motion vectors found."}
134
 
135
+ labels, centers, counts = learn_flows_auto(vectors)
136
  if labels is None:
137
  return None, {"error": "Insufficient data for clustering."}
138
 
139
  centers = centers / (np.linalg.norm(centers, axis=1, keepdims=True) + 1e-6)
140
+ img_path = draw_flow_overlay(vectors, labels, centers, counts, background)
141
 
142
  stats = {
143
  "num_vectors": int(len(vectors)),
144
  "dominant_flows": int(len(centers)),
145
+ "flow_counts": counts.tolist(),
146
+ "flow_centers": centers.tolist(),
147
  }
148
  return img_path, stats
149
 
 
152
  # 🖥️ 5. Gradio Interface
153
  # ============================================================
154
  description_text = """
155
+ ### 🧭 Dominant Flow Learning (Stage 2 Auto + Dominance)
156
+ Automatically detects if traffic is **one-way** or **two-way**
157
+ and orders flows by **vehicle count** so Flow 1 is the true dominant direction.
 
158
  """
159
 
160
  example_json = "trajectories_sample.json" if os.path.exists("trajectories_sample.json") else None
 
170
  gr.Image(label="Dominant Flow Overlay"),
171
  gr.JSON(label="Flow Stats")
172
  ],
173
+ title="🚗 Dominant Flow Learning – Stage 2 (Auto + Dominance)",
174
  description=description_text,
175
  examples=[[example_json, example_bg]] if example_json else None,
176
  )