trannam1084 commited on
Commit
8cb5977
·
verified ·
1 Parent(s): d266903

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +216 -37
app.py CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  import os
2
  import tempfile
3
  import cv2
@@ -6,39 +9,49 @@ import gradio as gr
6
  import supervision as sv
7
  from ultralytics import YOLO
8
 
 
 
 
 
 
9
  model = YOLO("yolo11n.pt")
10
  CLASS_NAMES_DICT = model.model.names
11
 
12
- SELECTED_CLASS_NAMES = ['car', 'truck', 'bus', 'motorcycle']
13
  SELECTED_CLASS_IDS = [
14
  {value: key for key, value in CLASS_NAMES_DICT.items()}[name]
15
  for name in SELECTED_CLASS_NAMES
16
  ]
17
 
 
18
  box_annotator = sv.BoxAnnotator(thickness=4)
19
  label_annotator = sv.LabelAnnotator(text_thickness=2, text_scale=1.5, text_color=sv.Color.BLACK)
20
  trace_annotator = sv.TraceAnnotator(thickness=4, trace_length=50)
21
- line_zone_annotator = sv.LineZoneAnnotator(
22
- thickness=4, text_thickness=2, text_scale=2,
23
- display_in_count=False, display_out_count=False
24
- )
25
-
 
 
 
26
 
27
- def process_video(video_path):
 
 
 
 
 
28
  if video_path is None:
29
  return None
30
 
 
31
  if isinstance(video_path, dict):
32
  video_path = video_path.get("path", video_path)
33
 
 
34
  video_info = sv.VideoInfo.from_video_path(video_path)
35
- w, h = video_info.width, video_info.height
36
-
37
- line_y = int(h * 0.5)
38
- line_start = sv.Point(int(w * 0.005), line_y)
39
- line_end = sv.Point(int(w * 0.995), line_y)
40
 
