trannam1084 commited on
Commit
5897aa9
·
verified ·
1 Parent(s): 67e5283

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +204 -373
app.py CHANGED
@@ -1,223 +1,120 @@
1
  import os
2
- import shutil
3
- import subprocess
4
  import tempfile
 
5
 
6
  import cv2
7
- import imageio
8
- import numpy as np
9
  import gradio as gr
 
10
  import supervision as sv
11
  from ultralytics import YOLO
12
 
13
- DEFAULT_MAX_FRAME_SIZE = 480
14
- DEFAULT_DETECT_EVERY_N_FRAMES = 3
15
- DEFAULT_ZONE_MARGIN = 0.10
16
 
17
- model = YOLO("yolov8n.pt")
18
- CLASS_NAMES_DICT = model.model.names
19
 
20
- SELECTED_CLASS_NAMES = ['car', 'truck', 'bus', 'motorcycle', ]
 
21
  SELECTED_CLASS_IDS = [
22
- {value: key for key, value in CLASS_NAMES_DICT.items()}[name]
23
- for name in SELECTED_CLASS_NAMES
24
  ]
25
 
26
- box_annotator = sv.BoxAnnotator(thickness=4)
27
- label_annotator = sv.LabelAnnotator(text_thickness=2, text_scale=1.5, text_color=sv.Color.BLACK)
28
- trace_annotator = sv.TraceAnnotator(thickness=4, trace_length=50)
29
  def process_video(
30
- video_path,
31
- use_resize: bool = True,
32
- max_frame_size: int = DEFAULT_MAX_FRAME_SIZE,
33
- detect_every_n: int = DEFAULT_DETECT_EVERY_N_FRAMES,
34
- line_orientation: str = "Ngang",
35
- zone_margin: float = DEFAULT_ZONE_MARGIN,
36
- ):
37
- if video_path is None:
38
- return None
39
-
40
- if isinstance(video_path, dict):
41
- video_path = video_path.get("path", video_path)
42
 
43
  cap = cv2.VideoCapture(video_path)
44
  if not cap.isOpened():
45
- return None
 
 
 
 
46
 
47
- fps = cap.get(cv2.CAP_PROP_FPS)
48
- if fps is None or fps <= 0 or np.isnan(fps):
49
- fps = 30
 
50
 
51
  byte_tracker = sv.ByteTrack(
52
  track_activation_threshold=0.25,
53
  lost_track_buffer=30,
54
  minimum_matching_threshold=0.8,
55
  frame_rate=fps,
56
- minimum_consecutive_frames=3
57
  )
58
- byte_tracker.reset()
59
-
60
- class_counts = {name: 0 for name in SELECTED_CLASS_NAMES}
61
- counted_ids = set()
62
 
63
- def callback(frame: np.ndarray, index: int) -> np.ndarray:
64
- nonlocal class_counts, counted_ids
65
-
66
- if max_frame_size is None or max_frame_size <= 0:
67
- max_size = DEFAULT_MAX_FRAME_SIZE
68
- else:
69
- max_size = int(max_frame_size)
70
-
71
- if detect_every_n is None or detect_every_n < 1:
72
- detect_every = 1
73
- else:
74
- detect_every = int(detect_every_n)
75
-
76
- fh_orig, fw_orig = frame.shape[:2]
77
- if use_resize:
78
- scale = min(1.0, max_size / max(fh_orig, fw_orig))
79
- if scale < 1.0:
80
- frame_infer = cv2.resize(
81
- frame, (int(fw_orig * scale), int(fh_orig * scale))
82
- )
83
- else:
84
- frame_infer = frame
85
- else:
86
- frame_infer = frame
 
 
 
 
 
 
87
 
88
- fh, fw = frame_infer.shape[:2]
89
- if line_orientation == "Dọc":
90
- line_pos = int(fw * 0.5)
91
- is_horizontal = False
92
- else:
93
- line_pos = int(fh * 0.5)
94
- is_horizontal = True
95
 
96
- if zone_margin is None or zone_margin <= 0:
97
- zm_ratio = DEFAULT_ZONE_MARGIN
98
- else:
99
- zm_ratio = max(0.01, min(0.5, float(zone_margin)))
100
 
