feat(training): 新增握力环 2.0 摄像头手势识别页
用前置摄像头 + VisionKit/ROI 管线驱动开合计数,复用跟练舞台与连击粒子,并加固结束收尾避免末段卡死无响应。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
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<HandSample, 'gesture' | 'points'>): 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
|
||||
}
|
||||
Reference in New Issue
Block a user