shun-ren commited on
Commit
75c21c4
·
1 Parent(s): 00b925a

Fix model path

Browse files
Files changed (1) hide show
  1. app.py +238 -93
app.py CHANGED
@@ -1,110 +1,255 @@
 
1
  from ultralytics import YOLO
2
  from PIL import Image
3
  import numpy as np
4
  import cv2
5
  import gradio as gr
6
-
7
- # Load your trained YOLOv8 model (path to best weights)
8
- model = YOLO('results/yolov8n_KD_triple/weights/best.pt') #triple kd trained model
9
-
10
- # Predict function for YOLOv8; takes a PIL image, runs detection, and returns annotated image plus result string
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  def predict_yolov8(img: Image.Image):
12
-
13
- # Convert PIL image to RGB numpy array for model input
14
- img_np = np.array(img.convert('RGB'))
15
-
16
- # Run YOLOv8 prediction on array
 
 
 
 
 
 
 
 
 
 
 
17
  results = model.predict(img_np)
18
 
19
- img_draw = img_np.copy() # Copy for drawing
20
- preds_info = [] # Store detection details
21
 
 
22
  for box in results[0].boxes:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- # Extract bounding box, class, confidence
25
- x1, y1, x2, y2 = [int(v) for v in box.xyxy.squeeze().tolist()]
26
- class_id = int(box.cls.cpu().item())
27
- conf = float(box.conf.cpu().item())
28
- label = f"{model.model.names[class_id]} {conf:.2f}"
29
-
30
- # Draw bbox and label onto image
31
- cv2.rectangle(img_draw, (x1, y1), (x2, y2), (0,255,0), 2)
32
- cv2.putText(img_draw, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (36,255,12), 2)
33
  preds_info.append({
34
- 'bbox': [x1, y1, x2, y2],
35
- 'class': model.model.names[class_id],
36
- 'confidence': round(conf, 2)
37
  })
38
 
39
- out_img = Image.fromarray(img_draw) # Create output PIL image
40
-
41
- # Summarize results as readable string
42
- result_str = "\n".join([
43
- f"[{p['class']}] {p['bbox']}, conf={p['confidence']}"
44
- for p in preds_info
45
- ]) or "No detections"
46
  return out_img, result_str
47
 
48
-
49
-
50
- # Build Gradio UI with two tabs: image upload and webcam
51
- with gr.Blocks(theme=gr.themes.Soft(), css="footer {display: none !important;}", fill_height=True, title="YOLOv8 Detection Demo") as demo:
52
-
53
- gr.Markdown("# YOLOv8 Detection + Classification Demo")
54
-
55
- # Tab 1: Allow user to upload image and run detection
56
- with gr.Tab("Image Upload"):
57
-
58
- with gr.Row():
59
-
60
- with gr.Column(scale=1):
61
- image_input = gr.Image(type="pil", label="Upload Image", sources=["upload"])
62
- with gr.Column(scale=1):
63
- image_output = gr.Image(type="pil", label="Detections")
64
-
65
- with gr.Row():
66
- with gr.Column(scale=2):
67
- results_output = gr.Textbox(label="Detection Results", lines=6, max_lines=20, scale=1)
68
-
69
- btn = gr.Button("Detect")
70
- btn.click(
71
- fn=predict_yolov8,
72
- inputs=image_input,
73
- outputs=[image_output, results_output]
74
- )
75
-
76
- # Tab 2: Allow detection from webcam input
77
- with gr.Tab("Webcam"):
78
-
79
- webcam_input = gr.Image(type="pil", label="Webcam", sources=["webcam"])
80
-
81
- webcam_output = gr.Image(type="pil", label="Detections")
82
-
83
- webcam_results = gr.Textbox(label="Detection Results")
84
-
85
- webcam_btn = gr.Button("Detect")
86
- webcam_btn.click(
87
- fn=predict_yolov8,
88
- inputs=webcam_input,
89
- outputs=[webcam_output, webcam_results]
90
- )
91
-
92
- # Tab 3: Live Feed
93
- with gr.Tab("Live Feed"):
94
-
95
- webcam_input = gr.Image(type="pil", label="Live Feed", sources=["webcam"])
96
-
97
- webcam_output = gr.Image(type="pil", label="Detections")
98
-
99
- webcam_results = gr.Textbox(label="Detection Results")
100
-
101
- webcam_btn = gr.Button("Detect")
102
- webcam_btn.click(
103
- fn=predict_yolov8,
104
- inputs=webcam_input,
105
- outputs=[webcam_output, webcam_results]
106
- )
107
-
108
- # Launch Gradio app
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  if __name__ == "__main__":
110
- demo.launch()
 
1
+ # app.py -- YOLOv8 live IP camera with threaded capture + motion throttling
2
  from ultralytics import YOLO
3
  from PIL import Image
4
  import numpy as np
5
  import cv2
6
  import gradio as gr
