-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
118 lines (97 loc) · 3.65 KB
/
app.py
File metadata and controls
118 lines (97 loc) · 3.65 KB
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
import cv2
import mediapipe as mp
from collections import deque, Counter
# Initialize MediaPipe FaceMesh
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(
max_num_faces=1,
refine_landmarks=True # Needed for iris tracking
)
# History for smoothing gaze detection
gaze_history = deque(maxlen=10)
def smooth_gaze(new_gaze):
gaze_history.append(new_gaze)
return Counter(gaze_history).most_common(1)[0][0]
def get_gaze_direction(landmarks):
# Iris landmarks
right_iris = [landmarks[i] for i in [474, 475, 476, 477]]
left_iris = [landmarks[i] for i in [469, 470, 471, 472]]
right_iris_x = sum(pt.x for pt in right_iris) / 4
right_iris_y = sum(pt.y for pt in right_iris) / 4
left_iris_x = sum(pt.x for pt in left_iris) / 4
left_iris_y = sum(pt.y for pt in left_iris) / 4
# Eye corners (horizontal reference)
right_eye_left = landmarks[33].x
right_eye_right = landmarks[133].x
left_eye_left = landmarks[362].x
left_eye_right = landmarks[263].x
# Eyelids (vertical reference) - use min/max to avoid flip
right_eye_top = min(landmarks[159].y, landmarks[145].y)
right_eye_bottom = max(landmarks[159].y, landmarks[145].y)
left_eye_top = min(landmarks[386].y, landmarks[374].y)
left_eye_bottom = max(landmarks[386].y, landmarks[374].y)
# Ratios
right_horz = (right_iris_x - right_eye_left) / (right_eye_right - right_eye_left)
left_horz = (left_iris_x - left_eye_left) / (left_eye_right - left_eye_left)
avg_horz = (right_horz + left_horz) / 2
right_vert = (right_iris_y - right_eye_top) / (right_eye_bottom - right_eye_top)
left_vert = (left_iris_y - left_eye_top) / (left_eye_bottom - left_eye_top)
avg_vert = (right_vert + left_vert) / 2
# Horizontal classification
if avg_horz < 0.40:
horz = "Right"
elif avg_horz > 0.60:
horz = "Left"
else:
horz = "Center"
# Vertical classification
if avg_vert < 0.35:
vert = "Up"
elif avg_vert > 0.65:
vert = "Down"
else:
vert = "Center"
# Combine both
if horz == "Center" and vert == "Center":
return "Center"
elif horz == "Center":
return vert
elif vert == "Center":
return horz
else:
return f"{vert}-{horz}" # e.g. "Up-Left"
def draw_arrow(image, x, y, gaze):
length = 40
dx, dy = 0, 0
if "Left" in gaze: dx = -length
if "Right" in gaze: dx = length
if "Up" in gaze: dy = -length
if "Down" in gaze: dy = length
if dx or dy:
cv2.arrowedLine(image, (x, y), (x+dx, y+dy), (0, 255, 0), 2, tipLength=0.3)
# Webcam capture
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
results = face_mesh.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
if results.multi_face_landmarks:
for face_landmarks in results.multi_face_landmarks:
gaze = smooth_gaze(get_gaze_direction(face_landmarks.landmark))
h, w, _ = frame.shape
# Iris centers: 468 (left), 473 (right)
for i in [468, 473]:
cx, cy = int(face_landmarks.landmark[i].x * w), int(face_landmarks.landmark[i].y * h)
cv2.circle(frame, (cx, cy), 3, (0, 255, 255), -1)
draw_arrow(frame, cx, cy, gaze)
# Display text
cv2.putText(frame, f'Gaze: {gaze}', (30, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2)
cv2.imshow("Gaze Detection", frame)
if cv2.waitKey(1) & 0xFF == 27: # ESC to exit
break
cap.release()
cv2.destroyAllWindows()