import type { HandSample, Point2D } from './types' /** VisionKit 文档:1=布/开掌,3=握拳 */ const GESTURE_OPEN = 1 const GESTURE_FIST = 3 /** * OpenPose-21 指尖索引(相对手腕距离用于连续 openness) * 0 wrist, 4 thumb tip, 8 index, 12 middle, 16 ring, 20 pinky */ const TIP_INDEXES = [4, 8, 12, 16, 20] function dist(a: Point2D, b: Point2D) { const dx = a.x - b.x const dy = a.y - b.y return Math.sqrt(dx * dx + dy * dy) } function clamp01(v: number) { return Math.max(0, Math.min(1, v)) } /** 离散手势 → 开合度;未知手势返回 null(交给几何或保持) */ export function opennessFromGesture(gesture?: number): number | null { if (gesture === GESTURE_OPEN) return 1 if (gesture === GESTURE_FIST) return 0 return null } /** * 用指尖到手腕的平均距离 / 掌宽 估计 openness。 * 握力环场景开合幅度偏小,映射区间比裸掌更紧一些。 */ export function opennessFromLandmarks(points?: Point2D[]): number | null { if (!points || points.length < 21) return null const wrist = points[0] const mcpIndex = points[5] const mcpPinky = points[17] const palmWidth = dist(mcpIndex, mcpPinky) if (palmWidth < 1e-6) return null let tipSum = 0 for (const i of TIP_INDEXES) { tipSum += dist(points[i], wrist) } const tipMean = tipSum / TIP_INDEXES.length // 握力环:握紧约 0.9~1.35 掌宽,半开约 1.5~2.2 掌宽 const ratio = tipMean / palmWidth return clamp01((ratio - 0.95) / 1.15) } export function mergeOpenness(sample: Pick): number | null { const fromGeom = opennessFromLandmarks(sample.points) const fromGesture = opennessFromGesture(sample.gesture) if (fromGeom != null && fromGesture != null) { // 几何为主:离散手势(布/拳)在捏环时容易误判,只做轻牵引 return clamp01(fromGeom * 0.85 + fromGesture * 0.15) } if (fromGeom != null) return fromGeom if (fromGesture != null) return fromGesture return null }