JavaScript實(shí)現(xiàn)手勢(shì)識(shí)別的示例詳解
利用 TensorFlow.js 中的 HandPose 模型,實(shí)現(xiàn)一個(gè)基于視頻流的手勢(shì)識(shí)別系統(tǒng),通過 HTML5 視頻流獲取攝像頭數(shù)據(jù),并結(jié)合 HandPose 模型來識(shí)別用戶的手勢(shì)。最終的目標(biāo)是能夠通過攝像頭實(shí)時(shí)識(shí)別并顯示手指數(shù)量的手勢(shì)。
項(xiàng)目概述
這個(gè)項(xiàng)目的實(shí)現(xiàn)包括以下幾個(gè)步驟:
- 獲取視頻流并設(shè)置攝像頭。
- 加載 HandPose 模型,并顯示加載進(jìn)度。
- 通過模型識(shí)別手勢(shì)。
- 基于手指的狀態(tài)輸出對(duì)應(yīng)的手勢(shì)數(shù)字(例如“1”代表一個(gè)手指,“2”代表兩個(gè)手指等)。
獲取視頻流并設(shè)置攝像頭
首先,需要通過瀏覽器的 navigator.mediaDevices.getUserMedia API 獲取用戶的攝像頭視頻流,并在網(wǎng)頁上顯示該視頻流。具體代碼如下:
async function setupCamera() {
const video = document.getElementById('video');
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
});
video.srcObject = stream;
return new Promise((resolve) => {
video.onloadedmetadata = () => {
resolve(video);
};
});
}
此函數(shù)將視頻流顯示在 HTML 中的 <video> 元素上,并在視頻加載完成后返回該視頻元素。
加載 HandPose 模型并顯示加載進(jìn)度
為了進(jìn)行手勢(shì)識(shí)別,我們需要加載 TensorFlow.js 提供的 HandPose 模型。HandPose 模型是一個(gè)用于估計(jì)手部關(guān)鍵點(diǎn)位置的深度學(xué)習(xí)模型,可以幫助我們識(shí)別手的姿勢(shì)。 在加載 HandPose 模型時(shí),我們希望顯示加載進(jìn)度。這是通過 progressCallback 實(shí)現(xiàn)的,它會(huì)在每次加載進(jìn)度更新時(shí)觸發(fā):
async function loadHandPoseModel() {
const loadingContainer = document.getElementById('loading-container');
loadingContainer.style.display = 'block'; // 顯示加載進(jìn)度條
const model = await handpose.load({
flipHorizontal: false, // 不要水平翻轉(zhuǎn)模型
progressCallback: (fraction) => {
const progress = Math.round(fraction * 100); // 計(jì)算加載的百分比
showLoadingProgress(progress); // 更新進(jìn)度條
}
});
loadingContainer.style.display = 'none'; // 隱藏加載進(jìn)度條
return model;
}
通過此函數(shù),可以實(shí)時(shí)展示模型加載的進(jìn)度條,提升用戶體驗(yàn)。
識(shí)別手勢(shì)
在加載完 HandPose 模型之后,我們就可以使用該模型來識(shí)別手勢(shì)了。模型會(huì)估計(jì)手部的 21 個(gè)關(guān)鍵點(diǎn)位置,并通過這些關(guān)鍵點(diǎn)來推測(cè)手指是否伸展。 我們需要遍歷這些關(guān)鍵點(diǎn),分析每個(gè)手指是否伸出。如果手指的末端關(guān)鍵點(diǎn)(例如食指末端)高于相鄰的關(guān)節(jié)點(diǎn),表示該手指伸展。通過此邏輯,我們可以判斷用戶伸出的手指數(shù)量:
function detectFingers(landmarks) {
let fingers = 0;
// 判斷每根手指的伸展情況
const thumb = landmarks[4]; // 拇指
const index = landmarks[8]; // 食指
const middle = landmarks[12]; // 中指
const ring = landmarks[16]; // 無名指
const pinky = landmarks[20]; // 小拇指
if (index[1] < thumb[1]) fingers++; // 食指伸展
if (middle[1] < index[1]) fingers++; // 中指伸展
if (ring[1] < middle[1]) fingers++; // 無名指伸展
if (pinky[1] < ring[1]) fingers++; // 小拇指伸展
return fingers;
}
通過此函數(shù),能夠判斷出當(dāng)前用戶伸出的手指數(shù)量。
輸出手勢(shì)結(jié)果
通過識(shí)別出的手指數(shù)量,我們可以展示一個(gè)對(duì)應(yīng)的數(shù)字手勢(shì)。例如,如果用戶伸出一個(gè)手指,我們顯示“1”;如果伸出兩個(gè)手指,則顯示“2”,依此類推。手勢(shì)識(shí)別結(jié)果會(huì)實(shí)時(shí)更新在網(wǎng)頁上:
async function detectGesture(video, model) {
const predictions = await model.estimateHands(video);
// 如果沒有檢測(cè)到手部,則顯示"檢測(cè)中..."
if (predictions.length === 0) {
document.getElementById('result').innerText = "檢測(cè)中...";
requestAnimationFrame(() => detectGesture(video, model));
return;
}
const landmarks = predictions[0].landmarks; // 獲取手部的關(guān)鍵點(diǎn)
const fingers = detectFingers(landmarks);
let resultText = "檢測(cè)中...";
// 基于手指的狀態(tài)判斷手勢(shì)
switch (fingers) {
case 1:
resultText = "一";
break;
case 2:
resultText = "二";
break;
case 3:
resultText = "三";
break;
case 4:
resultText = "四";
break;
case 5:
resultText = "五";
break;
default:
resultText = "無法識(shí)別手勢(shì)";
break;
}
// 更新檢測(cè)結(jié)果顯示
document.getElementById('result').innerText = resultText;
// 每一幀進(jìn)行檢測(cè)
requestAnimationFrame(() => detectGesture(video, model));
}
每一幀都會(huì)重新檢測(cè)手勢(shì),以確保輸出實(shí)時(shí)更新。
整合所有部分
最后,將所有的功能整合在一起,啟動(dòng)視頻流并開始手勢(shì)識(shí)別:
async function main() {
// 等待模型加載
const model = await loadHandPoseModel();
// 設(shè)置視頻流并開始播放
const video = await setupCamera();
video.play();
// 開始手勢(shì)識(shí)別
detectGesture(video, model);
}
main();
通過以上步驟,實(shí)現(xiàn)了一個(gè)基于 HandPose 模型的手勢(shì)識(shí)別系統(tǒng)。利用瀏覽器提供的 getUserMedia API 獲取視頻流,并通過 TensorFlow.js 提供的 HandPose 模型來估計(jì)手的關(guān)鍵點(diǎn),最終實(shí)現(xiàn)了對(duì)手勢(shì)的實(shí)時(shí)識(shí)別。
完整代碼
// 顯示加載進(jìn)度條
function showLoadingProgress(progress) {
const progressBar = document.getElementById('progress');
progressBar.style.width = progress + '%';
}
// 1. 獲取視頻流并設(shè)置攝像頭
async function setupCamera() {
const video = document.getElementById('video');
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
});
video.srcObject = stream;
return new Promise((resolve) => {
video.onloadedmetadata = () => {
resolve(video);
};
});
}
// 2. 加載 HandPose 模型并顯示加載進(jìn)度
async function loadHandPoseModel() {
const loadingContainer = document.getElementById('loading-container');
loadingContainer.style.display = 'block'; // 顯示加載進(jìn)度條
const model = await handpose.load({
flipHorizontal: false, // 不要水平翻轉(zhuǎn)模型
progressCallback: (fraction) => {
const progress = Math.round(fraction * 100); // 計(jì)算加載的百分比
showLoadingProgress(progress); // 更新進(jìn)度條
}
});
loadingContainer.style.display = 'none'; // 隱藏加載進(jìn)度條
return model;
}
// 3. 識(shí)別手勢(shì)
async function detectGesture(video, model) {
const predictions = await model.estimateHands(video);
// 如果沒有檢測(cè)到手部,則顯示"檢測(cè)中..."
if (predictions.length === 0) {
document.getElementById('result').innerText = "檢測(cè)中...";
requestAnimationFrame(() => detectGesture(video, model));
return;
}
const landmarks = predictions[0].landmarks; // 獲取手部的關(guān)鍵點(diǎn)
const fingers = detectFingers(landmarks);
let resultText = "檢測(cè)中...";
// 基于手指的狀態(tài)判斷手勢(shì)
switch (fingers) {
case 1:
resultText = "一";
break;
case 2:
resultText = "二";
break;
case 3:
resultText = "三";
break;
case 4:
resultText = "四";
break;
case 5:
resultText = "五";
break;
default:
resultText = "無法識(shí)別手勢(shì)";
break;
}
// 更新檢測(cè)結(jié)果顯示
document.getElementById('result').innerText = resultText;
// 每一幀進(jìn)行檢測(cè)
requestAnimationFrame(() => detectGesture(video, model));
}
// 4. 判斷手勢(shì)
function detectFingers(landmarks) {
let fingers = 0;
// 判斷每個(gè)手指是否伸出:如果手指的末端關(guān)鍵點(diǎn)在其他關(guān)鍵點(diǎn)的上方,說明該手指伸出
// 對(duì)于每根手指,檢查其關(guān)鍵點(diǎn)位置
// 手指順序: [1: thumb, 2: index, 3: middle, 4: ring, 5: pinky]
// 檢查每根手指的伸展情況
const thumb = landmarks[4]; // 拇指
const index = landmarks[8]; // 食指
const middle = landmarks[12]; // 中指
const ring = landmarks[16]; // 無名指
const pinky = landmarks[20]; // 小拇指
if (index[1] < thumb[1]) fingers++; // 如果食指的末端在拇指的末端前面,說明食指伸展
if (middle[1] < index[1]) fingers++; // 如果中指的末端在食指的末端前面,說明中指伸展
if (ring[1] < middle[1]) fingers++; // 如果無名指的末端在中指的末端前面,說明無名指伸展
if (pinky[1] < ring[1]) fingers++; // 如果小拇指的末端在無名指的末端前面,說明小拇指伸展
// 在"四"和"五"的判斷上,可以加大判斷容忍度,避免誤判
if (ring[1] < middle[1] && pinky[1] < ring[1]) {
// 可能"四"或者"五"的手勢(shì)識(shí)別較弱,可以通過容忍度來改進(jìn)
fingers = (fingers === 4) ? 5 : fingers;
}
return fingers;
}
async function main() {
// 等待模型加載
const model = await loadHandPoseModel();
// 設(shè)置視頻流并開始播放
const video = await setupCamera();
video.play();
// 開始手勢(shì)識(shí)別
detectGesture(video, model);
}
main();
html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手勢(shì)識(shí)別</title>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/handpose"></script>
<script src="https://unpkg.com/@tensorflow/tfjs-backend-webgl"></script>
</head>
<body>
<h1>手勢(shì)識(shí)別</h1>
<video id="video" width="640" height="480" autoplay></video>
<div id="result">檢測(cè)中...</div>
<div id="loading-container">
<div id="progress-bar">
<div id="progress"></div>
</div>
<div id="loading-text">正在加載模型...</div>
</div>
<script src="app.js"></script>
</body>
</html>
效果展示