7
+ import time
8
+ import threading
9
+
10
+ # -------------------------
11
+ # Model + IP camera config
12
+ # -------------------------
13
+ model = YOLO('/Paper-detection-model/best.pt') # your trained weights
14
+ ip_url = None # change to your IP webcam URL
15
+
16
+ # -------------------------
17
+ # Shared state (thread-safe-ish)
18
+ # -------------------------
19
+ cap = None
20
+ prev_gray = None
21
+ latest_frame = None # PIL image (annotated) to show in UI
22
+ latest_result = "Waiting..." # text summary
23
+ stop_flag = False
24
+ camera_thread_obj = None
25
+
26
+ # Tuning params (change these to adjust responsiveness / CPU)
27
+ MOTION_THRESHOLD = 50 # number of changed pixels to consider motion
28
+ COOLDOWN_SEC = 1.0 # min seconds between YOLO runs
29
+ RESIZE_TO = (640, 360) # inference size used before passing to model (smaller -> faster)
30
+ POLL_INTERVAL = 0.4 # seconds between UI polls (gr.Timer interval)
31
+ SLEEP_BETWEEN_READS = 0.02 # small sleep inside camera thread to avoid tight loop
32
+
33
+
34
+
35
+ # -------------------------
36
+ # Inference helper (robust to None)
37
+ # -------------------------
38
  def predict_yolov8(img: Image.Image):
39
+ """
40
+ Accepts a PIL.Image or None. Returns (PIL annotated image or placeholder, string result).
41
+ """
42
+ if img is None:
43
+ # return placeholder
44
+ placeholder = Image.new("RGB", RESIZE_TO, (0, 0, 0))
45
+ return placeholder, "No image"
46
+
47
+ try:
48
+ img_np = np.array(img.convert('RGB'))
49
+ except Exception as e:
50
+ placeholder = Image.new("RGB", RESIZE_TO, (0, 0, 0))
51
+ return placeholder, f"Bad image: {e}"
52
+
53
+ # Run YOLO inference (batch size 1)
54
+ # NOTE: if your model.predict(...) supports stream/inference kwargs to reduce overhead you can pass them.
55
  results = model.predict(img_np)
56
 
57
+ img_draw = img_np.copy()
58
+ preds_info = []
59
 
60
+ # results[0].boxes may be empty
61
  for box in results[0].boxes:
62
+ # x1, y1, x2, y2 (float) -> int
63
+ xy = box.xyxy.squeeze().tolist()
64
+ if isinstance(xy[0], list): # handle edge-cases
65
+ x1, y1, x2, y2 = [int(v) for v in xy[0]]
66
+ else:
67
+ x1, y1, x2, y2 = [int(v) for v in xy]
68
+
69
+ class_id = int(box.cls.cpu().item()) if hasattr(box, "cls") else int(box.cls)
70
+ conf = float(box.conf.cpu().item()) if hasattr(box, "conf") else float(box.conf)
71
+ label_text = f"{model.model.names[class_id]} {conf:.2f}"
72
+
73
+ # Draw rectangle + label
74
+ cv2.rectangle(img_draw, (x1, y1), (x2, y2), (0, 255, 0), 2)
75
+ cv2.putText(img_draw, label_text, (x1, max(15, y1 - 10)),
76
+ cv2.FONT_HERSHEY_SIMPLEX, 0.45, (36, 255, 12), 2)
77
 
 
 
 
 
 
 
 
 
 
78
  preds_info.append({
79
+ "bbox": [x1, y1, x2, y2],
80
+ "class": model.model.names[class_id],
81
+ "confidence": round(conf, 2)
82
  })
83
 
84
+ out_img = Image.fromarray(img_draw)
85
+ if preds_info:
86
+ result_str = "\n".join([f"[{p['class']}] {p['bbox']}, conf={p['confidence']}" for p in preds_info])
87
+ else:
88
+ result_str = "No detections"
 
 
89
  return out_img, result_str
90
 
