Jaya242 commited on
Commit
495ac9b
Β·
1 Parent(s): d5f1bb1

initial deploy: traffic analytics gradio app

Browse files
Files changed (4) hide show
  1. README.md +1 -1
  2. app.py +33 -0
  3. requirements.txt +3 -0
  4. src/detector.py +186 -0
README.md CHANGED
@@ -5,7 +5,7 @@ colorFrom: red
5
  colorTo: blue
6
  sdk: gradio
7
  sdk_version: 6.19.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
  license: mit
 
5
  colorTo: blue
6
  sdk: gradio
7
  sdk_version: 6.19.0
8
+ python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
  license: mit
app.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from src.detector import process_video
3
+
4
+ def run(video_input):
5
+ if video_input is None:
6
+ return None, None, "⚠️ Please upload a video first."
7
+ return process_video(video_input)
8
+
9
+
10
+ demo = gr.Interface(
11
+ fn=run,
12
+ inputs=gr.Video(label="Upload a traffic video"),
13
+ outputs=[
14
+ gr.Video(label="Annotated Output (with tracking + counting lines)"),
15
+ gr.File(label="Crossings CSV"),
16
+ gr.Markdown(label="Summary"),
17
+ ],
18
+ title="🚦 Traffic Analytics Pipeline",
19
+ description=(
20
+ "Upload a traffic intersection video. Get back annotated output with "
21
+ "persistent vehicle tracking, dual-line crossing counter, and a CSV log "
22
+ "of every crossing event. Built with YOLOv8 + ByteTrack + OpenCV."
23
+ ),
24
+ article=(
25
+ "Code on [GitHub](https://github.com/Jaya242/traffic_detector). "
26
+ "Validated at 96.2% unique-vehicle accuracy on a 2,208-frame test clip."
27
+ ),
28
+ flagging_mode="never",
29
+ )
30
+
31
+
32
+ if __name__ == "__main__":
33
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=4.0
2
+ ultralytics>=8.0
3
+ opencv-python-headless>=4.8
src/detector.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import os
3
+ import csv
4
+ from ultralytics import YOLO
5
+ import tempfile
6
+ import re
7
+
8
+ model = YOLO('yolov8n.pt')
9
+
10
+ ALLOWED_CLASSES = {"car", "bus", "truck", "motorcycle"}
11
+ ALLOWED_CLASS_IDS = [2, 3, 5, 7] # COCO ids for car, motorcycle, bus, truck
12
+ CONF_THRESHOLD = 0.5
13
+
14
+ LINE_Y_RATIO = 0.7
15
+ LINE_X_RATIO = 0.5
16
+
17
+ MANUAL_UNIQUE = 26 # actual unique vehicles in the clip (camera angle: diagonal SE traffic)
18
+
19
+
20
+ def detect(frame):
21
+ results = model.track(frame, persist=True, verbose=False, classes=ALLOWED_CLASS_IDS)
22
+ detections = []
23
+ for box in results[0].boxes:
24
+ cls_id = int(box.cls[0])
25
+ conf = float(box.conf[0])
26
+ cls_name = model.names[cls_id]
27
+
28
+ if cls_name not in ALLOWED_CLASSES:
29
+ continue
30
+ if conf < CONF_THRESHOLD:
31
+ continue
32
+ x1, y1, x2, y2 = map(int, box.xyxy[0])
33
+
34
+ track_id = int(box.id[0]) if box.id is not None else None
35
+
36
+ detections.append({
37
+ "track_id": track_id,
38
+ "class": cls_name,
39
+ "confidence": round(conf, 2),
40
+ "bbox": [x1, y1, x2, y2],
41
+ })
42
+ annotated = results[0].plot()
43
+ return detections, annotated
44
+
45
+
46
+ def process_video(input_video_path):
47
+ output_dir = tempfile.mkdtemp(prefix="traffic_")
48
+ output_video_path = os.path.join(output_dir, "traffic_tracked.mp4")
49
+ output_csv_path = os.path.join(output_dir, "crossings.csv")
50
+
51
+ cap = cv2.VideoCapture(input_video_path)
52
+ if not cap.isOpened():
53
+ print("❌ Couldn't open video")
54
+ exit()
55
+
56
+ fps = cap.get(cv2.CAP_PROP_FPS)
57
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
58
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
59
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
60
+ writer = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))
61
+
62
+ LINE_Y = int(height * LINE_Y_RATIO)
63
+ LINE_X = int(width * LINE_X_RATIO)
64
+
65
+ previous_centres = {}
66
+ counted_horizontal = set()
67
+ counted_vertical = set()
68
+ crossings = []
69
+
70
+ frame_count = 0
71
+ while True:
72
+ ret, frame = cap.read()
73
+ if not ret:
74
+ break
75
+
76
+ detections, annotated = detect(frame)
77
+
78
+ cv2.line(annotated, (0, LINE_Y), (width, LINE_Y), (0, 255, 255), 2)
79
+ cv2.line(annotated, (LINE_X, 0), (LINE_X, height), (0, 255, 255), 2)
80
+
81
+ for det in detections:
82
+ track_id = det["track_id"]
83
+ if track_id is None:
84
+ continue
85
+ x1, y1, x2, y2 = det["bbox"]
86
+ cx = (x1 + x2) // 2
87
+ cy = (y1 + y2) // 2
88
+
89
+ prev = previous_centres.get(track_id)
90
+ if prev is not None:
91
+ prev_cx, prev_cy = prev
92
+
93
+ # Horizontal line β€” catches north/south
94
+ if track_id not in counted_horizontal:
95
+ if prev_cy < LINE_Y <= cy:
96
+ counted_horizontal.add(track_id)
97
+ crossings.append({
98
+ "track_id": track_id, "class": det["class"],
99
+ "line": "horizontal", "direction": "south",
100
+ "frame": frame_count,
101
+ "timestamp_sec": round(frame_count / fps, 2),
102
+ })
103
+ elif prev_cy > LINE_Y >= cy:
104
+ counted_horizontal.add(track_id)
105
+ crossings.append({
106
+ "track_id": track_id, "class": det["class"],
107
+ "line": "horizontal", "direction": "north",
108
+ "frame": frame_count,
109
+ "timestamp_sec": round(frame_count / fps, 2),
110
+ })
111
+
112
+ # Vertical line β€” catches east/west
113
+ if track_id not in counted_vertical:
114
+ if prev_cx < LINE_X <= cx:
115
+ counted_vertical.add(track_id)
116
+ crossings.append({
117
+ "track_id": track_id, "class": det["class"],
118
+ "line": "vertical", "direction": "east",
119
+ "frame": frame_count,
120
+ "timestamp_sec": round(frame_count / fps, 2),
121
+ })
122
+ elif prev_cx > LINE_X >= cx:
123
+ counted_vertical.add(track_id)
124
+ crossings.append({
125
+ "track_id": track_id, "class": det["class"],
126
+ "line": "vertical", "direction": "west",
127
+ "frame": frame_count,
128
+ "timestamp_sec": round(frame_count / fps, 2),
129
+ })
130
+
131
+ previous_centres[track_id] = (cx, cy)
132
+
133
+ current_unique = len(counted_horizontal | counted_vertical)
134
+ counter_text = f"Vehicles: {current_unique}"
135
+ cv2.putText(annotated, counter_text, (20, 40),
136
+ cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 2)
137
+
138
+ writer.write(annotated)
139
+ frame_count += 1
140
+ print(f"Frame {frame_count}: {len(detections)} objects | Unique so far: {current_unique}")
141
+
142
+ cap.release()
143
+ writer.release()
144
+
145
+ with open(output_csv_path, "w", newline="") as f:
146
+ fieldnames = ["track_id", "class", "line", "direction", "frame", "timestamp_sec"]
147
+ csv_writer = csv.DictWriter(f, fieldnames=fieldnames)
148
+ csv_writer.writeheader()
149
+ csv_writer.writerows(crossings)
150
+
151
+ unique_vehicles = counted_horizontal | counted_vertical
152
+ turning_vehicles = counted_horizontal & counted_vertical
153
+
154
+ summary = f"""### 🚦 Detection Results
155
+
156
+ - **Frames processed:** {frame_count}
157
+ - πŸš— **Unique vehicles detected:** {len(unique_vehicles)}
158
+ - β†ͺ️ **Turning vehicles** (crossed both lines): {len(turning_vehicles)}
159
+ - πŸ“Š **Total crossing events:** {len(crossings)}
160
+
161
+ _Built with YOLOv8 + ByteTrack. See repo for methodology._
162
+ """
163
+
164
+ return output_video_path, output_csv_path, summary
165
+
166
+
167
+ if __name__ == "__main__":
168
+ # CLI mode β€” runs on the local test video
169
+ script_dir = os.path.dirname(__file__)
170
+ project_dir = os.path.dirname(script_dir)
171
+ VIDEO_PATH = os.path.join(project_dir, "data", "traffic.mp4")
172
+
173
+ annotated, csv_out, summary = process_video(VIDEO_PATH)
174
+
175
+ print(f"\nβœ… Done!")
176
+ print(f"πŸ“Ή Annotated video: {annotated}")
177
+ print(f"πŸ“Š Crossings CSV: {csv_out}")
178
+ print(summary)
179
+
180
+ if MANUAL_UNIQUE > 0:
181
+ # Parse unique count from summary for accuracy print
182
+ m = re.search(r"Unique vehicles detected:\*\* (\d+)", summary)
183
+ if m:
184
+ auto = int(m.group(1))
185
+ acc = round(100 * (1 - abs(auto - MANUAL_UNIQUE) / MANUAL_UNIQUE), 1)
186
+ print(f"\n🎯 Unique vehicle accuracy vs manual ({MANUAL_UNIQUE}): {acc}%")