101
- if is_horizontal:
102
- z_half = int(fh * zm_ratio)
103
- z_top = max(0, line_pos - z_half)
104
- z_bot = min(fh - 1, line_pos + z_half)
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  else:
106
- z_half = int(fw * zm_ratio)
107
- z_left = max(0, line_pos - z_half)
108
- z_right = min(fw - 1, line_pos + z_half)
109
-
110
- if detect_every > 1 and index % detect_every != 0:
111
- annotator_frame = frame_infer.copy()
112
- overlay_zone = annotator_frame.copy()
113
- if is_horizontal:
114
- cv2.rectangle(
115
- overlay_zone,
116
- (0, z_top),
117
- (fw, z_bot),
118
- (0, 0, 200),
119
- -1,
120
- )
121
- else:
122
- cv2.rectangle(
123
- overlay_zone,
124
- (z_left, 0),
125
- (z_right, fh),
126
- (0, 0, 200),
127
- -1,
128
- )
129
- annotator_frame = cv2.addWeighted(
130
- overlay_zone, 0.18, annotator_frame, 0.82, 0
131
- )
132
-
133
- thickness_base = max(2, int(2 * (max(fw, fh) / 1920)))
134
- if is_horizontal:
135
- cv2.line(
136
- annotator_frame,
137
- (0, z_top),
138
- (fw, z_top),
139
- (0, 100, 255),
140
- thickness_base,
141
- )
142
- cv2.line(
143
- annotator_frame,
144
- (0, z_bot),
145
- (fw, z_bot),
146
- (0, 100, 255),
147
- thickness_base,
148
- )
149
- cv2.line(
150
- annotator_frame,
151
- (0, line_pos),
152
- (fw, line_pos),
153
- (0, 0, 255),
154
- max(3, thickness_base + 1),
155
- )
156
- else:
157
- cv2.line(
158
- annotator_frame,
159
- (z_left, 0),
160
- (z_left, fh),
161
- (0, 100, 255),
162
- thickness_base,
163
- )
164
- cv2.line(
165
- annotator_frame,
166
- (z_right, 0),
167
- (z_right, fh),
168
- (0, 100, 255),
169
- thickness_base,
170
- )
171
- cv2.line(
172
- annotator_frame,
173
- (line_pos, 0),
174
- (line_pos, fh),
175
- (0, 0, 255),
176
- max(3, thickness_base + 1),
177
- )
178
-
179
- # Kích thước khung thống kê tỉ lệ theo kích thước khung hình
180
- scale_ui = max(fw, fh) / 1280.0
181
- base_box_w = 260
182
- base_box_h = 60 + len(SELECTED_CLASS_NAMES) * 26
183
- box_w = int(base_box_w * scale_ui)
184
- box_h = int(base_box_h * scale_ui)
185
- x0, y0 = fw - box_w - 20, 20
186
-
187
- overlay = annotator_frame.copy()
188
- cv2.rectangle(overlay, (x0, y0), (x0 + box_w, y0 + box_h), (0, 0, 0), -1)
189
- annotator_frame = cv2.addWeighted(overlay, 0.6, annotator_frame, 0.4, 0)
190
-
191
- total = sum(class_counts.values())
192
- cv2.putText(
193
- annotator_frame,
194
- f'Total: {total}',
195
- (x0 + 10, y0 + 30),
196
- cv2.FONT_HERSHEY_SIMPLEX,
197
- 0.8,
198
- (0, 255, 0),
199
- 2,
200
- )
201
- for i, cls_name in enumerate(SELECTED_CLASS_NAMES):
202
- cnt = class_counts.get(cls_name, 0)
203
- cv2.putText(
204
- annotator_frame,
205
- f'{cls_name.capitalize()}: {cnt}',
206
- (x0 + 10, y0 + 60 + i * 28),
207
- cv2.FONT_HERSHEY_SIMPLEX,
208
- 0.7,
209
- (255, 255, 255),
210
- 2,
211
- )
212
-
213
- return annotator_frame
214
 
