import { GestureHandler } from "@/hand_landmark/gesture_handler";
|
|
import {
|
|
FilesetResolver,
|
|
GestureRecognizer,
|
|
GestureRecognizerOptions,
|
|
} from "@mediapipe/tasks-vision";
|
|
|
|
// 手势
|
|
export enum HandGesture {
|
|
// 食指举起,移动鼠标
|
|
ONLY_INDEX_UP = "only_index_up",
|
|
|
|
// 食指和拇指举起,移动鼠标
|
|
INDEX_AND_THUMB_UP = "index_and_thumb_up",
|
|
|
|
// ok手势 - 滚动屏幕
|
|
SCROLL_GESTURE = "scroll_gesture",
|
|
|
|
// 四根手指同时竖起 - enter
|
|
FOUR_FINGERS_UP = "four_fingers_up",
|
|
|
|
// 五根手指同时竖起 - 暂停/开始 识别
|
|
STOP_GESTURE = "stop_gesture",
|
|
|
|
// 6手势 - 语音识别
|
|
VOICE_GESTURE_START = "voice_gesture_start",
|
|
VOICE_GESTURE_STOP = "voice_gesture_stop",
|
|
|
|
// 其他手势
|
|
OTHER = "other",
|
|
|
|
}
|
|
|
|
interface HandLandmark {
|
|
x: number;
|
|
y: number;
|
|
z: number;
|
|
}
|
|
|
|
export interface HandInfo {
|
|
landmarks: HandLandmark[];
|
|
handedness: "Left" | "Right";
|
|
score: number;
|
|
categoryName?: string;
|
|
}
|
|
|
|
interface DetectionResult {
|
|
leftHand?: HandInfo;
|
|
rightHand?: HandInfo;
|
|
// 原始检测结果,以防需要访问其他数据
|
|
rawResult: any;
|
|
}
|
|
|
|
/**
|
|
* 检测器类 - 负责手势识别和手势分类
|
|
* 主要职责:
|
|
* 1. 初始化
|
|
* 2. 检测视频帧中的手部
|
|
* 3. 分析手势类型(手指竖起等)
|
|
* 4. 提供手部关键点查询方法
|
|
*/
|
|
export class Detector {
|
|
private detector: GestureRecognizer | null = null;
|
|
private gestureHandler: GestureHandler | null = null;
|
|
|
|
async initialize(useCanvas = false) {
|
|
const vision = await FilesetResolver.forVisionTasks("/mediapipe/wasm");
|
|
try {
|
|
const params = {
|
|
baseOptions: {
|
|
modelAssetPath: "/mediapipe/gesture_recognizer.task",
|
|
delegate: "GPU",
|
|
},
|
|
runningMode: "VIDEO",
|
|
numHands: 1,
|
|
} as GestureRecognizerOptions;
|
|
if (useCanvas) {
|
|
params.canvas = document.createElement("canvas");
|
|
}
|
|
|
|
this.detector = await GestureRecognizer.createFromOptions(vision, params);
|
|
this.gestureHandler = new GestureHandler();
|
|
} catch (error: any) {
|
|
if (error.toString().includes("kGpuService")) {
|
|
await this.initialize(true);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检测视频帧中的手部(保持独立)
|
|
async detect(video: HTMLVideoElement): Promise<DetectionResult> {
|
|
if (!this.detector) {
|
|
throw new Error("检测器未初始化");
|
|
}
|
|
|
|
const now = performance.now();
|
|
const result = await this.detector.recognizeForVideo(video, now);
|
|
const detection: DetectionResult = {
|
|
rawResult: result,
|
|
};
|
|
|
|
if (result.landmarks && result.handedness) {
|
|
for (let i = 0; i < result.landmarks.length; i++) {
|
|
const hand: HandInfo = {
|
|
landmarks: result.landmarks[i],
|
|
handedness: result.handedness[i][0].categoryName as "Left" | "Right",
|
|
score: result.handedness[i][0].score,
|
|
};
|
|
|
|
if (result.gestures.length > 0) {
|
|
hand.categoryName = result.gestures[0][0].categoryName;
|
|
}
|
|
|
|
if (hand.handedness === "Left") {
|
|
detection.leftHand = hand;
|
|
} else {
|
|
detection.rightHand = hand;
|
|
}
|
|
}
|
|
}
|
|
|
|
return detection;
|
|
}
|
|
|
|
/**
|
|
* 检测手指是否竖起
|
|
*/
|
|
static _fingersUp(hand: HandInfo): number[] {
|
|
const fingers: number[] = [];
|
|
const tipIds = [4, 8, 12, 16, 20]; // 从大拇指开始,依次为每个手指指尖
|
|
|
|
// 检测大拇指
|
|
if (hand.handedness === "Right") {
|
|
if (hand.landmarks[tipIds[0]].x < hand.landmarks[tipIds[0] - 1].x) {
|
|
fingers.push(0);
|
|
} else {
|
|
fingers.push(1);
|
|
}
|
|
} else {
|
|
if (hand.landmarks[tipIds[0]].x > hand.landmarks[tipIds[0] - 1].x) {
|
|
fingers.push(0);
|
|
} else {
|
|
fingers.push(1);
|
|
}
|
|
}
|
|
|
|
// 检测其他四个手指
|
|
for (let id = 1; id < 5; id++) {
|
|
if (hand.landmarks[tipIds[id]].y < hand.landmarks[tipIds[id] - 2].y) {
|
|
fingers.push(1);
|
|
} else {
|
|
fingers.push(0);
|
|
}
|
|
}
|
|
|
|
return fingers;
|
|
}
|
|
|
|
/**
|
|
* 获取单个手的手势类型
|
|
*/
|
|
public static getSingleHandGesture(hand: HandInfo): HandGesture {
|
|
const fingers = this._fingersUp(hand);
|
|
const fingerState = fingers.join(",");
|
|
|
|
// 定义手势映射表
|
|
const gestureMap = new Map<string, HandGesture>([
|
|
// 食指举起,移动鼠标
|
|
["0,1,0,0,0", HandGesture.ONLY_INDEX_UP],
|
|
|
|
// 鼠标左键点击手势
|
|
["1,1,0,0,0", HandGesture.INDEX_AND_THUMB_UP],
|
|
|
|
// 滚动屏幕手势 ok
|
|
["0,0,1,1,1", HandGesture.SCROLL_GESTURE],
|
|
|
|
// 四根手指同时竖起
|
|
["0,1,1,1,1", HandGesture.FOUR_FINGERS_UP],
|
|
|
|
// 五根手指同时竖起 - 暂停/开始 识别
|
|
["1,1,1,1,1", HandGesture.STOP_GESTURE],
|
|
|
|
// 6手势- 语音识别
|
|
["1,0,0,0,1", HandGesture.VOICE_GESTURE_START],
|
|
|
|
// 结束语音识别
|
|
["0,0,0,0,0", HandGesture.VOICE_GESTURE_STOP],
|
|
|
|
|
|
]);
|
|
|
|
if (gestureMap.has(fingerState)) {
|
|
return gestureMap.get(fingerState) as HandGesture;
|
|
}
|
|
|
|
|
|
// 返回默认值
|
|
return HandGesture.OTHER;
|
|
}
|
|
|
|
/**
|
|
* 处理检测结果并执行相应动作
|
|
*/
|
|
//async process(detection: DetectionResult): Promise<HandGesture> {
|
|
// const hand = detection.rightHand ?? detection.leftHand;
|
|
|
|
// if (!hand) {
|
|
// console.log("没检测到手");
|
|
// return HandGesture.OTHER;
|
|
// }
|
|
|
|
// const gesture = Detector.getSingleHandGesture(hand);
|
|
// console.log("当前手势状态是:", gesture);
|
|
|
|
// if (gesture === HandGesture.ONLY_INDEX_UP) {
|
|
// console.log("识别到食指竖起!");
|
|
// }
|
|
|
|
// if (gesture === HandGesture.INDEX_AND_THUMB_UP) {
|
|
// console.log("识别到食指和大拇指竖起!");
|
|
// }
|
|
|
|
// if (gesture === HandGesture.THREE_FINGERS_UP) {
|
|
// console.log("识别到ok手势!");
|
|
// }
|
|
|
|
// if (gesture === HandGesture.FOUR_FINGERS_UP) {
|
|
// console.log("识别到四指竖起!");
|
|
// }
|
|
|
|
// if (gesture === HandGesture.STOP_GESTURE) {
|
|
// console.log("识别到五指竖起!");
|
|
// }
|
|
|
|
// if (gesture === HandGesture.VOICE_GESTURE_START) {
|
|
// console.log("识别到6手势!");
|
|
// }
|
|
|
|
// if (gesture === HandGesture.VOICE_GESTURE_STOP) {
|
|
// console.log("识别到拳头手势!");
|
|
// }
|
|
|
|
// return gesture;
|
|
async process(detection: DetectionResult): Promise<void> {
|
|
const rightHandGesture = detection.rightHand
|
|
? Detector.getSingleHandGesture(detection.rightHand)
|
|
: HandGesture.OTHER;
|
|
const leftHandGesture = detection.leftHand
|
|
? Detector.getSingleHandGesture(detection.leftHand)
|
|
: HandGesture.OTHER;
|
|
|
|
// 优先使用右手
|
|
let effectiveGesture = rightHandGesture;
|
|
if (detection.rightHand) {
|
|
effectiveGesture = rightHandGesture;
|
|
} else if (detection.leftHand) {
|
|
effectiveGesture = leftHandGesture;
|
|
}
|
|
|
|
// 将手势处理交给GestureHandler
|
|
if (detection.rightHand) {
|
|
this.gestureHandler?.handleGesture(effectiveGesture, detection.rightHand);
|
|
} else if (detection.leftHand) {
|
|
this.gestureHandler?.handleGesture(effectiveGesture, detection.leftHand);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|