到此這篇關(guān)于JavaScript實(shí)現(xiàn)手勢(shì)識(shí)別的示例詳解的文章就介紹到這了,更多相關(guān)JavaScript手勢(shì)識(shí)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript如何讓select選擇框可輸入和可下拉選擇
我們知道一般select下拉框是只能選擇的,而有時(shí)我們會(huì)遇到下拉框中沒有要選擇的信息項(xiàng)或者下拉選項(xiàng)特別多時(shí),需要允許用戶輸入想要的內(nèi)容,這篇文章主要給大家介紹了關(guān)于JavaScript如何讓select選擇框可輸入和可下拉選擇的相關(guān)資料,需要的朋友可以參考下2023-10-10
JS調(diào)用頁面表格導(dǎo)出excel示例代碼
這篇文章主要介紹了JS調(diào)用頁面表格導(dǎo)出excel的具體實(shí)現(xiàn),需要的朋友可以參考下2014-03-03
比較詳細(xì)的javascript DOM 學(xué)習(xí)筆記
看了很多的js dom學(xué)習(xí)資料,發(fā)現(xiàn)這個(gè)比較詳細(xì),特轉(zhuǎn)載給大家學(xué)習(xí)一下2008-06-06
Javascript計(jì)算兩個(gè)marker之間的距離(Google Map V3)
做地圖開發(fā),最常用到的就是marker一些操作和交互。簡(jiǎn)單介紹一下,兩個(gè)marker之間的距離計(jì)算,感興趣的朋友可以參考下哈,希望對(duì)你有所幫助2013-04-04
javascript實(shí)現(xiàn)消滅星星小游戲簡(jiǎn)單版
消滅星星是一款經(jīng)典的益智手游,單位里看到同事天天在手機(jī)上玩的游戲,現(xiàn)在也有web版了,出于業(yè)余愛好,自己嘗試用javascript實(shí)現(xiàn)了下,就是略簡(jiǎn)單了點(diǎn),文中給出了完整的實(shí)例代碼,大家可以自行完善??!下面來一起看看吧。2016-11-11
JavaScript進(jìn)制轉(zhuǎn)換實(shí)現(xiàn)方法解析
這篇文章主要介紹了JavaScript進(jìn)制轉(zhuǎn)換實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了JavaScript進(jìn)制轉(zhuǎn)換中十進(jìn)制與其他進(jìn)制轉(zhuǎn)換、以及隨機(jī)顏色生成相關(guān)操作技巧,需要的朋友可以參考下2020-01-01
js實(shí)現(xiàn)在頁面上彈出蒙板技巧簡(jiǎn)單實(shí)用
蒙板是兩個(gè)div,其中popWindow樣式的div用于遮住整個(gè)頁面并半透明,感興趣的朋友可以參考下哈,希望對(duì)你學(xué)習(xí)js蒙版有所幫助2013-04-04