41
- line_zone = sv.LineZone(start=line_start, end=line_end)
42
  byte_tracker = sv.ByteTrack(
43
  track_activation_threshold=0.25,
44
  lost_track_buffer=30,
@@ -55,11 +68,100 @@ def process_video(video_path):
55
  def callback(frame: np.ndarray, index: int) -> np.ndarray:
56
  nonlocal previous_positions, class_counts, crossed_ids
57
 
58
- results = model(frame, verbose=False)[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  detections = sv.Detections.from_ultralytics(results)
60
  detections = detections[np.isin(detections.class_id, SELECTED_CLASS_IDS)]
61
  detections = byte_tracker.update_with_detections(detections)
62
 
 
63
  if detections.tracker_id is not None:
64
  xyxy = detections.xyxy
65
  for i in range(len(detections)):
@@ -69,15 +171,26 @@ def process_video(video_path):
69
  cx = (xyxy[i, 0] + xyxy[i, 2]) / 2
70
  cy = (xyxy[i, 1] + xyxy[i, 3]) / 2
71
 
 
 
 
72
  if tid in previous_positions:
73
- py = previous_positions[tid]
74
- if py < line_y and cy > line_y and (tid, 'out') not in crossed_ids:
75
- crossed_ids.add((tid, 'out'))
 
 
 
 
76
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
77
- elif py > line_y and cy < line_y and (tid, 'in') not in crossed_ids:
78
- crossed_ids.add((tid, 'in'))
 
 
 
 
79
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
80
- previous_positions[tid] = cy
81
 
82
  labels = [
83
  f"#{tid} {CLASS_NAMES_DICT[cid]} {conf:0.2f}"
@@ -86,15 +199,32 @@ def process_video(video_path):
86
  )
87
  ]
88
 
89
- annotator_frame = frame.copy()
90
  annotator_frame = trace_annotator.annotate(scene=annotator_frame, detections=detections)
91
  annotator_frame = box_annotator.annotate(scene=annotator_frame, detections=detections)
92
- annotator_frame = label_annotator.annotate(scene=annotator_frame, detections=detections, labels=labels)
 
 
93
 
94
- line_zone.trigger(detections)
95
- annotator_frame = line_zone_annotator.annotate(annotator_frame, line_counter=line_zone)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- fh, fw, _ = annotator_frame.shape
98
  box_w, box_h = 280, 50 + len(SELECTED_CLASS_NAMES) * 28
99
  x0, y0 = fw - box_w - 20, 20
100
 
@@ -103,13 +233,26 @@ def process_video(video_path):
103
  annotator_frame = cv2.addWeighted(overlay, 0.6, annotator_frame, 0.4, 0)
104
 
105
  total = sum(class_counts.values())
106
- cv2.putText(annotator_frame, f'Total: {total}', (x0 + 10, y0 + 30),
107
- cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
 
 
 
 
 
 
 
108
  for i, cls_name in enumerate(SELECTED_CLASS_NAMES):
109
  cnt = class_counts.get(cls_name, 0)
110
- cv2.putText(annotator_frame, f'{cls_name.capitalize()}: {cnt}',
111
- (x0 + 10, y0 + 60 + i * 28),
112
- cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
 
 
 
 
 
 
113
 
114
  return annotator_frame
115
 
@@ -122,22 +265,58 @@ def process_video(video_path):
122
  return output_path
123
 
124
 
125
- with gr.Blocks(title="Đếm xe", theme=gr.themes.Soft()) as demo:
126
- gr.Markdown("# 🚗 Đếm xe (YOLOv8 + ByteTrack)")
127
- gr.Markdown("Upload video.")
 
 
 
128
 
129
  with gr.Row():
130
  video_input = gr.Video(label="Video đầu vào")
131
  video_output = gr.Video(label="Video đã xử lý")
132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  btn = gr.Button("▶️ Xử lý video")
134
- btn.click(fn=process_video, inputs=video_input, outputs=video_output)
 
 
 
 
135
 
136
- gr.Markdown("""
 
137
  ### Lưu ý
 
 
138
  - Chạy trên CPU miễn phí → nên dùng video **ngắn** (< 30 giây) để tránh chờ lâu
139
- - Model: YOLOv8n
140
- """)
 
141
 
142
  if __name__ == "__main__":
143
  demo.launch()
 
1
+ """
2
+ Đếm xe qua line - Deploy lên Hugging Face Spaces với Gradio
3
+ """
4
  import os
5
  import tempfile
6
  import cv2
 
9
  import supervision as sv
10
  from ultralytics import YOLO
11
 
12
+ # Cấu hình mặc định cho tối ưu
13
+ DEFAULT_MAX_FRAME_SIZE = 640 # Giới hạn kích thước khung hình đưa vào YOLO
14
+ DEFAULT_DETECT_EVERY_N_FRAMES = 2 # Chỉ detect mỗi N frame để giảm tải CPU
15
+
16
+ # Load model
17
  model = YOLO("yolo11n.pt")
18
  CLASS_NAMES_DICT = model.model.names
19
 
20
+ SELECTED_CLASS_NAMES = ['person', 'bus', 'motorcycle', 'car', 'truck']
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
+ # Annotators (tạo 1 lần, dùng lại)
27
  box_annotator = sv.BoxAnnotator(thickness=4)
28
  label_annotator = sv.LabelAnnotator(text_thickness=2, text_scale=1.5, text_color=sv.Color.BLACK)
29
  trace_annotator = sv.TraceAnnotator(thickness=4, trace_length=50)
30
+ def process_video(
31
+ video_path,
32
+ use_resize: bool = True,
33
+ max_frame_size: int = DEFAULT_MAX_FRAME_SIZE,
34
+ detect_every_n: int = DEFAULT_DETECT_EVERY_N_FRAMES,
35
+ line_orientation: str = "Ngang", # "Ngang" | "Dọc"
36
+ ):
37
+ """Xử lý video: đếm xe qua line và trả về video đã annotate.
38
 
39
+ Các tham số tối ưu và cấu hình được truyền từ UI:
40
+ - use_resize: có resize khung hình trước khi detect hay không
41
+ - max_frame_size: cạnh dài tối đa sau resize
42
+ - detect_every_n: chỉ detect mỗi N frame (1 = detect mọi frame)
43
+ - line_orientation: "Ngang" hoặc "Dọc"
44
+ """
45
  if video_path is None:
46
  return None
47
 
48
+ # Gradio Video có thể trả về dict với key "path"
49
  if isinstance(video_path, dict):
50
  video_path = video_path.get("path", video_path)
51
 
52
+ # Lấy thông tin video (dùng cho tracker)
53
  video_info = sv.VideoInfo.from_video_path(video_path)
 
 
 
 
 
54
 
 
55
  byte_tracker = sv.ByteTrack(
56
  track_activation_threshold=0.25,
57
  lost_track_buffer=30,
 
68
  def callback(frame: np.ndarray, index: int) -> np.ndarray:
69
  nonlocal previous_positions, class_counts, crossed_ids
70
 
71
+ # Đảm bảo giá trị hợp lệ
72
+ if max_frame_size is None or max_frame_size <= 0:
73
+ max_size = DEFAULT_MAX_FRAME_SIZE
74
+ else:
75
+ max_size = int(max_frame_size)
76
+
77
+ if detect_every_n is None or detect_every_n < 1:
78
+ detect_every = 1
79
+ else:
80
+ detect_every = int(detect_every_n)
81
+
82
+ # Resize khung hình trước khi đưa vào YOLO để giảm tải (nếu bật)
83
+ fh_orig, fw_orig = frame.shape[:2]
84
+ if use_resize:
85
+ scale = min(1.0, max_size / max(fh_orig, fw_orig))
86
+ if scale < 1.0:
87
+ frame_infer = cv2.resize(
88
+ frame, (int(fw_orig * scale), int(fh_orig * scale))
89
+ )
90
+ else:
91
+ frame_infer = frame
92
+ else:
93
+ frame_infer = frame
94
+
95
+ # Tính lại kích thước sau khi resize
96
+ fh, fw = frame_infer.shape[:2]
97
+ # Vị trí line theo hướng người dùng chọn
98
+ if line_orientation == "Dọc":
99
+ line_pos = int(fw * 0.5)
100
+ is_horizontal = False
101
+ else:
102
+ line_pos = int(fh * 0.5)
103
+ is_horizontal = True
104
+
105
+ # Bỏ qua một số frame để giảm số lần detect
106
+ if detect_every > 1 and index % detect_every != 0:
107
+ annotator_frame = frame_infer.copy()
108
+
109
+ # Vẽ line đếm theo cấu hình
110
+ if is_horizontal:
111
+ cv2.line(
112
+ annotator_frame,
113
+ (int(fw * 0.05), line_pos),
114
+ (int(fw * 0.95), line_pos),
115
+ (0, 255, 255),
116
+ 2,
117
+ )
118
+ else:
119
+ cv2.line(
120
+ annotator_frame,
121
+ (line_pos, int(fh * 0.05)),
122
+ (line_pos, int(fh * 0.95)),
123
+ (0, 255, 255),
124
+ 2,
125
+ )
126
+
127
+ # Khung tổng đếm (dùng class_counts hiện tại)
128
+ box_w, box_h = 280, 50 + len(SELECTED_CLASS_NAMES) * 28
129
+ x0, y0 = fw - box_w - 20, 20
130
+
131
+ overlay = annotator_frame.copy()
132
+ cv2.rectangle(overlay, (x0, y0), (x0 + box_w, y0 + box_h), (0, 0, 0), -1)
133
+ annotator_frame = cv2.addWeighted(overlay, 0.6, annotator_frame, 0.4, 0)
134
+
135
+ total = sum(class_counts.values())
136
+ cv2.putText(
137
+ annotator_frame,
138
+ f'Total: {total}',
139
+ (x0 + 10, y0 + 30),
140
+ cv2.FONT_HERSHEY_SIMPLEX,
141
+ 0.8,
142
+ (0, 255, 0),
143
+ 2,
144
+ )
145
+ for i, cls_name in enumerate(SELECTED_CLASS_NAMES):
146
+ cnt = class_counts.get(cls_name, 0)
147
+ cv2.putText(
148
+ annotator_frame,
149
+ f'{cls_name.capitalize()}: {cnt}',
150
+ (x0 + 10, y0 + 60 + i * 28),
151
+ cv2.FONT_HERSHEY_SIMPLEX,
152
+ 0.7,
153
+ (255, 255, 255),
154
+ 2,
155
+ )
156
+
157
+ return annotator_frame
158
+
159
+ results = model(frame_infer, verbose=False)[0]
160
  detections = sv.Detections.from_ultralytics(results)
161
  detections = detections[np.isin(detections.class_id, SELECTED_CLASS_IDS)]
162
  detections = byte_tracker.update_with_detections(detections)
163
 
164
+ # Đếm theo loại khi qua line
165
  if detections.tracker_id is not None:
166
  xyxy = detections.xyxy
167
  for i in range(len(detections)):
 
171
  cx = (xyxy[i, 0] + xyxy[i, 2]) / 2
172
  cy = (xyxy[i, 1] + xyxy[i, 3]) / 2
173
 
174
+ # Toạ độ 1D dùng để kiểm tra qua line (y nếu line ngang, x nếu line dọc)
175
+ curr_coord = cy if is_horizontal else cx
176
+
177
  if tid in previous_positions:
178
+ prev_coord = previous_positions[tid]
179
+ if (
180
+ prev_coord < line_pos
181
+ and curr_coord > line_pos
182
+ and (tid, "out") not in crossed_ids
183
+ ):
184
+ crossed_ids.add((tid, "out"))
185
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
186
+ elif (
187
+ prev_coord > line_pos
188
+ and curr_coord < line_pos
189
+ and (tid, "in") not in crossed_ids
190
+ ):
191
+ crossed_ids.add((tid, "in"))
192
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
193
+ previous_positions[tid] = curr_coord
194
 
195
  labels = [
196
  f"#{tid} {CLASS_NAMES_DICT[cid]} {conf:0.2f}"
 
199
  )
200
  ]
201
 
202
+ annotator_frame = frame_infer.copy()
203
  annotator_frame = trace_annotator.annotate(scene=annotator_frame, detections=detections)
204
  annotator_frame = box_annotator.annotate(scene=annotator_frame, detections=detections)
205
+ annotator_frame = label_annotator.annotate(
206
+ scene=annotator_frame, detections=detections, labels=labels
207
+ )
208
 
209
+ # Vẽ line đếm theo cấu hình
210
+ if is_horizontal:
211
+ cv2.line(
212
+ annotator_frame,
213
+ (int(fw * 0.05), line_pos),
214
+ (int(fw * 0.95), line_pos),
215
+ (0, 255, 255),
216
+ 2,
217
+ )
218
+ else:
219
+ cv2.line(
220
+ annotator_frame,
221
+ (line_pos, int(fh * 0.05)),
222
+ (line_pos, int(fh * 0.95)),
223
+ (0, 255, 255),
224
+ 2,
225
+ )
226
 
227
+ # Khung tổng đếm
228
  box_w, box_h = 280, 50 + len(SELECTED_CLASS_NAMES) * 28
229
  x0, y0 = fw - box_w - 20, 20
230
 
 
233
  annotator_frame = cv2.addWeighted(overlay, 0.6, annotator_frame, 0.4, 0)
234
 
235
  total = sum(class_counts.values())
236
+ cv2.putText(
237
+ annotator_frame,
238
+ f'Total: {total}',
239
+ (x0 + 10, y0 + 30),
240
+ cv2.FONT_HERSHEY_SIMPLEX,
241
+ 0.8,
242
+ (0, 255, 0),
243
+ 2,
244
+ )
245
  for i, cls_name in enumerate(SELECTED_CLASS_NAMES):
246
  cnt = class_counts.get(cls_name, 0)
247
+ cv2.putText(
248
+ annotator_frame,
249
+ f'{cls_name.capitalize()}: {cnt}',
250
+ (x0 + 10, y0 + 60 + i * 28),
251
+ cv2.FONT_HERSHEY_SIMPLEX,
252
+ 0.7,
253
+ (255, 255, 255),
254
+ 2,
255
+ )
256
 
257
  return annotator_frame
258
 
 
265
  return output_path
266
 
267
 
268
+ # Gradio UI
269
+ with gr.Blocks(title="Đếm xe qua line", theme=gr.themes.Soft()) as demo:
270
+ gr.Markdown("# 🚗 Đếm xe qua line (YOLOv8 + ByteTrack)")
271
+ gr.Markdown(
272
+ "Upload video, hệ thống sẽ đếm person, car, bus, truck, motorcycle khi qua đường line giữa khung hình."
273
+ )
274
 
275
  with gr.Row():
276
  video_input = gr.Video(label="Video đầu vào")
277
  video_output = gr.Video(label="Video đã xử lý")
278
 
279
+ # Tùy chọn cấu hình
280
+ with gr.Accordion("⚙️ Tùy chọn nâng cao", open=False):
281
+ use_resize = gr.Checkbox(
282
+ value=True, label="Giảm kích thước khung hình trước khi detect"
283
+ )
284
+ max_frame_size = gr.Slider(
285
+ minimum=320,
286
+ maximum=1280,
287
+ value=DEFAULT_MAX_FRAME_SIZE,
288
+ step=64,
289
+ label="Kích thước tối đa (px)",
290
+ )
291
+ detect_every_n = gr.Slider(
292
+ minimum=1,
293
+ maximum=5,
294
+ value=DEFAULT_DETECT_EVERY_N_FRAMES,
295
+ step=1,
296
+ label="Detect mỗi N frame (1 = mọi frame)",
297
+ )
298
+ line_orientation = gr.Radio(
299
+ choices=["Ngang", "Dọc"],
300
+ value="Ngang",
301
+ label="Hướng line đếm",
302
+ )
303
+
304
  btn = gr.Button("▶️ Xử lý video")
305
+ btn.click(
306
+ fn=process_video,
307
+ inputs=[video_input, use_resize, max_frame_size, detect_every_n, line_orientation],
308
+ outputs=video_output,
309
+ )
310
 
311
+ gr.Markdown(
312
+ """
313
  ### Lưu ý
314
+ - Mặc định line đếm nằm **ngang** ở giữa khung hình (50% chiều cao)
315
+ - Có thể chọn lại line **dọc** trong phần "Tùy chọn nâng cao"
316
  - Chạy trên CPU miễn phí → nên dùng video **ngắn** (< 30 giây) để tránh chờ lâu
317
+ - Model: YOLOv8n (nhẹ cho CPU)
318
+ """
319
+ )
320
 
321
  if __name__ == "__main__":
322
  demo.launch()