1 Commits
Author SHA1 Message Date
james.maesandClaude Opus 4.7 813c6cc750 Add finger counting and facial expression (smile/frown/neutral) detection
Both build on Vision's existing requests — no new model files, no
Python conversion. The FingerCounter reads named hand-joint positions
from the existing VNDetectHumanHandPoseRequest output; the
FacialExpressionAnalyzer reads outer-lip landmarks from a swapped-in
VNDetectFaceLandmarksRequest (a superset of the existing rectangles
request — same bbox, plus the landmarks).

## FingerCounter

For each detected hand:
- Index / middle / ring / pinky are "extended" when their tip is
  farther from the wrist than the MCP base joint by >1.25× (tolerates
  partial curl while rejecting fully-tucked fingers)
- Thumb is special — measures perpendicular distance from the wrist→
  middleMCP axis; if the thumb tip is >0.045 normalized units lateral
  to that line, it's extended
- Emits `gestureDetected("fingers_<N>", confidence)` on the event bus
  where confidence is the average joint confidence
- Throttled to 1.5s per N — so showing 3 fingers, opening to 5,
  closing to 3 will emit cleanly but holding 3 fingers up steady won't
  flood the bus

## FacialExpressionAnalyzer

For each face with landmarks:
- Read `landmarks.outerLips.normalizedPoints` — closed contour around
  the mouth
- Mouth corners = leftmost + rightmost x in the contour
- Centroid y = average of all contour y values
- Compare corner_avg_y vs centroid_y with a dead-band scaled to the
  face's bounding-box height (default 1%):
  - Corners above centroid → smile
  - Corners below centroid → frown
  - Within dead-band → neutral_face
- Emits `gestureDetected("smile" | "frown" | "neutral_face", confidence)`
- Throttled to 4s per expression name

## ObjectDetector wiring

- `VNDetectFaceRectanglesRequest` → `VNDetectFaceLandmarksRequest`
  (still returns VNFaceObservation with .boundingBox for tracking +
  face recognition, but now also populates .landmarks for expression)
- Two new private members: `fingerCounter` + `facialExpressionAnalyzer`
- After the face-result loop in `detectAndBootstrap`, both analyzers
  receive the raw Vision observations (PoseDetection.points are
  unnamed, so the analyzers go straight to the source)

History and Log Stream both display the new events automatically since
they reuse the existing `gestureDetected` case in DetectionEvent.Kind.
No event-vocabulary changes, no UI changes needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 17:29:50 -05:00