trannam1084 commited on
Commit
1e2f3c9
·
verified ·
1 Parent(s): 6d550d5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -230
app.py CHANGED
@@ -1,120 +1,73 @@
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,169 +76,80 @@ def process_video(
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,
183
- (0, 255, 0),
184
- 2,
185
- )
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,
194
- (255, 255, 255),
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
-
 
1
  import os
2
  import tempfile
 
 
3
  import cv2
 
4
  import numpy as np
5
+ 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', 'bus', 'truck', '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, orientation="Horizontal"):
28
+ """Process video: detect objects and return annotated video."""
29
+ if video_path is None:
30
+ return None
31
+
32
+ if isinstance(video_path, dict):
33
+ video_path = video_path.get("path", video_path)
34
 
35
+ video_info = sv.VideoInfo.from_video_path(video_path)
36
+ w, h = video_info.width, video_info.height
 
37
 
38
+ is_horizontal = orientation.lower().startswith("n")
39
+ if is_horizontal:
40
+ line_pos = int(h * 0.5)
41
+ line_start = sv.Point(int(w * 0.005), line_pos)
42
+ line_end = sv.Point(int(w * 0.995), line_pos)
43
+ else:
44
+ line_pos = int(w * 0.5)
45
+ line_start = sv.Point(line_pos, int(h * 0.005))
46
+ line_end = sv.Point(line_pos, int(h * 0.995))
47
 
48
+ line_zone = sv.LineZone(start=line_start, end=line_end)
49
  byte_tracker = sv.ByteTrack(
50
  track_activation_threshold=0.25,
51
  lost_track_buffer=30,
52
  minimum_matching_threshold=0.8,
53
+ frame_rate=video_info.fps or 30,
54
+ minimum_consecutive_frames=3
55
  )
56
+ byte_tracker.reset()
57
 
58
+ previous_positions = {}
59
+ class_counts = {name: 0 for name in SELECTED_CLASS_NAMES}
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  crossed_ids = set()
61
 
62
+ def callback(frame: np.ndarray, index: int) -> np.ndarray:
63
+ nonlocal previous_positions, class_counts, crossed_ids
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ results = model(frame, verbose=False)[0]
66
+ detections = sv.Detections.from_ultralytics(results)
67
+ detections = detections[np.isin(detections.class_id, SELECTED_CLASS_IDS)]
68
+ detections = byte_tracker.update_with_detections(detections)
69
 
70
+ if detections.tracker_id is not None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  xyxy = detections.xyxy
72
  for i in range(len(detections)):
73
  tid = int(detections.tracker_id[i])
 
76
  cx = (xyxy[i, 0] + xyxy[i, 2]) / 2
77
  cy = (xyxy[i, 1] + xyxy[i, 3]) / 2
78
 
79
+ curr_coord = cy if is_horizontal else cx
 
80
  if tid in previous_positions:
81
+ prev_coord = previous_positions[tid]
82
+ if prev_coord < line_pos and curr_coord > line_pos and (tid, 'pos') not in crossed_ids:
83
+ crossed_ids.add((tid, 'pos'))
 
 
 
 
84
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
85
+ elif prev_coord > line_pos and curr_coord < line_pos and (tid, 'neg') not in crossed_ids:
86
+ crossed_ids.add((tid, 'neg'))
 
 
 
 
87
  class_counts[cls_name] = class_counts.get(cls_name, 0) + 1
88
+ previous_positions[tid] = curr_coord
 
 
 
 
 
 
 
89
 
90
+ labels = [
91
+ f"#{tid} {CLASS_NAMES_DICT[cid]} {conf:0.2f}"
92
+ for conf, cid, tid in zip(
93
+ detections.confidence, detections.class_id, detections.tracker_id
 
 
 
 
94
  )
95
+ ]
96
 
97
+ annotator_frame = frame.copy()
98
+ annotator_frame = trace_annotator.annotate(scene=annotator_frame, detections=detections)
99
+ annotator_frame = box_annotator.annotate(scene=annotator_frame, detections=detections)
100
+ annotator_frame = label_annotator.annotate(scene=annotator_frame, detections=detections, labels=labels)
101
+
102
+ line_zone.trigger(detections)
103
+ annotator_frame = line_zone_annotator.annotate(annotator_frame, line_counter=line_zone)
104
 
105
+ fh, fw, _ = annotator_frame.shape
106
  box_w, box_h = 280, 50 + len(SELECTED_CLASS_NAMES) * 28
107
+ x0, y0 = fw - box_w - 20, 20
108
 
109
+ overlay = annotator_frame.copy()
110
  cv2.rectangle(overlay, (x0, y0), (x0 + box_w, y0 + box_h), (0, 0, 0), -1)
111
+ annotator_frame = cv2.addWeighted(overlay, 0.6, annotator_frame, 0.4, 0)
112
 
113
  total = sum(class_counts.values())
114
+ cv2.putText(annotator_frame, f'Total: {total}', (x0 + 10, y0 + 30),
115
+ cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
 
 
 
 
 
 
 
116
  for i, cls_name in enumerate(SELECTED_CLASS_NAMES):
117
  cnt = class_counts.get(cls_name, 0)
118
+ cv2.putText(annotator_frame, f'{cls_name.capitalize()}: {cnt}',
119
+ (x0 + 10, y0 + 60 + i * 28),
120
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ return annotator_frame
 
 
 
 
 
 
 
 
123
 
124
+ output_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
125
+ sv.process_video(
126
+ source_path=video_path,
127
+ target_path=output_path,
128
+ callback=callback
 
 
 
 
 
129
  )
130
+ return output_path
131
 
 
 
 
 
 
 
 
132
 
133
+ with gr.Blocks(title="Object Detection", theme=gr.themes.Soft()) as demo:
134
+ gr.Markdown("# 🚗 Nhận dạng phương tiện (YOLOv8 + ByteTrack)")
135
+ gr.Markdown("Upload video, hệ thống sẽ nhận dạng phương tiện trong video.")
 
 
 
 
 
 
136
 
137
  with gr.Row():
138
+ video_input = gr.Video(label="Video input")
139
+ video_output = gr.Video(label="Video output")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
+ orientation_input = gr.Radio(
142
+ choices=["Horizontal", "Vertical"],
143
+ value="Horizontal",
144
+ label="Line orientation"
145
  )
146
 
147
+ btn = gr.Button("▶️ Process Video")
148
+ btn.click(fn=process_video, inputs=[video_input, orientation_input], outputs=video_output)
149
+
150
+ gr.Markdown("""
151
+ ### Author: Trần Hải Nam - 223332840
152
+ """)
153
 
154
  if __name__ == "__main__":
155
  demo.launch()