91
+ # -------------------------
92
+ # Camera thread: reads frames, detects motion, runs YOLO + updates shared state
93
+ # -------------------------
94
+ def camera_thread():
95
+ global cap, prev_gray, latest_frame, latest_result, stop_flag
96
+
97
+ try:
98
+ cap = cv2.VideoCapture(ip_url)
99
+ except Exception as e:
100
+ latest_frame = Image.new("RGB", RESIZE_TO, (0, 0, 0))
101
+ latest_result = f"Failed to open camera: {e}"
102
+ return
103
+
104
+ # warm-up read
105
+ time.sleep(0.8)
106
+ ret, frame = cap.read()
107
+ if not ret or frame is None:
108
+ latest_frame = Image.new("RGB", RESIZE_TO, (0, 0, 0))
109
+ latest_result = "Camera opened but no frames received"
110
+ cap.release()
111
+ cap = None
112
+ return
113
+
114
+ prev_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
115
+ last_trigger = 0.0
116
+
117
+ while not stop_flag:
118
+ ret, frame = cap.read()
119
+ if not ret or frame is None:
120
+ # keep trying
121
+ time.sleep(0.5)
122
+ continue
123
+
124
+ # motion detection (fast grayscale diff)
125
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
126
+ diff = cv2.absdiff(prev_gray, gray)
127
+ thresh = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[1]
128
+ motion_level = int(cv2.countNonZero(thresh))
129
+ prev_gray = gray
130
+
131
+ if motion_level < MOTION_THRESHOLD:
132
+ # no meaningful motion; skip heavy processing
133
+ time.sleep(SLEEP_BETWEEN_READS)
134
+ continue
135
+
136
+ # throttle YOLO inference
137
+ now = time.time()
138
+ if now - last_trigger < COOLDOWN_SEC:
139
+ time.sleep(SLEEP_BETWEEN_READS)
140
+ continue
141
+ last_trigger = now
142
+
143
+ # prepare frame for model (resize -> PIL)
144
+ pil_frame = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).resize(RESIZE_TO)
145
+
146
+ # run inference (this is the heavy op)
147
+ annotated, result_str = predict_yolov8(pil_frame)
148
+
149
+ # update shared state for UI polling
150
+ latest_frame = annotated
151
+ latest_result = f"{result_str} (motion={motion_level})"
152
+
153
+ # tiny sleep to yield CPU
154
+ time.sleep(0.005)
155
+
156
+ # cleanup when stop_flag set
157
+ if cap:
158
+ cap.release()
159
+ cap = None
160
+
161
+ # -------------------------
162
+ # Control functions for Gradio
163
+ # -------------------------
164
+ def start_live(ip):
165
+ global stop_flag, camera_thread_obj, latest_result, latest_frame, ip_url
166
+
167
+ ip_url = f"http://{ip}:8080/video" # Construct the full URL with the provided IP
168
+
169
+ # Try to open the connection and handle errors
170
+ try:
171
+ cap = cv2.VideoCapture(ip_url)
172
+ if not cap.isOpened():
173
+ raise Exception("Failed to connect to the camera.")
174
+
175
+ # If connected successfully, start the camera thread
176
+ if camera_thread_obj and camera_thread_obj.is_alive():
177
+ return "Already running"
178
+
179
+ stop_flag = False
180
+ latest_result = "Starting camera..."
181
+ camera_thread_obj = threading.Thread(target=camera_thread, daemon=True)
182
+ camera_thread_obj.start()
183
+ return "Live feed started"
184
+ except Exception as e:
185
+ # Handle connection failure
186
+ latest_result = f"Failed to connect: {str(e)}"
187
+ return latest_result
188
+
189
+
190
+ def stop_live():
191
+ global stop_flag, camera_thread_obj
192
+ stop_flag = True
193
+ # camera thread will release the capture and exit
194
+ return "Stopped"
195
+
196
+ def get_latest():
197
+ """Called from UI timer to fetch latest annotated image + text."""
198
+ if latest_frame is None:
199
+ # placeholder when nothing yet
200
+ placeholder = Image.new("RGB", RESIZE_TO, (20, 20, 20))
201
+ return placeholder, latest_result
202
+ return latest_frame, latest_result
203
+
204
+ # -------------------------
205
+ # Gradio UI
206
+ # -------------------------
207
+ css = "footer {display: none !important;}"
208
+
209
+ with gr.Blocks(theme=gr.themes.Soft(), css=css, title="YOLOv8 Detection Demo") as demo:
210
+ gr.Markdown("# YOLOv8 Detection + Live IP Camera")
211
+
212
+ with gr.Tabs():
213
+
214
+ with gr.Tab("Image Upload"):
215
+ with gr.Row():
216
+ input_img = gr.Image(type="pil", label="Upload Image")
217
+ out_img = gr.Image(type="pil", label="Detections")
218
+ results_box = gr.Textbox(label="Detection Results")
219
+ btn = gr.Button("Detect")
220
+ btn.click(predict_yolov8, inputs=input_img, outputs=[out_img, results_box])
221
+
222
+ with gr.Tab("Webcam"):
223
+ webcam_input = gr.Image(type="pil", label="Webcam (browser)")
224
+ webcam_out = gr.Image(type="pil", label="Detections")
225
+ webcam_text = gr.Textbox(label="Detection Results")
226
+ webcam_btn = gr.Button("Detect")
227
+ webcam_btn.click(predict_yolov8, inputs=webcam_input, outputs=[webcam_out, webcam_text])
228
+
229
+ with gr.Tab("Live IP Camera"):
230
+ ip_input = gr.Textbox(label="IP Camera URL", placeholder="Enter ip address here")
231
+ live_img = gr.Image(type="pil", label="Live Detection", height=480)
232
+ live_txt = gr.Textbox(label="YOLO Results")
233
+ start_btn = gr.Button("Start Live")
234
+ stop_btn = gr.Button("Stop Live")
235
+
236
+ start_btn.click(
237
+ fn=start_live,
238
+ inputs=ip_input,
239
+ outputs=live_txt
240
+ )
241
+
242
+ stop_btn.click(stop_live, outputs=live_txt)
243
+
244
+ # Poll for latest annotated frame every POLL_INTERVAL seconds
245
+ timer = gr.Timer(POLL_INTERVAL)
246
+ timer.tick(
247
+ fn=get_latest,
248
+ inputs=None,
249
+ outputs=[live_img, live_txt]
250
+ )
251
+
252
+
253
+ # Launch
254
  if __name__ == "__main__":
255
+ demo.launch()