Spaces:
Sleeping
Sleeping
| import os | |
| import tempfile | |
| import cv2 | |
| import numpy as np | |
| import gradio as gr | |
| import supervision as sv | |
| from ultralytics import YOLO | |
| model = YOLO("yolo11n.pt") | |
| CLASS_NAMES_DICT = model.model.names | |
| SELECTED_CLASS_NAMES = ['car', 'bus', 'truck', 'motorcycle'] | |
| SELECTED_CLASS_IDS = [ | |
| {value: key for key, value in CLASS_NAMES_DICT.items()}[name] | |
| for name in SELECTED_CLASS_NAMES | |
| ] | |
| box_annotator = sv.BoxAnnotator(thickness=4) | |
| label_annotator = sv.LabelAnnotator(text_thickness=2, text_scale=1.5, text_color=sv.Color.BLACK) | |
| trace_annotator = sv.TraceAnnotator(thickness=4, trace_length=50) | |
| line_zone_annotator = sv.LineZoneAnnotator( | |
| thickness=4, text_thickness=2, text_scale=2, | |
| display_in_count=False, display_out_count=False | |
| ) | |
| def process_video(video_path, orientation="Ngang"): | |
| """Xử lý video: nhận dạng phương tiện và trả về video đã annotate.""" | |
| if video_path is None: | |
| return None | |
| if isinstance(video_path, dict): | |
| video_path = video_path.get("path", video_path) | |
| video_info = sv.VideoInfo.from_video_path(video_path) | |
| w, h = video_info.width, video_info.height | |
| ori = str(orientation).strip().lower() | |
| is_horizontal = ori.startswith("n") or ori.startswith("h") | |
| if is_horizontal: | |
| line_pos = int(h * 0.5) | |
| line_start = sv.Point(int(w * 0.005), line_pos) | |
| line_end = sv.Point(int(w * 0.995), line_pos) | |
| else: | |
| line_pos = int(w * 0.5) | |
| line_start = sv.Point(line_pos, int(h * 0.005)) | |
| line_end = sv.Point(line_pos, int(h * 0.995)) | |
| line_zone = sv.LineZone(start=line_start, end=line_end) | |
| byte_tracker = sv.ByteTrack( | |
| track_activation_threshold=0.25, | |
| lost_track_buffer=30, | |
| minimum_matching_threshold=0.8, | |
| frame_rate=video_info.fps or 30, | |
| minimum_consecutive_frames=3 | |
| ) | |
| byte_tracker.reset() | |
| previous_positions = {} | |
| class_counts = {name: 0 for name in SELECTED_CLASS_NAMES} | |
| crossed_ids = set() | |
| def callback(frame: np.ndarray, index: int) -> np.ndarray: | |
| nonlocal previous_positions, class_counts, crossed_ids | |
| results = model(frame, verbose=False)[0] | |
| detections = sv.Detections.from_ultralytics(results) | |
| detections = detections[np.isin(detections.class_id, SELECTED_CLASS_IDS)] | |
| detections = byte_tracker.update_with_detections(detections) | |
| if detections.tracker_id is not None: | |
| xyxy = detections.xyxy | |
| for i in range(len(detections)): | |
| tid = int(detections.tracker_id[i]) | |
| cls_id = int(detections.class_id[i]) | |
| cls_name = CLASS_NAMES_DICT[cls_id] | |
| cx = (xyxy[i, 0] + xyxy[i, 2]) / 2 | |
| cy = (xyxy[i, 1] + xyxy[i, 3]) / 2 | |
| curr_coord = cy if is_horizontal else cx | |
| if tid in previous_positions: | |
| prev_coord = previous_positions[tid] | |
| if prev_coord < line_pos and curr_coord > line_pos and (tid, 'pos') not in crossed_ids: | |
| crossed_ids.add((tid, 'pos')) | |
| class_counts[cls_name] = class_counts.get(cls_name, 0) + 1 | |
| elif prev_coord > line_pos and curr_coord < line_pos and (tid, 'neg') not in crossed_ids: | |
| crossed_ids.add((tid, 'neg')) | |
| class_counts[cls_name] = class_counts.get(cls_name, 0) + 1 | |
| previous_positions[tid] = curr_coord | |
| labels = [ | |
| f"#{tid} {CLASS_NAMES_DICT[cid]} {conf:0.2f}" | |
| for conf, cid, tid in zip( | |
| detections.confidence, detections.class_id, detections.tracker_id | |
| ) | |
| ] | |
| annotator_frame = frame.copy() | |
| annotator_frame = trace_annotator.annotate(scene=annotator_frame, detections=detections) | |
| annotator_frame = box_annotator.annotate(scene=annotator_frame, detections=detections) | |
| annotator_frame = label_annotator.annotate(scene=annotator_frame, detections=detections, labels=labels) | |
| line_zone.trigger(detections) | |
| annotator_frame = line_zone_annotator.annotate(annotator_frame, line_counter=line_zone) | |
| fh, fw, _ = annotator_frame.shape | |
| box_w, box_h = 280, 50 + len(SELECTED_CLASS_NAMES) * 28 | |
| x0, y0 = fw - box_w - 20, 20 | |
| overlay = annotator_frame.copy() | |
| cv2.rectangle(overlay, (x0, y0), (x0 + box_w, y0 + box_h), (0, 0, 0), -1) | |
| annotator_frame = cv2.addWeighted(overlay, 0.6, annotator_frame, 0.4, 0) | |
| total = sum(class_counts.values()) | |
| cv2.putText(annotator_frame, f'Total: {total}', (x0 + 10, y0 + 30), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) | |
| for i, cls_name in enumerate(SELECTED_CLASS_NAMES): | |
| cnt = class_counts.get(cls_name, 0) | |
| cv2.putText(annotator_frame, f'{cls_name.capitalize()}: {cnt}', | |
| (x0 + 10, y0 + 60 + i * 28), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) | |
| return annotator_frame | |
| output_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name | |
| sv.process_video( | |
| source_path=video_path, | |
| target_path=output_path, | |
| callback=callback | |
| ) | |
| return output_path | |
| with gr.Blocks(title="Object Detection", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# 🚗 Nhận dạng phương tiện (YOLOv8 + ByteTrack)") | |
| gr.Markdown("Tải lên video, hệ thống sẽ nhận dạng phương tiện trong video.") | |
| with gr.Row(): | |
| video_input = gr.Video(label="Video đầu vào") | |
| video_output = gr.Video(label="Video đã xử lý") | |
| orientation_input = gr.Radio( | |
| choices=["Ngang", "Dọc"], | |
| value="Ngang", | |
| label="Vị trí line" | |
| ) | |
| btn = gr.Button("▶️ Xử lý video") | |
| btn.click(fn=process_video, inputs=[video_input, orientation_input], outputs=video_output) | |
| gr.Markdown(""" | |
| ### Author: Trần Hải Nam - 223332840 | |
| """) | |
| if __name__ == "__main__": | |
| demo.launch() | |