Files
zyt/TUICallKit-Vue3/training/motion/roi-openness.ts
T
longandCursor 729d2a084d feat(training): 新增握力环 2.0 摄像头手势识别页
用前置摄像头 + VisionKit/ROI 管线驱动开合计数,复用跟练舞台与连击粒子,并加固结束收尾避免末段卡死无响应。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 18:31:31 +08:00

69 lines
2.0 KiB
TypeScript

import type { CameraFrame, HandSample } from './types'
function clamp01(v: number) {
return Math.max(0, Math.min(1, v))
}
/**
* 开发者工具 / 不支持 VisionKit 时的粗开合估计:
* 取画面中心 ROI 的亮度方差与边缘能量,映射到 openness。
* 仅用于打通链路与 UI 联调,不能作为产品识别。
*/
export function estimateOpennessFromRoi(frame: CameraFrame): HandSample {
const { width, height, data } = frame
const u8 = new Uint8Array(data)
const cx0 = Math.floor(width * 0.28)
const cy0 = Math.floor(height * 0.22)
const cx1 = Math.floor(width * 0.72)
const cy1 = Math.floor(height * 0.78)
const step = Math.max(4, Math.floor(Math.min(width, height) / 40))
let count = 0
let sum = 0
let sumSq = 0
let edge = 0
let prev = 0
for (let y = cy0; y < cy1; y += step) {
for (let x = cx0; x < cx1; x += step) {
const i = (y * width + x) * 4
const r = u8[i]
const g = u8[i + 1]
const b = u8[i + 2]
// 粗略肤色门控 + 亮度
const luma = 0.299 * r + 0.587 * g + 0.114 * b
const skinish = r > 60 && g > 30 && b > 20 && r >= g && Math.abs(r - g) > 8
if (!skinish && luma < 40) continue
sum += luma
sumSq += luma * luma
edge += Math.abs(luma - prev)
prev = luma
count += 1
}
}
if (count < 20) {
return {
ts: Date.now(),
openness: 0.5,
score: 0.05,
source: 'roi',
}
}
const mean = sum / count
const variance = Math.max(0, sumSq / count - mean * mean)
const edgeNorm = edge / count
// 张开手掌通常带来更大轮廓/边缘;握拳更紧凑。经验映射,仅联调。
const openness = clamp01((Math.sqrt(variance) / 55 + edgeNorm / 40) * 0.85)
const score = clamp01(0.25 + count / 800)
return {
ts: Date.now(),
openness,
score,
source: 'roi',
}
}