KPrashanth commited on
Commit
146b696
·
verified ·
1 Parent(s): 44d73d2

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -140
app.py CHANGED
@@ -1,181 +1,144 @@
1
- import os
2
  import cv2
3
  import numpy as np
4
  import gradio as gr
5
  from ultralytics import YOLO
6
 
7
- # ----------------------------
8
- # Config
9
- # ----------------------------
10
- MODEL_NAME = os.getenv("YOLO_MODEL", "yolov8n.pt")
11
- CLASS_OF_INTEREST = "person"
12
-
13
- # Danger zone: top-left, bottom-right (x, y)
14
- DANGER_ZONE = ((100, 100), (400, 400))
15
-
16
- # Inference config
17
- CONF_THRES = 0.35
18
- IMG_SIZE = 640
19
-
20
- # ----------------------------
21
  # Load model once (global)
22
- # ----------------------------
 
23
  model = YOLO(MODEL_NAME)
24
 
25
- # Build class-name -> id mapping once (YOLOv8 COCO)
26
- # For yolov8n.pt, names is dict {id: name}
27
- NAMES = model.names
28
- PERSON_CLASS_ID = None
29
- for k, v in NAMES.items():
30
- if v == CLASS_OF_INTEREST:
31
- PERSON_CLASS_ID = int(k)
32
- break
33
-
34
- if PERSON_CLASS_ID is None:
35
- raise RuntimeError("Could not find 'person' class in model.names")
36
-
37
-
38
- # ----------------------------
39
- # Helpers
40
- # ----------------------------
41
- def overlaps_zone(box_xyxy, zone):
42
- """True if box overlaps danger zone (partial overlap)."""
43
- x1, y1, x2, y2 = box_xyxy
44
  (zx1, zy1), (zx2, zy2) = zone
 
45
  overlap_x = (x1 < zx2) and (x2 > zx1)
46
  overlap_y = (y1 < zy2) and (y2 > zy1)
47
  return overlap_x and overlap_y
48
 
49
-
50
- def make_beep(sr=22050, freq=880, duration=0.25):
51
- """Return a short beep waveform for browser playback."""
52
- t = np.linspace(0, duration, int(sr * duration), endpoint=False)
53
- wave = 0.2 * np.sin(2 * np.pi * freq * t) # low volume
54
- return (sr, wave.astype(np.float32))
55
-
56
-
57
- BEEP_AUDIO = make_beep()
58
-
59
-
60
- # ----------------------------
61
- # Frame processor
62
- # ----------------------------
63
- def process_frame(frame, zone_x1, zone_y1, zone_x2, zone_y2, conf_thres):
64
  """
65
- frame: numpy array RGB from gradio
66
- returns:
67
- - annotated RGB frame
68
- - grayscale RGB frame
69
- - infrared frame (RGB)
70
- - beep audio tuple or None
71
- - status text
72
  """
73
  if frame is None:
74
- return None, None, None, None, "No frame"
75
 
76
- # Gradio gives RGB; OpenCV prefers BGR for drawing
77
- rgb = frame
78
- bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
79
 
80
- zone = ((int(zone_x1), int(zone_y1)), (int(zone_x2), int(zone_y2)))
 
 
 
 
81
 
82
- # Derived feeds
83
- gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
84
- gray_bgr = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
85
- infrared = cv2.applyColorMap(gray, cv2.COLORMAP_JET)
86
 
87
- # Draw danger zone
88
- for img in (bgr, gray_bgr, infrared):
89
- cv2.rectangle(img, zone[0], zone[1], (0, 0, 255), 2)
90
-
91
- # YOLO inference (stream=False for single image)
92
- # verbose=False keeps logs clean
93
- results = model.predict(
94
- source=bgr,
95
- imgsz=IMG_SIZE,
96
- conf=float(conf_thres),
97
- verbose=False
98
- )
99
 
100
- alert = False
101
- det_count = 0
102
 
103
- r = results[0]
104
- if r.boxes is not None and len(r.boxes) > 0:
105
- boxes = r.boxes.xyxy.cpu().numpy().astype(int)
106
- cls_ids = r.boxes.cls.cpu().numpy().astype(int)
107
- confs = r.boxes.conf.cpu().numpy()
108
 
109
- for (x1, y1, x2, y2), cid, c in zip(boxes, cls_ids, confs):
110
- if cid != PERSON_CLASS_ID:
111
- continue
112
 
113
- det_count += 1
114
- label = f"person: {c:.2f}"
 
 
115
 
