This commit is contained in:
Michael Freno
2026-01-31 23:49:06 -05:00
parent a20b3701a6
commit 11f2313b34
3 changed files with 41 additions and 7 deletions

View File

@@ -65,7 +65,8 @@ class EyeTrackingService: NSObject, ObservableObject {
faceWidthScaleMax: 1.4,
eyeBoundsHorizontalPadding: TrackingConfig.default.eyeBoundsHorizontalPadding,
eyeBoundsVerticalPaddingUp: TrackingConfig.default.eyeBoundsVerticalPaddingUp,
eyeBoundsVerticalPaddingDown: TrackingConfig.default.eyeBoundsVerticalPaddingDown
eyeBoundsVerticalPaddingDown: TrackingConfig.default.eyeBoundsVerticalPaddingDown,
eyeBoundsSmoothing: TrackingConfig.default.eyeBoundsSmoothing
)
processor.updateConfig(config)

View File

@@ -68,7 +68,8 @@ public struct TrackingConfig: Sendable {
faceWidthScaleMax: Double,
eyeBoundsHorizontalPadding: Double,
eyeBoundsVerticalPaddingUp: Double,
eyeBoundsVerticalPaddingDown: Double
eyeBoundsVerticalPaddingDown: Double,
eyeBoundsSmoothing: Double
) {
self.horizontalAwayThreshold = horizontalAwayThreshold
self.verticalAwayThreshold = verticalAwayThreshold
@@ -86,6 +87,7 @@ public struct TrackingConfig: Sendable {
self.eyeBoundsHorizontalPadding = eyeBoundsHorizontalPadding
self.eyeBoundsVerticalPaddingUp = eyeBoundsVerticalPaddingUp
self.eyeBoundsVerticalPaddingDown = eyeBoundsVerticalPaddingDown
self.eyeBoundsSmoothing = eyeBoundsSmoothing
}
public let horizontalAwayThreshold: Double
@@ -104,6 +106,7 @@ public struct TrackingConfig: Sendable {
public let eyeBoundsHorizontalPadding: Double
public let eyeBoundsVerticalPaddingUp: Double
public let eyeBoundsVerticalPaddingDown: Double
public let eyeBoundsSmoothing: Double
public static let `default` = TrackingConfig(
horizontalAwayThreshold: 0.08,
@@ -121,6 +124,7 @@ public struct TrackingConfig: Sendable {
faceWidthScaleMax: 1.4,
eyeBoundsHorizontalPadding: 0.1,
eyeBoundsVerticalPaddingUp: 0.9,
eyeBoundsVerticalPaddingDown: 0.4
eyeBoundsVerticalPaddingDown: 0.4,
eyeBoundsSmoothing: 0.2
)
}

View File

@@ -32,6 +32,8 @@ final class VisionGazeProcessor: @unchecked Sendable {
private let baselineModel = GazeBaselineModel()
private var faceWidthBaseline: Double?
private var faceWidthSmoothed: Double?
private var leftEyeFrameSmoothed: CGRect?
private var rightEyeFrameSmoothed: CGRect?
private var config: TrackingConfig
init(config: TrackingConfig) {
@@ -46,6 +48,8 @@ final class VisionGazeProcessor: @unchecked Sendable {
baselineModel.reset()
faceWidthBaseline = nil
faceWidthSmoothed = nil
leftEyeFrameSmoothed = nil
rightEyeFrameSmoothed = nil
}
func process(analysis: VisionPipeline.FaceAnalysis) -> ObservationResult {
@@ -77,13 +81,15 @@ final class VisionGazeProcessor: @unchecked Sendable {
eye: landmarks.leftEye,
pupil: landmarks.leftPupil,
face: face,
imageSize: analysis.imageSize
imageSize: analysis.imageSize,
smoothingRect: &leftEyeFrameSmoothed
)
let rightEye = makeEyeObservation(
eye: landmarks.rightEye,
pupil: landmarks.rightPupil,
face: face,
imageSize: analysis.imageSize
imageSize: analysis.imageSize,
smoothingRect: &rightEyeFrameSmoothed
)
let eyesClosed = detectEyesClosed(left: leftEye, right: rightEye)
@@ -126,7 +132,8 @@ final class VisionGazeProcessor: @unchecked Sendable {
eye: VNFaceLandmarkRegion2D?,
pupil: VNFaceLandmarkRegion2D?,
face: VNFaceObservation,
imageSize: CGSize
imageSize: CGSize,
smoothingRect: inout CGRect?
) -> EyeObservation? {
guard let eye else { return nil }
@@ -142,8 +149,10 @@ final class VisionGazeProcessor: @unchecked Sendable {
pupilPoint = bounds.center
}
let rawFrame = CGRect(x: bounds.minX, y: bounds.minY, width: bounds.size.width, height: bounds.size.height)
let smoothedFrame = smoothRect(rawFrame, existing: &smoothingRect, smoothing: config.eyeBoundsSmoothing)
let paddedFrame = expandRect(
CGRect(x: bounds.minX, y: bounds.minY, width: bounds.size.width, height: bounds.size.height),
smoothedFrame,
horizontalPadding: config.eyeBoundsHorizontalPadding,
verticalPaddingUp: config.eyeBoundsVerticalPaddingUp,
verticalPaddingDown: config.eyeBoundsVerticalPaddingDown
@@ -238,6 +247,26 @@ final class VisionGazeProcessor: @unchecked Sendable {
)
}
private func smoothRect(_ rect: CGRect, existing: inout CGRect?, smoothing: Double) -> CGRect {
guard smoothing > 0, smoothing < 1 else {
existing = rect
return rect
}
if let current = existing {
let newOriginX = current.origin.x + (rect.origin.x - current.origin.x) * smoothing
let newOriginY = current.origin.y + (rect.origin.y - current.origin.y) * smoothing
let newWidth = current.size.width + (rect.size.width - current.size.width) * smoothing
let newHeight = current.size.height + (rect.size.height - current.size.height) * smoothing
let updated = CGRect(x: newOriginX, y: newOriginY, width: newWidth, height: newHeight)
existing = updated
return updated
}
existing = rect
return rect
}
private func averageCoordinate(left: CGFloat?, right: CGFloat?, fallback: Double?) -> Double? {
switch (left, right) {
case let (left?, right?):