File size: 6,055 Bytes
dfd3880
 
 
5897aa9
1e2f3c9
dfd3880
 
 
064f359
5897aa9
1e2f3c9
 
dfd3880
1e2f3c9
 
dfd3880
 
1e2f3c9
 
 
 
 
 
 
5897aa9
dfd3880
d26187e
7198c83
1e2f3c9
 
 
 
 
5897aa9
1e2f3c9
 
5808aa0
623838a
 
1e2f3c9
 
 
 
 
 
 
 
dfd3880
1e2f3c9
dfd3880
 
 
 
1e2f3c9
 
dfd3880
1e2f3c9
dfd3880
1e2f3c9
 
5897aa9
 
1e2f3c9
d26187e
dfd3880
1e2f3c9
 
 
 
dfd3880
1e2f3c9
dfd3880
 
 
 
 
 
 
 
1e2f3c9
5897aa9
1e2f3c9
 
 
dfd3880
1e2f3c9
 
dfd3880
1e2f3c9
dfd3880
1e2f3c9
 
 
 
dfd3880
1e2f3c9
5897aa9
1e2f3c9
 
 
 
 
 
 
dfd3880
1e2f3c9
5897aa9
1e2f3c9
dfd3880
1e2f3c9
dfd3880
1e2f3c9
dfd3880
 
1e2f3c9
 
dfd3880
 
1e2f3c9
 
 
5897aa9
1e2f3c9
5897aa9
1e2f3c9
 
 
 
 
5897aa9
1e2f3c9
5c15472
dfd3880
1e2f3c9
 
7198c83
dfd3880
5897aa9
7198c83
 
dfd3880
1e2f3c9
7198c83
 
 
dfd3880
 
7198c83
d26187e
1e2f3c9
 
d26187e
1e2f3c9
dfd3880
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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()