123 lines
4.8 KiB
JavaScript
123 lines
4.8 KiB
JavaScript
const video = document.getElementById('webcam');
|
||
const canvas = document.getElementById('output');
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
let detector, rafId;
|
||
|
||
async function setupCamera() {
|
||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||
try {
|
||
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||
video.srcObject = stream;
|
||
return new Promise((resolve) => {
|
||
video.onloadedmetadata = () => {
|
||
video.play(); // 确保视频播放才能获取到正确的宽度和高度
|
||
video.onplaying = () => { // 视频真正开始播放时
|
||
canvas.width = video.videoWidth;
|
||
canvas.height = video.videoHeight;
|
||
|
||
resolve();
|
||
};
|
||
};
|
||
});
|
||
} catch (error) {
|
||
console.error('Error accessing webcam:', error);
|
||
alert('Cannot access webcam. Please check permissions.');
|
||
}
|
||
} else {
|
||
alert('Your browser does not support webcam access.');
|
||
}
|
||
}
|
||
|
||
async function loadModel() {
|
||
const model = handPoseDetection.SupportedModels.MediaPipeHands;
|
||
const detectorConfig = {
|
||
runtime: 'mediapipe', // 或者 'tfjs'
|
||
solutionPath: 'https://cdn.jsdelivr.net/npm/@mediapipe/hands' // MediaPipe solution files path
|
||
};
|
||
detector = await handPoseDetection.createDetector(model, detectorConfig);
|
||
console.log('Hand Pose Detector loaded.');
|
||
}
|
||
|
||
const handSkeleton = {
|
||
'thumb': [0, 1, 2, 3, 4],
|
||
'indexFinger': [0, 5, 6, 7, 8],
|
||
'middleFinger': [0, 9, 10, 11, 12],
|
||
'ringFinger': [0, 13, 14, 15, 16],
|
||
'pinky': [0, 17, 18, 19, 20],
|
||
// 腕部和手掌基部的连接,确保0号点连接到所有手指的起始关节
|
||
'palmBase': [0, 1, 5, 9, 13, 17, 0] // 形成手掌的近似轮廓
|
||
};
|
||
|
||
async function detectHands() {
|
||
if (detector && video.readyState === 4) {
|
||
const hands = await detector.estimateHands(video, {
|
||
flipHorizontal: false
|
||
});
|
||
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空画布
|
||
|
||
if (hands.length > 0) {
|
||
for (const hand of hands) {
|
||
// 绘制手部骨架连接线
|
||
// MediaPipe Hands 模型通常有21个关键点,这些关键点有固定的索引
|
||
// 这些连接关系是标准的,可以通过硬编码定义或查找库的定义
|
||
const keypoints = hand.keypoints; // 简化访问
|
||
|
||
// 直接根据索引绘制连接线
|
||
const connectionsToDraw = [
|
||
// Thumb (拇指)
|
||
[0, 1], [1, 2], [2, 3], [3, 4],
|
||
// Index finger (食指)
|
||
[0, 5], [5, 6], [6, 7], [7, 8],
|
||
// Middle finger (中指)
|
||
[0, 9], [9, 10], [10, 11], [11, 12],
|
||
// Ring finger (无名指)
|
||
[0, 13], [13, 14], [14, 15], [15, 16],
|
||
// Pinky finger (小指)
|
||
[0, 17], [17, 18], [18, 19], [19, 20],
|
||
// Palm base connections (手掌基部连接)
|
||
[0, 5], [5, 9], [9, 13], [13, 17], [17, 0] // Connect wrist to finger bases and form a loop
|
||
];
|
||
|
||
ctx.strokeStyle = '#00FFFF'; // 青色
|
||
ctx.lineWidth = 2; // 线宽
|
||
|
||
for (const connection of connectionsToDraw) {
|
||
const start = keypoints[connection[0]];
|
||
const end = keypoints[connection[1]];
|
||
if (start && end) { // 确保关键点存在
|
||
ctx.beginPath();
|
||
ctx.moveTo(start.x, start.y);
|
||
ctx.lineTo(end.x, end.y);
|
||
ctx.stroke();
|
||
}
|
||
}
|
||
|
||
// 绘制关键点
|
||
ctx.fillStyle = '#FF0000'; // 红色
|
||
for (const keypoint of keypoints) {
|
||
// keypoint.x, keypoint.y 是像素坐标
|
||
// keypoint.z 是深度信息 (相对坐标),通常不用于2D绘制
|
||
ctx.beginPath();
|
||
ctx.arc(keypoint.x, keypoint.y, 4, 0, 2 * Math.PI); // 绘制半径为4的圆
|
||
ctx.fill();
|
||
}
|
||
|
||
// 绘制手部关键点标记(如果仍想使用util函数绘制)
|
||
// handPoseDetection.util.drawLandmarks(ctx, hand.keypoints, {color: '#FF0000', radius: 4});
|
||
// 由于我们已经手动绘制了,这行可以注释掉或移除,避免重复绘制。
|
||
}
|
||
}
|
||
}
|
||
rafId = requestAnimationFrame(detectHands);
|
||
}
|
||
|
||
async function app() {
|
||
await setupCamera();
|
||
await loadModel();
|
||
detectHands();
|
||
}
|
||
|
||
app();
|