215
- results = model(frame_infer, verbose=False, imgsz=480)[0]
216
- detections = sv.Detections.from_ultralytics(results)
217
- detections = detections[np.isin(detections.class_id, SELECTED_CLASS_IDS)]
218
- detections = byte_tracker.update_with_detections(detections)
219
-
220
- if detections.tracker_id is not None:
221
  xyxy = detections.xyxy
222
  for i in range(len(detections)):
223
  tid = int(detections.tracker_id[i])
@@ -226,111 +123,60 @@ def process_video(
226
  cx = (xyxy[i, 0] + xyxy[i, 2]) / 2
227
  cy = (xyxy[i, 1] + xyxy[i, 3]) / 2
228
 
229
- if cls_name in SELECTED_CLASS_NAMES and tid not in counted_ids:
230
- if is_horizontal and z_top <= cy <= z_bot:
 
 
 
 
 
 
 
 
231
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
232
- counted_ids.add(tid)
233
- elif (not is_horizontal) and z_left <= cx <= z_right:
 
 
 
 
234
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
235
- counted_ids.add(tid)
236
-
237
- labels = [
238
- f"#{tid} {CLASS_NAMES_DICT[cid]} {conf:0.2f}"
239
- for conf, cid, tid in zip(
240
- detections.confidence, detections.class_id, detections.tracker_id
241
- )
242
- ]
243
-
244
- annotator_frame = frame_infer.copy()
245
- annotator_frame = trace_annotator.annotate(scene=annotator_frame, detections=detections)
246
- annotator_frame = box_annotator.annotate(scene=annotator_frame, detections=detections)
247
- annotator_frame = label_annotator.annotate(
248
- scene=annotator_frame, detections=detections, labels=labels
249
- )
250
 
251
- overlay_zone = annotator_frame.copy()
252
- if is_horizontal:
253
- cv2.rectangle(
254
- overlay_zone,
255
- (0, z_top),
256
- (fw, z_bot),
257
- (0, 0, 200),
258
- -1,
259
- )
260
- else:
261
- cv2.rectangle(
262
- overlay_zone,
263
- (z_left, 0),
264
- (z_right, fh),
265
- (0, 0, 200),
266
- -1,
267
- )
268
- annotator_frame = cv2.addWeighted(
269
- overlay_zone, 0.18, annotator_frame, 0.82, 0
270
- )
271
 
272
- thickness_base = max(2, int(2 * (max(fw, fh) / 1920)))
273
- if is_horizontal:
274
- cv2.line(
275
- annotator_frame,
276
- (0, z_top),
277
- (fw, z_top),
278
- (0, 100, 255),
279
- thickness_base,
280
  )
281
- cv2.line(
282
- annotator_frame,
283
- (0, z_bot),
284
- (fw, z_bot),
285
- (0, 100, 255),
286
- thickness_base,
287
  )
288
- cv2.line(
289
- annotator_frame,
290
- (0, line_pos),
291
- (fw, line_pos),
292
- (0, 0, 255),
293
- max(3, thickness_base + 1),
294
  )
295
- else:
296
- cv2.line(
297
- annotator_frame,
298
- (z_left, 0),
299
- (z_left, fh),
300
- (0, 100, 255),
301
- thickness_base,
302
- )
303
- cv2.line(
304
- annotator_frame,
305
- (z_right, 0),
306
- (z_right, fh),
307
- (0, 100, 255),
308
- thickness_base,
309
- )
310
- cv2.line(
311
- annotator_frame,
312
- (line_pos, 0),
313
- (line_pos, fh),
314
- (0, 0, 255),
315
- max(3, thickness_base + 1),
316
  )
317
 
318
- # Kích thước khung thống kê tỉ lệ theo kích thước khung hình
319
- scale_ui = max(fw, fh) / 1280.0
320
- base_box_w = 260
321
- base_box_h = 60 + len(SELECTED_CLASS_NAMES) * 26
322
- box_w = int(base_box_w * scale_ui)
323
- box_h = int(base_box_h * scale_ui)
324
- x0, y0 = fw - box_w - 20, 20
325
 
326
- overlay = annotator_frame.copy()
327
  cv2.rectangle(overlay, (x0, y0), (x0 + box_w, y0 + box_h), (0, 0, 0), -1)
328
- annotator_frame = cv2.addWeighted(overlay, 0.6, annotator_frame, 0.4, 0)
329
 
330
  total = sum(class_counts.values())
331
  cv2.putText(
332
- annotator_frame,
333
- f'Total: {total}',
334
  (x0 + 10, y0 + 30),
335
  cv2.FONT_HERSHEY_SIMPLEX,
336
  0.8,
@@ -340,8 +186,8 @@ def process_video(
340
  for i, cls_name in enumerate(SELECTED_CLASS_NAMES):
341
  cnt = class_counts.get(cls_name, 0)
342
  cv2.putText(
343
- annotator_frame,
344
- f'{cls_name.capitalize()}: {cnt}',
345
  (x0 + 10, y0 + 60 + i * 28),
346
  cv2.FONT_HERSHEY_SIMPLEX,
347
  0.7,
@@ -349,112 +195,97 @@ def process_video(
349
  2,
350
  )
351
 
352
- return annotator_frame
353
-
354
- output_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
355
- writer = imageio.get_writer(output_path, fps=fps)
356
-
357
- index = 0
358
- while True:
359
- ret, frame = cap.read()
360
- if not ret:
361
- break
362
-
363
- annotated_frame = callback(frame, index)
364
- index += 1
365
-
366
- # imageio expects RGB frames
367
- annotated_frame_rgb = cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB)
368
- writer.append_data(annotated_frame_rgb)
369
 
370
- writer.close()
371
  cap.release()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
 
373
- return output_path
 
 
 
 
374
 
 
375
 
376
- with gr.Blocks(title="Nhận dạng phương tiện giao thông", theme=gr.themes.Soft(primary_hue="blue", secondary_hue="gray")) as demo:
377
- with gr.Row():
378
- gr.Markdown(
379
- """
380
- <div style="display:flex;flex-direction:column;gap:4px;">
381
- <h1 style="margin-bottom:4px;">🚗 Nhận diện phương tiện giao thông (YOLOv8 + ByteTrack)</h1>
382
- <p style="margin:0;font-size:14px;color:#6b7280;">
383
- Thực hiện: <strong>Trần Hải Nam - 223332840</strong>
384
- </p>
385
- </div>
386
- """,
387
- elem_id="header",
388
- )
389
 
390
- with gr.Row():
391
- with gr.Column(scale=1):
392
- gr.Markdown(
393
- "### 🎥 Video đầu vào\n"
394
- "Upload video ngắn (ưu tiên &lt; 30s để xử lý nhanh hơn)."
395
- )
396
- video_input = gr.Video(label="Video đầu vào")
397
 
398
- with gr.Column(scale=1):
399
- gr.Markdown(
400
- "### ✅ Kết quả đã xử lý\n"
401
- "Hiển thị và thống kê số lượng theo lớp."
402
- )
403
- video_output = gr.Video(label="Video đã xử lý", format="mp4")
404
 
405
- # Tùy chọn cấu hình
406
- with gr.Accordion("⚙️ Tùy chọn nâng cao", open=False):
407
- with gr.Row():
408
- use_resize = gr.Checkbox(
409
- value=True, label="Giảm kích thước khung hình trước khi nhận dạng"
 
 
 
410
  )
411
- line_orientation = gr.Radio(
412
- choices=["Ngang", "Dọc"],
413
  value="Ngang",
414
- label="Hướng phương tiện di chuyển",
415
- )
416
- with gr.Row():
417
- max_frame_size = gr.Slider(
418
- minimum=320,
419
- maximum=1280,
420
- value=DEFAULT_MAX_FRAME_SIZE,
421
- step=64,
422
- label="Kích thước tối đa (px)",
423
  )
424
- detect_every_n = gr.Slider(
425
- minimum=1,
426
- maximum=5,
427
- value=DEFAULT_DETECT_EVERY_N_FRAMES,
428
- step=1,
429
- label="Detect mỗi N frame (1 = mọi frame)",
430
- )
431
- zone_margin = gr.Slider(
432
- minimum=0.02,
433
- maximum=0.30,
434
- value=DEFAULT_ZONE_MARGIN,
435
- step=0.01,
436
- label="Độ dày vùng đếm quanh line",
437
  )
438
 
439
- btn = gr.Button("▶️ Xử lý video", variant="primary")
440
- btn.click(
441
- fn=process_video,
442
- inputs=[video_input, use_resize, max_frame_size, detect_every_n, line_orientation, zone_margin],
443
- outputs=video_output,
444
  )
445
 
446
- gr.Markdown(
447
- """
448
- ---
449
- ### ℹ️ Gợi ý sử dụng
450
- - Mặc định hướng phương tiện di chuyển để nhận dạng là nằm **ngang** ở giữa khung hình (50% chiều cao).
451
- - Có thể chuyển sang hướng **dọc** trong phần _"Tùy chọn nâng cao"_.
452
- - Vì sử dụng CPU, nên:
453
- - Dùng video **ngắn** (&lt; 30 giây).
454
- - Tăng `Detect mỗi N frame` nếu muốn xử lý nhanh hơn.
455
- - Model sử dụng: **YOLOv8n**.
456
- """
457
- )
458
 
459
  if __name__ == "__main__":
460
  demo.launch()
 
 
1
  import os
 
 
2
  import tempfile
3
+ from typing import Dict, Tuple
4
 
5
  import cv2
 
 
6
  import gradio as gr
7
+ import numpy as np
8
  import supervision as sv
9
  from ultralytics import YOLO
10
 
 
 
 
11
 
12
+ MODEL_PATH = "yolo11n.pt"
13
+ model = YOLO(MODEL_PATH)
14
 
15
+ CLASS_NAMES_DICT = model.model.names
16
+ SELECTED_CLASS_NAMES = ["car", "bus", "truck", "motorcycle"]
17
  SELECTED_CLASS_IDS = [
18
+ {value: key for key, value in CLASS_NAMES_DICT.items()}[class_name]
19
+ for class_name in SELECTED_CLASS_NAMES
20
  ]
21
 
22
+
 
 
23
  def process_video(
24
+ video_path: str,
25
+ frame_stride: int = 2,
26
+ max_seconds: int = 60,
27
+ line_orientation: str = "horizontal",
28
+ ) -> Tuple[str, Dict[str, int]]:
29
+ """
30
+ Xử lý video: phát hiện + tracking + đếm phương tiện.
31
+ Trả về: đường dẫn video đã annotate và dict số lượng theo class.
32
+ """
33
+ video_info = sv.VideoInfo.from_video_path(video_path)
34
+ fps = video_info.fps
35
+ max_frames_for_detection = int(fps * max_seconds) if max_seconds is not None else None
36
 
37
  cap = cv2.VideoCapture(video_path)
38
  if not cap.isOpened():
39
+ raise RuntimeError("Không thể mở video đầu vào.")
40
+
41
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
42
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
43
+ fps = cap.get(cv2.CAP_PROP_FPS) or 25.0
44
 
45
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
46
+ tmp_dir = tempfile.mkdtemp()
47
+ out_path = os.path.join(tmp_dir, "result.mp4")
48
+ writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
49
 
50
  byte_tracker = sv.ByteTrack(
51
  track_activation_threshold=0.25,
52
  lost_track_buffer=30,
53
  minimum_matching_threshold=0.8,
54
  frame_rate=fps,
55
+ minimum_consecutive_frames=3,
56
  )
 
 
 
 
57
 
58
+ if line_orientation == "vertical":
59
+ line_pos = int(width * 0.5)
60
+ line_start = sv.Point(line_pos, int(height * 0.05))
61
+ line_end = sv.Point(line_pos, int(height * 0.95))
62
+ axis = "x"
63
+ else:
64
+ line_pos = int(height * 0.5)
65
+ line_start = sv.Point(int(width * 0.05), line_pos)
66
+ line_end = sv.Point(int(width * 0.95), line_pos)
67
+ axis = "y"
68
+
69
+ line_zone = sv.LineZone(start=line_start, end=line_end)
70
+
71
+ previous_positions: Dict[int, float] = {}
72
+ class_counts: Dict[str, int] = {name: 0 for name in SELECTED_CLASS_NAMES}
73
+ crossed_ids = set()
74
+
75
+ box_annotator = sv.BoxAnnotator(thickness=4)
76
+ label_annotator = sv.LabelAnnotator(
77
+ text_thickness=2, text_scale=1.0, text_color=sv.Color.BLACK
78
+ )
79
+ trace_annotator = sv.TraceAnnotator(thickness=4, trace_length=50)
80
+ line_zone_annotator = sv.LineZoneAnnotator(
81
+ thickness=4,
82
+ color=sv.Color.RED,
83
+ text_thickness=2,
84
+ text_scale=2,
85
+ display_in_count=False,
86
+ display_out_count=False,
87
+ )
88
 
89
+ frame_idx = 0
90
+ last_detections = None
 
 
 
 
 
91
 
92
+ while True:
93
+ ret, frame = cap.read()
94
+ if not ret:
95
+ break
96
 
97
+ annotated_frame = frame.copy()
98
+
99
+ can_run_detection = True
100
+ if max_frames_for_detection is not None and frame_idx >= max_frames_for_detection:
101
+ can_run_detection = False
102
+
103
+ if can_run_detection and frame_idx % frame_stride == 0:
104
+ results = model(
105
+ frame,
106
+ imgsz=640,
107
+ device="cpu",
108
+ verbose=False,
109
+ classes=SELECTED_CLASS_IDS,
110
+ )[0]
111
+ detections = sv.Detections.from_ultralytics(results)
112
+ detections = byte_tracker.update_with_detections(detections)
113
+ last_detections = detections
114
  else:
115
+ detections = last_detections
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
+ if detections is not None and detections.tracker_id is not None:
 
 
 
 
 
118
  xyxy = detections.xyxy
119
  for i in range(len(detections)):
120
  tid = int(detections.tracker_id[i])
 
123
  cx = (xyxy[i, 0] + xyxy[i, 2]) / 2
124
  cy = (xyxy[i, 1] + xyxy[i, 3]) / 2
125
 
126
+ cur_pos = cy if axis == "y" else cx
127
+
128
+ if tid in previous_positions:
129
+ prev_pos = previous_positions[tid]
130
+ if (
131
+ prev_pos < line_pos
132
+ and cur_pos > line_pos
133
+ and (tid, "pos") not in crossed_ids
134
+ ):
135
+ crossed_ids.add((tid, "pos"))
136
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
137
+ elif (
138
+ prev_pos > line_pos
139
+ and cur_pos < line_pos
140
+ and (tid, "neg") not in crossed_ids
141
+ ):
142
+ crossed_ids.add((tid, "neg"))
143
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
144
+ previous_positions[tid] = cur_pos
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
+ labels = [
147
+ f"#{tracker_id} {CLASS_NAMES_DICT[class_id]} {confidence:0.2f}"
148
+ for confidence, class_id, tracker_id in zip(
149
+ detections.confidence, detections.class_id, detections.tracker_id
150
+ )
151
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
+ annotated_frame = trace_annotator.annotate(
154
+ scene=annotated_frame, detections=detections
 
 
 
 
 
 
155
  )
156
+ annotated_frame = box_annotator.annotate(
157
+ scene=annotated_frame, detections=detections
 
 
 
 
158
  )
159
+ annotated_frame = label_annotator.annotate(
160
+ scene=annotated_frame, detections=detections, labels=labels
 
 
 
 
161
  )
162
+
163
+ line_zone.trigger(detections)
164
+ annotated_frame = line_zone_annotator.annotate(
165
+ annotated_frame, line_counter=line_zone
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  )
167
 
168
+ h, w, _ = annotated_frame.shape
169
+ box_w, box_h = 280, 50 + len(SELECTED_CLASS_NAMES) * 28
170
+ x0, y0 = w - box_w - 20, 20
 
 
 
 
171
 
172
+ overlay = annotated_frame.copy()
173
  cv2.rectangle(overlay, (x0, y0), (x0 + box_w, y0 + box_h), (0, 0, 0), -1)
174
+ annotated_frame = cv2.addWeighted(overlay, 0.6, annotated_frame, 0.4, 0)
175
 
176
  total = sum(class_counts.values())
177
  cv2.putText(
178
+ annotated_frame,
179
+ f"Total: {total}",
180
  (x0 + 10, y0 + 30),
181
  cv2.FONT_HERSHEY_SIMPLEX,
182
  0.8,
 
186
  for i, cls_name in enumerate(SELECTED_CLASS_NAMES):
187
  cnt = class_counts.get(cls_name, 0)
188
  cv2.putText(
189
+ annotated_frame,
190
+ f"{cls_name.capitalize()}: {cnt}",
191
  (x0 + 10, y0 + 60 + i * 28),
192
  cv2.FONT_HERSHEY_SIMPLEX,
193
  0.7,
 
195
  2,
196
  )
197
 
198
+ writer.write(annotated_frame)
199
+ frame_idx += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
 
201
  cap.release()
202
+ writer.release()
203
+
204
+ return out_path, class_counts
205
+
206
+
207
+ def gradio_infer(video, mode: str = "Cân bằng", line_type: str = "Ngang"):
208
+ if video is None:
209
+ return None, "Vui lòng upload 1 video."
210
+
211
+ if isinstance(video, dict):
212
+ video_path = video.get("name") or video.get("data")
213
+ else:
214
+ video_path = video
215
+
216
+ if not video_path or not os.path.exists(video_path):
217
+ return None, "Không tìm thấy file video."
218
+
219
+ if mode == "Nhanh":
220
+ frame_stride = 4
221
+ max_seconds = 30
222
+ elif mode == "Chính xác":
223
+ frame_stride = 1
224
+ max_seconds = 90
225
+ else: # Cân bằng
226
+ frame_stride = 2
227
+ max_seconds = 60
228
+
229
+ if line_type == "Dọc":
230
+ line_orientation = "vertical"
231
+ else:
232
+ line_orientation = "horizontal"
233
+
234
+ out_path, counts = process_video(
235
+ video_path=video_path,
236
+ frame_stride=frame_stride,
237
+ max_seconds=max_seconds,
238
+ line_orientation=line_orientation,
239
+ )
240
 
241
+ total = sum(counts.values())
242
+ lines = [f"Tổng số phương tiện: {total}"]
243
+ for cls_name in SELECTED_CLASS_NAMES:
244
+ lines.append(f"- {cls_name}: {counts.get(cls_name, 0)}")
245
+ summary = "\n".join(lines)
246
 
247
+ return out_path, summary
248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
+ with gr.Blocks() as demo:
251
+ gr.Markdown(
252
+ """
253
+ # Nhận diện phương tiện trong video (YOLO + ByteTrack)
 
 
 
254
 
255
+ Upload 1 video.
256
+ """
257
+ )
 
 
 
258
 
259
+ with gr.Row():
260
+ with gr.Column():
261
+ video_input = gr.Video(label="Video đầu vào", sources=["upload"])
262
+ mode_input = gr.Radio(
263
+ ["Nhanh", "Cân bằng", "Chính xác"],
264
+ value="Cân bằng",
265
+ label="Chế độ xử lý",
266
+ info="Nhanh: nhanh hơn, ít chính xác hơn. Chính xác: chậm hơn.",
267
  )
268
+ line_type_input = gr.Radio(
269
+ ["Ngang", "Dọc"],
270
  value="Ngang",
271
+ label="Hướng đường đếm",
272
+ info="Ngang: đường ngang ở giữa khung hình. Dọc: đường dọc ở giữa khung hình.",
 
 
 
 
 
 
 
273
  )
274
+ run_btn = gr.Button("Bắt đầu đếm")
275
+
276
+ with gr.Column():
277
+ video_output = gr.Video(label="Video đã xử lý")
278
+ text_output = gr.Textbox(
279
+ label="Kết quả đếm", lines=6, interactive=False
 
 
 
 
 
 
 
280
  )
281
 
282
+ run_btn.click(
283
+ fn=gradio_infer,
284
+ inputs=[video_input, mode_input, line_type_input],
285
+ outputs=[video_output, text_output],
 
286
  )
287
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
  if __name__ == "__main__":
290
  demo.launch()
291
+