File size: 5,066 Bytes
76d8244
 
 
 
 
146b696
76d8244
146b696
 
76d8244
 
146b696
 
 
 
 
 
 
 
 
76d8244
146b696
76d8244
 
 
 
146b696
76d8244
146b696
 
76d8244
 
146b696
76d8244
146b696
 
 
76d8244
146b696
 
 
 
 
76d8244
146b696
 
 
 
76d8244
146b696
76d8244
146b696
 
76d8244
146b696
 
76d8244
146b696
 
 
76d8244
146b696
 
 
 
76d8244
146b696
 
 
76d8244
146b696
 
 
 
76d8244
146b696
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76d8244
146b696
76d8244
146b696
76d8244
146b696
76d8244
 
146b696
76d8244
146b696
 
 
76d8244
 
 
 
146b696
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76d8244
 
146b696
 
 
76d8244
 
146b696
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
import cv2
import numpy as np
import gradio as gr
from ultralytics import YOLO

# -------------------------
# Load model once (global)
# -------------------------
MODEL_NAME = "yolov8n.pt"
model = YOLO(MODEL_NAME)

CLASS_OF_INTEREST = "person"

def is_in_danger_zone(box, zone):
    """

    box: (x1, y1, x2, y2)

    zone: ((zx1, zy1), (zx2, zy2))

    overlap logic: any partial overlap triggers True

    """
    x1, y1, x2, y2 = box
    (zx1, zy1), (zx2, zy2) = zone

    overlap_x = (x1 < zx2) and (x2 > zx1)
    overlap_y = (y1 < zy2) and (y2 > zy1)
    return overlap_x and overlap_y

def process_frame(frame, zx1, zy1, zx2, zy2, conf_thres):
    """

    frame: numpy array (H, W, 3) from Gradio webcam (RGB)

    returns: annotated frame (RGB), status markdown

    """
    if frame is None:
        return None, "Waiting for webcam input…"

    # Gradio gives RGB; OpenCV drawing expects BGR
    bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
    h, w = bgr.shape[:2]

    # Clamp and fix zone coordinates
    zx1 = int(np.clip(zx1, 0, w - 1))
    zx2 = int(np.clip(zx2, 0, w - 1))
    zy1 = int(np.clip(zy1, 0, h - 1))
    zy2 = int(np.clip(zy2, 0, h - 1))

    if zx2 < zx1:
        zx1, zx2 = zx2, zx1
    if zy2 < zy1:
        zy1, zy2 = zy2, zy1

    danger_zone = ((zx1, zy1), (zx2, zy2))

    # Draw danger zone
    cv2.rectangle(bgr, danger_zone[0], danger_zone[1], (0, 0, 255), 2)

    # Run YOLO (on original RGB frame or BGR — ultralytics handles numpy arrays)
    results = model.predict(source=frame, conf=float(conf_thres), verbose=False)

    alert_triggered = False
    persons_in_zone = 0
    persons_total = 0

    for r in results:
        names = r.names
        if r.boxes is None:
            continue

        boxes_xyxy = r.boxes.xyxy.cpu().numpy() if hasattr(r.boxes.xyxy, "cpu") else np.array(r.boxes.xyxy)
        cls_ids = r.boxes.cls.cpu().numpy() if hasattr(r.boxes.cls, "cpu") else np.array(r.boxes.cls)
        confs = r.boxes.conf.cpu().numpy() if hasattr(r.boxes.conf, "cpu") else np.array(r.boxes.conf)

        for (x1, y1, x2, y2), cls_id, cf in zip(boxes_xyxy, cls_ids, confs):
            class_name = names[int(cls_id)]
            if class_name != CLASS_OF_INTEREST:
                continue

            persons_total += 1

            x1, y1, x2, y2 = map(int, [x1, y1, x2, y2])

            # Draw person bbox
            cv2.rectangle(bgr, (x1, y1), (x2, y2), (255, 0, 0), 2)
            label = f"{class_name}: {float(cf):.2f}"
            cv2.putText(bgr, label, (x1, max(20, y1 - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)

            # Zone overlap check
            if is_in_danger_zone((x1, y1, x2, y2), danger_zone):
                alert_triggered = True
                persons_in_zone += 1

    if alert_triggered:
        cv2.putText(
            bgr,
            f"ALERT! {persons_in_zone} person(s) in danger zone",
            (20, 40),
            cv2.FONT_HERSHEY_SIMPLEX,
            1.0,
            (0, 0, 255),
            3
        )
        status = f"## 🔴 ALERT\n**{persons_in_zone}** person(s) inside danger zone.\n\nTotal persons detected: **{persons_total}**"
    else:
        status = f"## ✅ SAFE\nNo person inside danger zone.\n\nTotal persons detected: **{persons_total}**"

    # Convert back to RGB for Gradio output
    out_rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
    return out_rgb, status


with gr.Blocks(title="YOLOv8 Danger Zone (Webcam)") as demo:
    gr.Markdown(
        "# YOLOv8 Danger Zone Detection (Webcam)\n"
        "Use your webcam, define a rectangular danger zone, and detect if any **person** enters it.\n\n"
        "**Note:** On Hugging Face Spaces, server-side audio (pygame) isn’t reliable. We show a clear on-screen alert instead."
    )

    with gr.Row():
        with gr.Column():
            cam = gr.Image(
                label="Webcam Input",
                sources=["webcam"],
                type="numpy"
            )
        with gr.Column():
            out = gr.Image(label="Annotated Output", type="numpy")
            status_md = gr.Markdown("Waiting for webcam input…")

    with gr.Accordion("Danger Zone Controls", open=True):
        with gr.Row():
            zx1 = gr.Slider(0, 1280, value=100, step=1, label="Zone X1 (left)")
            zy1 = gr.Slider(0, 720, value=100, step=1, label="Zone Y1 (top)")
        with gr.Row():
            zx2 = gr.Slider(0, 1280, value=400, step=1, label="Zone X2 (right)")
            zy2 = gr.Slider(0, 720, value=400, step=1, label="Zone Y2 (bottom)")
        conf = gr.Slider(0.1, 0.9, value=0.35, step=0.05, label="Confidence Threshold")

    # Stream webcam frames to backend (Gradio 5 streaming)
    cam.stream(
        fn=process_frame,
        inputs=[cam, zx1, zy1, zx2, zy2, conf],
        outputs=[out, status_md],
        stream_every=0.1  # approx 10 fps snapshots (depends on device/network)
    )

demo.queue().launch()