116
- # draw bbox
117
- for img in (bgr, gray_bgr, infrared):
118
- cv2.rectangle(img, (x1, y1), (x2, y2), (255, 0, 0), 2)
119
- cv2.putText(img, label, (x1, max(15, y1 - 8)),
120
- cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
121
 
122
- if overlaps_zone((x1, y1, x2, y2), zone):
123
- alert = True
 
 
124
 
125
- if alert:
126
- for img in (bgr, gray_bgr, infrared):
127
- cv2.putText(img, "ALERT: Person in danger zone", (20, 45),
128
- cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 3)
129
- status = f"🚨 ALERT! persons detected: {det_count}"
130
- beep = BEEP_AUDIO
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  else:
132
- status = f"✅ OK (persons detected: {det_count})"
133
- beep = None
134
 
135
- # Convert back to RGB for gradio display
136
  out_rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
137
- out_gray = cv2.cvtColor(gray_bgr, cv2.COLOR_BGR2RGB)
138
- out_infra = cv2.cvtColor(infrared, cv2.COLOR_BGR2RGB)
139
-
140
- return out_rgb, out_gray, out_infra, beep, status
141
 
142
 
143
- # ----------------------------
144
- # Gradio UI
145
- # ----------------------------
146
- with gr.Blocks(title="YOLOv8 Danger Zone Demo") as demo:
147
  gr.Markdown(
148
- """
149
- # YOLOv8 Danger Zone Detection (Demo)
150
- - Uses browser webcam input (works on Hugging Face Spaces)
151
- - Detects **person** and triggers **alert** if they overlap the danger zone
152
- """
153
  )
154
 
155
  with gr.Row():
156
- cam = gr.Image(sources=["webcam"],streaming=True,type="numpy",label="Webcam (Input)")
157
  with gr.Column():
158
- zone_x1 = gr.Slider(0, 1280, value=DANGER_ZONE[0][0], step=1, label="Zone x1")
159
- zone_y1 = gr.Slider(0, 720, value=DANGER_ZONE[0][1], step=1, label="Zone y1")
160
- zone_x2 = gr.Slider(0, 1280, value=DANGER_ZONE[1][0], step=1, label="Zone x2")
161
- zone_y2 = gr.Slider(0, 720, value=DANGER_ZONE[1][1], step=1, label="Zone y2")
162
- conf = gr.Slider(0.05, 0.90, value=CONF_THRES, step=0.01, label="Confidence Threshold")
163
-
164
- with gr.Row():
165
- out1 = gr.Image(type="numpy", label="Color (Annotated)")
166
- out2 = gr.Image(type="numpy", label="Grayscale (Annotated)")
167
- out3 = gr.Image(type="numpy", label="Infrared (Annotated)")
168
-
169
- with gr.Row():
170
- alert_audio = gr.Audio(label="Alert Beep (plays when triggered)", autoplay=True)
171
- status = gr.Textbox(label="Status", interactive=False)
172
-
 
 
 
 
173
  cam.stream(
174
  fn=process_frame,
175
- inputs=[cam, zone_x1, zone_y1, zone_x2, zone_y2, conf],
176
- outputs=[out1, out2, out3, alert_audio, status],
177
- show_progress=False
178
  )
179
 
180
- demo.queue().launch()
181
-
 
 
1
  import cv2
2
  import numpy as np
3
  import gradio as gr
4
  from ultralytics import YOLO
5
 
6
+ # -------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  # Load model once (global)
8
+ # -------------------------
9
+ MODEL_NAME = "yolov8n.pt"
10
  model = YOLO(MODEL_NAME)
11
 
12
+ CLASS_OF_INTEREST = "person"
13
+
14
+ def is_in_danger_zone(box, zone):
15
+ """
16
+ box: (x1, y1, x2, y2)
17
+ zone: ((zx1, zy1), (zx2, zy2))
18
+ overlap logic: any partial overlap triggers True
19
+ """
20
+ x1, y1, x2, y2 = box
 
 
 
 
 
 
 
 
 
 
21
  (zx1, zy1), (zx2, zy2) = zone
22
+
23
  overlap_x = (x1 < zx2) and (x2 > zx1)
24
  overlap_y = (y1 < zy2) and (y2 > zy1)
25
  return overlap_x and overlap_y
26
 
27
+ def process_frame(frame, zx1, zy1, zx2, zy2, conf_thres):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  """
29
+ frame: numpy array (H, W, 3) from Gradio webcam (RGB)
30
+ returns: annotated frame (RGB), status markdown
 
 
 
 
 
31
  """
32
  if frame is None:
33
+ return None, "Waiting for webcam input…"
34
 
35
+ # Gradio gives RGB; OpenCV drawing expects BGR
36
+ bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
37
+ h, w = bgr.shape[:2]
38
 
39
+ # Clamp and fix zone coordinates
40
+ zx1 = int(np.clip(zx1, 0, w - 1))
41
+ zx2 = int(np.clip(zx2, 0, w - 1))
42
+ zy1 = int(np.clip(zy1, 0, h - 1))
43
+ zy2 = int(np.clip(zy2, 0, h - 1))
44
 
45
+ if zx2 < zx1:
46
+ zx1, zx2 = zx2, zx1
47
+ if zy2 < zy1:
48
+ zy1, zy2 = zy2, zy1
49
 
50
+ danger_zone = ((zx1, zy1), (zx2, zy2))
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ # Draw danger zone
53
+ cv2.rectangle(bgr, danger_zone[0], danger_zone[1], (0, 0, 255), 2)
54
 
55
+ # Run YOLO (on original RGB frame or BGR — ultralytics handles numpy arrays)
56
+ results = model.predict(source=frame, conf=float(conf_thres), verbose=False)
 
 
 
57
 
58
+ alert_triggered = False
59
+ persons_in_zone = 0
60
+ persons_total = 0
61
 
62
+ for r in results:
63
+ names = r.names
64
+ if r.boxes is None:
65
+ continue
66
 
67
+ boxes_xyxy = r.boxes.xyxy.cpu().numpy() if hasattr(r.boxes.xyxy, "cpu") else np.array(r.boxes.xyxy)
68
+ cls_ids = r.boxes.cls.cpu().numpy() if hasattr(r.boxes.cls, "cpu") else np.array(r.boxes.cls)
69
+ confs = r.boxes.conf.cpu().numpy() if hasattr(r.boxes.conf, "cpu") else np.array(r.boxes.conf)
 
 
70
 
71
+ for (x1, y1, x2, y2), cls_id, cf in zip(boxes_xyxy, cls_ids, confs):
72
+ class_name = names[int(cls_id)]
73
+ if class_name != CLASS_OF_INTEREST:
74
+ continue
75
 
76
+ persons_total += 1
77
+
78
+ x1, y1, x2, y2 = map(int, [x1, y1, x2, y2])
79
+
80
+ # Draw person bbox
81
+ cv2.rectangle(bgr, (x1, y1), (x2, y2), (255, 0, 0), 2)
82
+ label = f"{class_name}: {float(cf):.2f}"
83
+ cv2.putText(bgr, label, (x1, max(20, y1 - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
84
+
85
+ # Zone overlap check
86
+ if is_in_danger_zone((x1, y1, x2, y2), danger_zone):
87
+ alert_triggered = True
88
+ persons_in_zone += 1
89
+
90
+ if alert_triggered:
91
+ cv2.putText(
92
+ bgr,
93
+ f"ALERT! {persons_in_zone} person(s) in danger zone",
94
+ (20, 40),
95
+ cv2.FONT_HERSHEY_SIMPLEX,
96
+ 1.0,
97
+ (0, 0, 255),
98
+ 3
99
+ )
100
+ status = f"## 🔴 ALERT\n**{persons_in_zone}** person(s) inside danger zone.\n\nTotal persons detected: **{persons_total}**"
101
  else:
102
+ status = f"## SAFE\nNo person inside danger zone.\n\nTotal persons detected: **{persons_total}**"
 
103
 
104
+ # Convert back to RGB for Gradio output
105
  out_rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
106
+ return out_rgb, status
 
 
 
107
 
108
 
109
+ with gr.Blocks(title="YOLOv8 Danger Zone (Webcam)") as demo:
 
 
 
110
  gr.Markdown(
111
+ "# YOLOv8 Danger Zone Detection (Webcam)\n"
112
+ "Use your webcam, define a rectangular danger zone, and detect if any **person** enters it.\n\n"
113
+ "**Note:** On Hugging Face Spaces, server-side audio (pygame) isn’t reliable. We show a clear on-screen alert instead."
 
 
114
  )
115
 
116
  with gr.Row():
 
117
  with gr.Column():
118
+ cam = gr.Image(
119
+ label="Webcam Input",
120
+ sources=["webcam"],
121
+ type="numpy"
122
+ )
123
+ with gr.Column():
124
+ out = gr.Image(label="Annotated Output", type="numpy")
125
+ status_md = gr.Markdown("Waiting for webcam input…")
126
+
127
+ with gr.Accordion("Danger Zone Controls", open=True):
128
+ with gr.Row():
129
+ zx1 = gr.Slider(0, 1280, value=100, step=1, label="Zone X1 (left)")
130
+ zy1 = gr.Slider(0, 720, value=100, step=1, label="Zone Y1 (top)")
131
+ with gr.Row():
132
+ zx2 = gr.Slider(0, 1280, value=400, step=1, label="Zone X2 (right)")
133
+ zy2 = gr.Slider(0, 720, value=400, step=1, label="Zone Y2 (bottom)")
134
+ conf = gr.Slider(0.1, 0.9, value=0.35, step=0.05, label="Confidence Threshold")
135
+
136
+ # Stream webcam frames to backend (Gradio 5 streaming)
137
  cam.stream(
138
  fn=process_frame,
139
+ inputs=[cam, zx1, zy1, zx2, zy2, conf],
140
+ outputs=[out, status_md],
141
+ stream_every=0.1 # approx 10 fps snapshots (depends on device/network)
142
  )
143
 
144
+ demo.queue().launch()