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()