Extract helper for calibration

This commit is contained in:
ck-zhang
2025-04-24 14:14:13 +08:00
parent fcc7b20fe6
commit a719fe6dc7
3 changed files with 81 additions and 103 deletions

View File

@@ -54,3 +54,61 @@ def wait_for_face_and_countdown(cap, gaze_estimator, sw, sh, dur: int = 2) -> bo
cv2.imshow("Calibration", canvas)
if cv2.waitKey(1) == 27:
return False
def _pulse_and_capture(
gaze_estimator,
cap,
pts,
sw: int,
sh: int,
pulse_d: float = 1.0,
cd_d: float = 1.0,
):
"""
Shared pulse-and-capture loop for each calibration point.
"""
feats, targs = [], []
for x, y in pts:
# pulse
ps = time.time()
final_radius = 20
while True:
e = time.time() - ps
if e > pulse_d:
break
ok, frame = cap.read()
if not ok:
continue
canvas = np.zeros((sh, sw, 3), dtype=np.uint8)
radius = 15 + int(15 * abs(np.sin(2 * np.pi * e)))
final_radius = radius
cv2.circle(canvas, (x, y), radius, (0, 255, 0), -1)
cv2.imshow("Calibration", canvas)
if cv2.waitKey(1) == 27:
return None
# capture
cs = time.time()
while True:
e = time.time() - cs
if e > cd_d:
break
ok, frame = cap.read()
if not ok:
continue
canvas = np.zeros((sh, sw, 3), dtype=np.uint8)
cv2.circle(canvas, (x, y), final_radius, (0, 255, 0), -1)
t = e / cd_d
ease = t * t * (3 - 2 * t)
ang = 360 * (1 - ease)
cv2.ellipse(canvas, (x, y), (40, 40), 0, -90, -90 + ang, (255, 255, 255), 4)
cv2.imshow("Calibration", canvas)
if cv2.waitKey(1) == 27:
return None
ft, blink = gaze_estimator.extract_features(frame)
if ft is not None and not blink:
feats.append(ft)
targs.append([x, y])
return feats, targs

View File

@@ -1,14 +1,13 @@
import time
import cv2
import numpy as np
from eyetrax.utils.screen import get_screen_size
from eyetrax.calibration.common import wait_for_face_and_countdown
from eyetrax.calibration.common import wait_for_face_and_countdown, _pulse_and_capture
def run_5_point_calibration(gaze_estimator, camera_index: int = 0):
"""
Faster five-point calibration
Faster five-point calibration.
"""
sw, sh = get_screen_size()
@@ -23,55 +22,11 @@ def run_5_point_calibration(gaze_estimator, camera_index: int = 0):
order = [(1, 1), (0, 0), (2, 0), (0, 2), (2, 2)]
pts = [(mx + int(c * (gw / 2)), my + int(r * (gh / 2))) for (r, c) in order]
feats, targs = [], []
pulse_d, cd_d = 1.0, 1.0
for _ in range(1):
for x, y in pts:
ps = time.time()
final_radius = 20
while True:
e = time.time() - ps
if e > pulse_d:
break
r, f = cap.read()
if not r:
continue
c = np.zeros((sh, sw, 3), dtype=np.uint8)
radius = 15 + int(15 * abs(np.sin(2 * np.pi * e)))
final_radius = radius
cv2.circle(c, (x, y), radius, (0, 255, 0), -1)
cv2.imshow("Calibration", c)
if cv2.waitKey(1) == 27:
cap.release()
cv2.destroyAllWindows()
return
cs = time.time()
while True:
e = time.time() - cs
if e > cd_d:
break
r, f = cap.read()
if not r:
continue
c = np.zeros((sh, sw, 3), dtype=np.uint8)
cv2.circle(c, (x, y), final_radius, (0, 255, 0), -1)
t = e / cd_d
ease = t * t * (3 - 2 * t)
ang = 360 * (1 - ease)
cv2.ellipse(c, (x, y), (40, 40), 0, -90, -90 + ang, (255, 255, 255), 4)
cv2.imshow("Calibration", c)
if cv2.waitKey(1) == 27:
cap.release()
cv2.destroyAllWindows()
return
ft, blink = gaze_estimator.extract_features(f)
if ft is not None and not blink:
feats.append(ft)
targs.append([x, y])
res = _pulse_and_capture(gaze_estimator, cap, pts, sw, sh)
cap.release()
cv2.destroyAllWindows()
if res is None:
return
feats, targs = res
if feats:
gaze_estimator.train(np.array(feats), np.array(targs))

View File

@@ -1,14 +1,13 @@
import time
import cv2
import numpy as np
from eyetrax.utils.screen import get_screen_size
from eyetrax.calibration.common import wait_for_face_and_countdown
from eyetrax.calibration.common import wait_for_face_and_countdown, _pulse_and_capture
def run_9_point_calibration(gaze_estimator, camera_index: int = 0):
"""
Standard ninepoint calibration
Standard nine-point calibration
"""
sw, sh = get_screen_size()
@@ -20,58 +19,24 @@ def run_9_point_calibration(gaze_estimator, camera_index: int = 0):
mx, my = int(sw * 0.1), int(sh * 0.1)
gw, gh = sw - 2 * mx, sh - 2 * my
order = [(1, 1), (0, 0), (2, 0), (0, 2), (2, 2), (1, 0), (0, 1), (2, 1), (1, 2)]
order = [
(1, 1),
(0, 0),
(2, 0),
(0, 2),
(2, 2),
(1, 0),
(0, 1),
(2, 1),
(1, 2),
]
pts = [(mx + int(c * (gw / 2)), my + int(r * (gh / 2))) for (r, c) in order]
feats, targs = [], []
pulse_d, cd_d = 1.0, 1.0
for _ in range(1):
for x, y in pts:
ps = time.time()
final_radius = 20
while True:
e = time.time() - ps
if e > pulse_d:
break
r, f = cap.read()
if not r:
continue
c = np.zeros((sh, sw, 3), dtype=np.uint8)
radius = 15 + int(15 * abs(np.sin(2 * np.pi * e)))
final_radius = radius
cv2.circle(c, (x, y), radius, (0, 255, 0), -1)
cv2.imshow("Calibration", c)
if cv2.waitKey(1) == 27:
cap.release()
cv2.destroyAllWindows()
return
cs = time.time()
while True:
e = time.time() - cs
if e > cd_d:
break
r, f = cap.read()
if not r:
continue
c = np.zeros((sh, sw, 3), dtype=np.uint8)
cv2.circle(c, (x, y), final_radius, (0, 255, 0), -1)
t = e / cd_d
ease = t * t * (3 - 2 * t)
ang = 360 * (1 - ease)
cv2.ellipse(c, (x, y), (40, 40), 0, -90, -90 + ang, (255, 255, 255), 4)
cv2.imshow("Calibration", c)
if cv2.waitKey(1) == 27:
cap.release()
cv2.destroyAllWindows()
return
ft, blink = gaze_estimator.extract_features(f)
if ft is not None and not blink:
feats.append(ft)
targs.append([x, y])
res = _pulse_and_capture(gaze_estimator, cap, pts, sw, sh)
cap.release()
cv2.destroyAllWindows()
if res is None:
return
feats, targs = res
if feats:
gaze_estimator.train(np.array(feats), np.array(targs))