Spaces:
Sleeping
Sleeping
File size: 9,639 Bytes
496c8b9 |
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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
import cv2
import dlib
import numpy as np
from scipy.spatial import distance as dist # For EAR calculation
# Constants for detection
# These values might need fine-tuning
EYEBROW_TO_EYE_VERTICAL_DISTANCE_INCREASE_FACTOR = 0.15 # Factor for eyebrow-to-eye distance increase for "No"
CALIBRATION_FRAMES = 60 # Number of frames for initial calibration
EAR_THRESHOLD = 0.20 # Eye Aspect Ratio threshold for eye closure (Yes)
# Path to the dlib shape predictor model file
DLIB_SHAPE_PREDICTOR_PATH = "shape_predictor_68_face_landmarks.dat"
# Display states
STATE_YES = "Yes"
STATE_NO = "No"
STATE_NORMAL = "Normal"
STATE_CALIBRATING = "Calibrating..."
# Initialize dlib's face detector and facial landmark predictor
try:
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(DLIB_SHAPE_PREDICTOR_PATH)
except RuntimeError as e:
print(f"[ERROR] Failed to load dlib model: {e}")
print(f"Please download '{DLIB_SHAPE_PREDICTOR_PATH}' and place it near the script.")
exit()
# Landmark indices for eyes (used for EAR)
# Dlib's left_eye (user's left) are points 42-47 (0-indexed in predictor)
# Dlib's right_eye (user's right) are points 36-41
(user_L_eye_indices_start, user_L_eye_indices_end) = (42, 48)
(user_R_eye_indices_start, user_R_eye_indices_end) = (36, 42)
# Landmark indices for top of eyes (for No detection)
# User's Left eye top: dlib points 43, 44
# User's Right eye top: dlib points 37, 38
user_L_eye_top_indices = [43, 44]
user_R_eye_top_indices = [37, 38]
# Landmark indices for eyebrows (for No detection)
# User's Left eyebrow: dlib points 22-26. We'll average points 23, 24, 25.
user_L_eyebrow_y_calc_indices = range(23, 26)
# User's Right eyebrow: dlib points 17-21. We'll average points 18, 19, 20.
user_R_eyebrow_y_calc_indices = range(18, 21)
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("[ERROR] Cannot open webcam.")
exit()
# Calibration variables
calibration_counter = 0
# For eyebrows
normal_user_L_eyebrow_y_avg = 0
normal_user_R_eyebrow_y_avg = 0
calibration_data_user_L_eyebrow_y = []
calibration_data_user_R_eyebrow_y = []
# For top of eyes
normal_user_L_eye_top_y_avg = 0
normal_user_R_eye_top_y_avg = 0
calibration_data_user_L_eye_top_y = []
calibration_data_user_R_eye_top_y = []
# For calibrated distances
normal_dist_L_eyebrow_to_eye = 0
normal_dist_R_eyebrow_to_eye = 0
def get_landmark_point(landmarks, index):
return (landmarks.part(index).x, landmarks.part(index).y)
def eye_aspect_ratio(eye_pts):
A = dist.euclidean(eye_pts[1], eye_pts[5])
B = dist.euclidean(eye_pts[2], eye_pts[4])
C = dist.euclidean(eye_pts[0], eye_pts[3])
ear_val = (A + B) / (2.0 * C)
return ear_val
print("[INFO] Calibration started. Please look at the camera with a normal expression...")
current_state = STATE_CALIBRATING
while True:
ret, frame = cap.read()
if not ret:
print("[ERROR] Failed to grab frame from webcam.")
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = detector(gray)
if calibration_counter >= CALIBRATION_FRAMES:
current_state = STATE_NORMAL
for face in faces:
landmarks = predictor(gray, face)
# --- Calculate current eyebrow Y positions ---
user_L_eyebrow_current_y_pts = [landmarks.part(i).y for i in user_L_eyebrow_y_calc_indices]
current_user_L_eyebrow_y_avg = np.mean(user_L_eyebrow_current_y_pts) if user_L_eyebrow_current_y_pts else 0
user_R_eyebrow_current_y_pts = [landmarks.part(i).y for i in user_R_eyebrow_y_calc_indices]
current_user_R_eyebrow_y_avg = np.mean(user_R_eyebrow_current_y_pts) if user_R_eyebrow_current_y_pts else 0
# --- Calculate current top of eye Y positions ---
user_L_eye_top_current_y_pts = [landmarks.part(i).y for i in user_L_eye_top_indices]
current_user_L_eye_top_y_avg = np.mean(user_L_eye_top_current_y_pts) if user_L_eye_top_current_y_pts else 0
user_R_eye_top_current_y_pts = [landmarks.part(i).y for i in user_R_eye_top_indices]
current_user_R_eye_top_y_avg = np.mean(user_R_eye_top_current_y_pts) if user_R_eye_top_current_y_pts else 0
# --- Eye Aspect Ratio for "Yes" detection ---
user_L_eye_all_pts = np.array([get_landmark_point(landmarks, i) for i in range(user_L_eye_indices_start, user_L_eye_indices_end)], dtype="int")
user_R_eye_all_pts = np.array([get_landmark_point(landmarks, i) for i in range(user_R_eye_indices_start, user_R_eye_indices_end)], dtype="int")
left_ear = eye_aspect_ratio(user_L_eye_all_pts)
right_ear = eye_aspect_ratio(user_R_eye_all_pts)
avg_ear = (left_ear + right_ear) / 2.0
# --- Calibration Phase ---
if calibration_counter < CALIBRATION_FRAMES:
current_state = STATE_CALIBRATING
calibration_data_user_L_eyebrow_y.append(current_user_L_eyebrow_y_avg)
calibration_data_user_R_eyebrow_y.append(current_user_R_eyebrow_y_avg)
calibration_data_user_L_eye_top_y.append(current_user_L_eye_top_y_avg)
calibration_data_user_R_eye_top_y.append(current_user_R_eye_top_y_avg)
calibration_counter += 1
if calibration_counter == CALIBRATION_FRAMES:
normal_user_L_eyebrow_y_avg = np.mean(calibration_data_user_L_eyebrow_y)
normal_user_R_eyebrow_y_avg = np.mean(calibration_data_user_R_eyebrow_y)
normal_user_L_eye_top_y_avg = np.mean(calibration_data_user_L_eye_top_y)
normal_user_R_eye_top_y_avg = np.mean(calibration_data_user_R_eye_top_y)
# Distance = Y_eye_top - Y_eyebrow (larger means eyebrow is higher or eye is lower)
normal_dist_L_eyebrow_to_eye = normal_user_L_eye_top_y_avg - normal_user_L_eyebrow_y_avg
normal_dist_R_eyebrow_to_eye = normal_user_R_eye_top_y_avg - normal_user_R_eyebrow_y_avg
print("[INFO] Calibration finished.")
print(f"[INFO] Calibrated Avg L Eyebrow Y: {normal_user_L_eyebrow_y_avg:.2f}, Avg R Eyebrow Y: {normal_user_R_eyebrow_y_avg:.2f}")
print(f"[INFO] Calibrated Avg L Eye Top Y: {normal_user_L_eye_top_y_avg:.2f}, Avg R Eye Top Y: {normal_user_R_eye_top_y_avg:.2f}")
print(f"[INFO] Calibrated Dist L Eyebrow-Eye: {normal_dist_L_eyebrow_to_eye:.2f}, R Eyebrow-Eye: {normal_dist_R_eyebrow_to_eye:.2f}")
print(f"[INFO] 'No' detection if current distance > normal_dist * (1 + {EYEBROW_TO_EYE_VERTICAL_DISTANCE_INCREASE_FACTOR})")
print(f"[INFO] 'Yes' detection threshold (EAR must be <): {EAR_THRESHOLD:.2f}")
# --- Detection Phase ---
else:
if normal_dist_L_eyebrow_to_eye != 0 and normal_dist_R_eyebrow_to_eye != 0: # Ensure calibration is done
# Detect "Yes" (eyes closed)
if avg_ear < EAR_THRESHOLD:
current_state = STATE_YES
# Detect "No" (eyebrows raised significantly relative to eyes)
else: # Check for NO only if not YES
current_dist_L = current_user_L_eye_top_y_avg - current_user_L_eyebrow_y_avg
current_dist_R = current_user_R_eye_top_y_avg - current_user_R_eyebrow_y_avg
threshold_dist_L = normal_dist_L_eyebrow_to_eye * (1 + EYEBROW_TO_EYE_VERTICAL_DISTANCE_INCREASE_FACTOR)
threshold_dist_R = normal_dist_R_eyebrow_to_eye * (1 + EYEBROW_TO_EYE_VERTICAL_DISTANCE_INCREASE_FACTOR)
# Handle cases where normal distance might be zero or negative (eyebrow very low)
# If normal distance is small or negative, a small absolute increase might be enough.
# This part might need more sophisticated handling if eyebrows are naturally very low or touching eyelids.
# For now, simple multiplication factor.
if normal_dist_L_eyebrow_to_eye <= 0: threshold_dist_L = normal_dist_L_eyebrow_to_eye + abs(normal_dist_L_eyebrow_to_eye * EYEBROW_TO_EYE_VERTICAL_DISTANCE_INCREASE_FACTOR) + 5 # Add a small fixed increase
if normal_dist_R_eyebrow_to_eye <= 0: threshold_dist_R = normal_dist_R_eyebrow_to_eye + abs(normal_dist_R_eyebrow_to_eye * EYEBROW_TO_EYE_VERTICAL_DISTANCE_INCREASE_FACTOR) + 5
if current_dist_L > threshold_dist_L and current_dist_R > threshold_dist_R:
current_state = STATE_NO
# Optional: Draw landmarks for debugging
# Eyebrows
# for i in list(user_L_eyebrow_y_calc_indices) + list(user_R_eyebrow_y_calc_indices):
# p = get_landmark_point(landmarks, i)
# cv2.circle(frame, p, 2, (0, 255, 0), -1)
# # Top of eyes
# for i in user_L_eye_top_indices + user_R_eye_top_indices:
# p = get_landmark_point(landmarks, i)
# cv2.circle(frame, p, 2, (255, 0, 0), -1)
# Display the detected state
display_text = current_state
color = (255, 255, 0) # Default for Normal/Calibrating
if current_state == STATE_YES:
color = (0, 255, 0) # Green for Yes
elif current_state == STATE_NO:
color = (0, 0, 255) # Red for No
cv2.putText(frame, display_text, (50, 80), cv2.FONT_HERSHEY_SIMPLEX, 1.5, color, 3)
cv2.imshow("Gesture Detection (Press 'q' to quit)", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
print("[INFO] Application closed.") |