new
Browse files
app.py
CHANGED
@@ -135,6 +135,10 @@ def process_webcam(state):
|
|
135 |
if not cap.isOpened():
|
136 |
return None, "Error: Could not open webcam.", None
|
137 |
|
|
|
|
|
|
|
|
|
138 |
GAZE_STABILITY_THRESHOLD = 0.5
|
139 |
TIME_THRESHOLD = 15
|
140 |
BLINK_RATE_THRESHOLD = 1
|
@@ -172,6 +176,7 @@ def process_webcam(state):
|
|
172 |
"HEAD_STABILITY_THRESHOLD": HEAD_STABILITY_THRESHOLD,
|
173 |
"log_output": log_output
|
174 |
}
|
|
|
175 |
|
176 |
# Extract state variables
|
177 |
cap = state["cap"]
|
@@ -184,105 +189,118 @@ def process_webcam(state):
|
|
184 |
|
185 |
# Capture frame
|
186 |
ret, frame = cap.read()
|
187 |
-
if not ret:
|
188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
189 |
|
190 |
# Process frame
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
smoothed_head = smooth_values(head_history, head_pose)
|
208 |
-
smoothed_ear = smooth_values(ear_history, ear)
|
209 |
-
if smoothed_ear >= blink_detector.EAR_THRESHOLD:
|
210 |
-
cv2.drawMarker(frame, left_iris, (0, 255, 0), markerType=cv2.MARKER_CROSS, markerSize=10, thickness=2)
|
211 |
-
cv2.drawMarker(frame, right_iris, (0, 255, 0), markerType=cv2.MARKER_CROSS, markerSize=10, thickness=2)
|
212 |
-
|
213 |
-
# Add metrics to frame
|
214 |
-
cv2.putText(frame, f"Gaze H: {smoothed_gaze[0]:.2f}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
215 |
-
cv2.putText(frame, f"Gaze V: {smoothed_gaze[1]:.2f}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
216 |
-
cv2.putText(frame, f"Head Pose: {smoothed_head:.2f}", (10, 120), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
217 |
-
cv2.putText(frame, f"EAR: {smoothed_ear:.2f}", (10, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
218 |
-
|
219 |
-
# Check for gaze stability
|
220 |
-
if len(gaze_history) > 1:
|
221 |
-
gaze_diff = np.sqrt(np.sum((smoothed_gaze - gaze_history[-2])**2))
|
222 |
-
if gaze_diff < state["GAZE_STABILITY_THRESHOLD"]:
|
223 |
-
if state["stable_gaze_time"] == 0:
|
224 |
-
state["stable_gaze_time"] = current_time
|
225 |
else:
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
else:
|
235 |
-
state["
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
state["log_output"] = "\n".join(log_lines)
|
281 |
-
|
282 |
-
# Convert from BGR to RGB for Gradio
|
283 |
-
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
284 |
-
|
285 |
-
return state, state["log_output"], frame_rgb
|
286 |
|
287 |
def create_webcam_interface():
|
288 |
log_output = gr.Textbox(label="Gaze Tracking Log", lines=10)
|
|
|
135 |
if not cap.isOpened():
|
136 |
return None, "Error: Could not open webcam.", None
|
137 |
|
138 |
+
# Try to set webcam properties for better performance
|
139 |
+
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
|
140 |
+
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
|
141 |
+
|
142 |
GAZE_STABILITY_THRESHOLD = 0.5
|
143 |
TIME_THRESHOLD = 15
|
144 |
BLINK_RATE_THRESHOLD = 1
|
|
|
176 |
"HEAD_STABILITY_THRESHOLD": HEAD_STABILITY_THRESHOLD,
|
177 |
"log_output": log_output
|
178 |
}
|
179 |
+
return state, "Initializing webcam...", None
|
180 |
|
181 |
# Extract state variables
|
182 |
cap = state["cap"]
|
|
|
189 |
|
190 |
# Capture frame
|
191 |
ret, frame = cap.read()
|
192 |
+
if not ret or frame is None:
|
193 |
+
# Try to reinitialize the camera if frame capture fails
|
194 |
+
cap.release()
|
195 |
+
cap = cv2.VideoCapture(0)
|
196 |
+
if not cap.isOpened():
|
197 |
+
return state, log_output + "\nError: Could not read from webcam.", None
|
198 |
+
state["cap"] = cap
|
199 |
+
ret, frame = cap.read()
|
200 |
+
if not ret or frame is None:
|
201 |
+
return state, log_output + "\nError: Failed to capture frame after reinitialization.", None
|
202 |
|
203 |
# Process frame
|
204 |
+
try:
|
205 |
+
head_pose_gaze, gaze_h, gaze_v = gaze_predictor.predict_gaze(frame)
|
206 |
+
current_gaze = np.array([gaze_h, gaze_v])
|
207 |
+
smoothed_gaze = smooth_values(gaze_history, current_gaze)
|
208 |
+
|
209 |
+
ear, left_eye, right_eye, head_pose, left_iris, right_iris = blink_detector.detect_blinks(frame)
|
210 |
+
|
211 |
+
# Update display and logs
|
212 |
+
current_time = time.time()
|
213 |
+
logs = []
|
214 |
+
|
215 |
+
if ear is None:
|
216 |
+
cv2.putText(frame, "No face detected", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
|
217 |
+
smoothed_head = smooth_values(head_history, None)
|
218 |
+
smoothed_ear = smooth_values(ear_history, None)
|
219 |
+
logs.append("No face detected")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
220 |
else:
|
221 |
+
smoothed_head = smooth_values(head_history, head_pose)
|
222 |
+
smoothed_ear = smooth_values(ear_history, ear)
|
223 |
+
if smoothed_ear >= blink_detector.EAR_THRESHOLD:
|
224 |
+
cv2.drawMarker(frame, left_iris, (0, 255, 0), markerType=cv2.MARKER_CROSS, markerSize=10, thickness=2)
|
225 |
+
cv2.drawMarker(frame, right_iris, (0, 255, 0), markerType=cv2.MARKER_CROSS, markerSize=10, thickness=2)
|
226 |
+
|
227 |
+
# Add metrics to frame
|
228 |
+
cv2.putText(frame, f"Gaze H: {smoothed_gaze[0]:.2f}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
229 |
+
cv2.putText(frame, f"Gaze V: {smoothed_gaze[1]:.2f}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
230 |
+
cv2.putText(frame, f"Head Pose: {smoothed_head:.2f}", (10, 120), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
231 |
+
cv2.putText(frame, f"EAR: {smoothed_ear:.2f}", (10, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
232 |
+
|
233 |
+
# Check for gaze stability
|
234 |
+
if len(gaze_history) > 1:
|
235 |
+
gaze_diff = np.sqrt(np.sum((smoothed_gaze - gaze_history[-2])**2))
|
236 |
+
if gaze_diff < state["GAZE_STABILITY_THRESHOLD"]:
|
237 |
+
if state["stable_gaze_time"] == 0:
|
238 |
+
state["stable_gaze_time"] = current_time
|
239 |
+
else:
|
240 |
+
state["stable_gaze_time"] = 0
|
241 |
+
|
242 |
+
# Check for head stability
|
243 |
+
if len(head_history) > 1 and head_pose is not None:
|
244 |
+
head_diff = abs(smoothed_head - head_history[-2])
|
245 |
+
if head_diff < state["HEAD_STABILITY_THRESHOLD"]:
|
246 |
+
if state["stable_head_time"] == 0:
|
247 |
+
state["stable_head_time"] = current_time
|
248 |
+
else:
|
249 |
+
state["stable_head_time"] = 0
|
250 |
+
|
251 |
+
# Check for eye closure
|
252 |
+
if ear is not None and smoothed_ear < blink_detector.EAR_THRESHOLD:
|
253 |
+
if state["eye_closed_time"] == 0:
|
254 |
+
state["eye_closed_time"] = current_time
|
255 |
+
elif current_time - state["eye_closed_time"] > state["EYE_CLOSURE_THRESHOLD"]:
|
256 |
+
cv2.putText(frame, "Eyes Closed", (10, 210), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
|
257 |
+
logs.append("Eyes have been closed for an extended period")
|
258 |
else:
|
259 |
+
if state["eye_closed_time"] > 0 and current_time - state["eye_closed_time"] < 0.5:
|
260 |
+
state["blink_count"] += 1
|
261 |
+
logs.append("Blink detected")
|
262 |
+
state["eye_closed_time"] = 0
|
263 |
+
|
264 |
+
elapsed_seconds = current_time - state["start_time"]
|
265 |
+
elapsed_minutes = elapsed_seconds / 60
|
266 |
+
blink_rate = state["blink_count"] / elapsed_minutes if elapsed_minutes > 0 else 0
|
267 |
+
cv2.putText(frame, f"Blink Rate: {blink_rate:.1f}/min", (10, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
268 |
+
logs.append(f"Blink rate: {blink_rate:.1f}/min")
|
269 |
+
|
270 |
+
# Check for unconscious state
|
271 |
+
unconscious_conditions = [
|
272 |
+
state["stable_gaze_time"] > 0 and current_time - state["stable_gaze_time"] > state["TIME_THRESHOLD"],
|
273 |
+
blink_rate < state["BLINK_RATE_THRESHOLD"] and elapsed_minutes > 1,
|
274 |
+
state["eye_closed_time"] > 0 and current_time - state["eye_closed_time"] > state["EYE_CLOSURE_THRESHOLD"],
|
275 |
+
state["stable_head_time"] > 0 and current_time - state["stable_head_time"] > state["TIME_THRESHOLD"]
|
276 |
+
]
|
277 |
+
|
278 |
+
if sum(unconscious_conditions) >= 2:
|
279 |
+
cv2.putText(frame, "Unconscious Detected", (10, 270), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
|
280 |
+
state["is_unconscious"] = True
|
281 |
+
logs.append("WARNING: Possible unconscious state detected!")
|
282 |
+
else:
|
283 |
+
state["is_unconscious"] = False
|
284 |
+
|
285 |
+
# Update log output with latest information
|
286 |
+
logs.append(f"Gaze: ({smoothed_gaze[0]:.2f}, {smoothed_gaze[1]:.2f}) | Head: {smoothed_head:.2f} | EAR: {smoothed_ear:.2f}")
|
287 |
+
log_text = "\n".join(logs)
|
288 |
+
|
289 |
+
# Keep log_output to a reasonable size
|
290 |
+
log_lines = log_output.split("\n") if log_output else []
|
291 |
+
log_lines.append(log_text)
|
292 |
+
if len(log_lines) > 20: # Keep only last 20 entries
|
293 |
+
log_lines = log_lines[-20:]
|
294 |
+
state["log_output"] = "\n".join(log_lines)
|
295 |
+
|
296 |
+
# Convert from BGR to RGB for Gradio
|
297 |
+
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
298 |
+
|
299 |
+
return state, state["log_output"], frame_rgb
|
300 |
+
|
301 |
+
except Exception as e:
|
302 |
+
error_msg = f"Error processing frame: {str(e)}"
|
303 |
+
return state, log_output + "\n" + error_msg, None
|
|
|
|
|
|
|
|
|
|
|
|
|
304 |
|
305 |
def create_webcam_interface():
|
306 |
log_output = gr.Textbox(label="Gaze Tracking Log", lines=10)
|