trannam1084 commited on
Commit
e2e77ba
·
verified ·
1 Parent(s): a3a869b

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +26 -6
  2. app.py +156 -0
  3. requirements.txt +5 -0
README.md CHANGED
@@ -1,13 +1,33 @@
1
  ---
2
- title: Car Counting
3
- emoji: 🚀
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.8.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Đếm xe qua line
3
+ emoji: 🚗
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: gradio
7
+ sdk_version: 4.44.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
  ---
12
 
13
+ # 🚗 Đếm xe qua line (YOLOv8 + ByteTrack)
14
+
15
+ Ứng dụng đếm phương tiện (person, car, bus, truck, motorcycle) khi qua đường line trong video.
16
+
17
+ ## Cách dùng
18
+
19
+ 1. Upload video
20
+ 2. Bấm **Xử lý video**
21
+ 3. Xem video kết quả với khung đếm theo từng loại
22
+
23
+ ## Lưu ý
24
+
25
+ - Chạy trên **CPU Basic** (miễn phí). GPU HF tính phí ~0.40$/h.
26
+ - Nên dùng video **ngắn** (< 30 giây) vì CPU xử lý chậm.
27
+
28
+ ## Công nghệ
29
+
30
+ - **YOLOv8** (Ultralytics) - Object detection
31
+ - **ByteTrack** - Multi-object tracking
32
+ - **Supervision** - Line zone counting
33
+ - **Gradio** - Web UI
app.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
7
+ import numpy as np
8
+ import gradio as gr
9
+ import supervision as sv
10
+ from ultralytics import YOLO
11
+
12
+ # Load model (yolov8n nhẹ cho CPU miễn phí)
13
+ model = YOLO("yolov8n.pt")
14
+ CLASS_NAMES_DICT = model.model.names
15
+
16
+ SELECTED_CLASS_NAMES = ['person', 'bus', 'motorcycle', 'car', 'truck']
17
+ SELECTED_CLASS_IDS = [
18
+ {value: key for key, value in CLASS_NAMES_DICT.items()}[name]
19
+ for name in SELECTED_CLASS_NAMES
20
+ ]
21
+
22
+ # Annotators (tạo 1 lần, dùng lại)
23
+ box_annotator = sv.BoxAnnotator(thickness=4)
24
+ label_annotator = sv.LabelAnnotator(text_thickness=2, text_scale=1.5, text_color=sv.Color.BLACK)
25
+ trace_annotator = sv.TraceAnnotator(thickness=4, trace_length=50)
26
+ line_zone_annotator = sv.LineZoneAnnotator(
27
+ thickness=4, text_thickness=2, text_scale=2,
28
+ display_in_count=False, display_out_count=False
29
+ )
30
+
31
+
32
+ def process_video(video_path):
33
+ """Xử lý video: đếm xe qua line và trả về video đã annotate."""
34
+ if video_path is None:
35
+ return None
36
+
37
+ # Gradio Video có thể trả về dict với key "path"
38
+ if isinstance(video_path, dict):
39
+ video_path = video_path.get("path", video_path)
40
+
41
+ # Lấy thông tin video để tính line động theo kích thước frame
42
+ video_info = sv.VideoInfo.from_video_path(video_path)
43
+ w, h = video_info.width, video_info.height
44
+
45
+ # Line ngang ở giữa khung (50% chiều cao), cách mép 5%
46
+ line_y = int(h * 0.5)
47
+ line_start = sv.Point(int(w * 0.05), line_y)
48
+ line_end = sv.Point(int(w * 0.95), line_y)
49
+
50
+ line_zone = sv.LineZone(start=line_start, end=line_end)
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=video_info.fps or 30,
56
+ minimum_consecutive_frames=3
57
+ )
58
+ byte_tracker.reset()
59
+
60
+ previous_positions = {}
61
+ class_counts = {name: 0 for name in SELECTED_CLASS_NAMES}
62
+ crossed_ids = set()
63
+
64
+ def callback(frame: np.ndarray, index: int) -> np.ndarray:
65
+ nonlocal previous_positions, class_counts, crossed_ids
66
+
67
+ results = model(frame, verbose=False)[0]
68
+ detections = sv.Detections.from_ultralytics(results)
69
+ detections = detections[np.isin(detections.class_id, SELECTED_CLASS_IDS)]
70
+ detections = byte_tracker.update_with_detections(detections)
71
+
72
+ # Đếm theo loại khi qua line
73
+ if detections.tracker_id is not None:
74
+ xyxy = detections.xyxy
75
+ for i in range(len(detections)):
76
+ tid = int(detections.tracker_id[i])
77
+ cls_id = int(detections.class_id[i])
78
+ cls_name = CLASS_NAMES_DICT[cls_id]
79
+ cx = (xyxy[i, 0] + xyxy[i, 2]) / 2
80
+ cy = (xyxy[i, 1] + xyxy[i, 3]) / 2
81
+
82
+ if tid in previous_positions:
83
+ py = previous_positions[tid]
84
+ if py < line_y and cy > line_y and (tid, 'out') not in crossed_ids:
85
+ crossed_ids.add((tid, 'out'))
86
+ class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
87
+ elif py > line_y and cy < line_y and (tid, 'in') not in crossed_ids:
88
+ crossed_ids.add((tid, 'in'))
89
+ class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
90
+ previous_positions[tid] = cy
91
+
92
+ labels = [
93
+ f"#{tid} {CLASS_NAMES_DICT[cid]} {conf:0.2f}"
94
+ for conf, cid, tid in zip(
95
+ detections.confidence, detections.class_id, detections.tracker_id
96
+ )
97
+ ]
98
+
99
+ annotator_frame = frame.copy()
100
+ annotator_frame = trace_annotator.annotate(scene=annotator_frame, detections=detections)
101
+ annotator_frame = box_annotator.annotate(scene=annotator_frame, detections=detections)
102
+ annotator_frame = label_annotator.annotate(scene=annotator_frame, detections=detections, labels=labels)
103
+
104
+ line_zone.trigger(detections)
105
+ annotator_frame = line_zone_annotator.annotate(annotator_frame, line_counter=line_zone)
106
+
107
+ # Khung tổng đếm
108
+ fh, fw, _ = annotator_frame.shape
109
+ box_w, box_h = 280, 50 + len(SELECTED_CLASS_NAMES) * 28
110
+ x0, y0 = fw - box_w - 20, 20
111
+
112
+ overlay = annotator_frame.copy()
113
+ cv2.rectangle(overlay, (x0, y0), (x0 + box_w, y0 + box_h), (0, 0, 0), -1)
114
+ annotator_frame = cv2.addWeighted(overlay, 0.6, annotator_frame, 0.4, 0)
115
+
116
+ total = sum(class_counts.values())
117
+ cv2.putText(annotator_frame, f'Total: {total}', (x0 + 10, y0 + 30),
118
+ cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
119
+ for i, cls_name in enumerate(SELECTED_CLASS_NAMES):
120
+ cnt = class_counts.get(cls_name, 0)
121
+ cv2.putText(annotator_frame, f'{cls_name.capitalize()}: {cnt}',
122
+ (x0 + 10, y0 + 60 + i * 28),
123
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
124
+
125
+ return annotator_frame
126
+
127
+ output_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
128
+ sv.process_video(
129
+ source_path=video_path,
130
+ target_path=output_path,
131
+ callback=callback
132
+ )
133
+ return output_path
134
+
135
+
136
+ # Gradio UI
137
+ with gr.Blocks(title="Đếm xe qua line", theme=gr.themes.Soft()) as demo:
138
+ gr.Markdown("# 🚗 Đếm xe qua line (YOLOv8 + ByteTrack)")
139
+ gr.Markdown("Upload video, hệ thống sẽ đếm person, car, bus, truck, motorcycle khi qua đường line giữa khung hình.")
140
+
141
+ with gr.Row():
142
+ video_input = gr.Video(label="Video đầu vào")
143
+ video_output = gr.Video(label="Video đã xử lý")
144
+
145
+ btn = gr.Button("▶️ Xử lý video")
146
+ btn.click(fn=process_video, inputs=video_input, outputs=video_output)
147
+
148
+ gr.Markdown("""
149
+ ### Lưu ý
150
+ - Line đếm nằm ngang ở **giữa khung hình** (50% chiều cao)
151
+ - Chạy trên CPU miễn phí → nên dùng video **ngắn** (< 30 giây) để tránh chờ lâu
152
+ - Model: YOLOv8n (nhẹ cho CPU)
153
+ """)
154
+
155
+ if __name__ == "__main__":
156
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ ultralytics>=8.3.0
3
+ supervision[assets]>=0.24.0
4
+ opencv-python-headless>=4.8.0
5
+ numpy>=1.24.0