JavaScript實現(xiàn)手勢識別的示例詳解
利用 TensorFlow.js 中的 HandPose 模型,實現(xiàn)一個基于視頻流的手勢識別系統(tǒng),通過 HTML5 視頻流獲取攝像頭數(shù)據(jù),并結(jié)合 HandPose 模型來識別用戶的手勢。最終的目標(biāo)是能夠通過攝像頭實時識別并顯示手指數(shù)量的手勢。
項目概述
這個項目的實現(xiàn)包括以下幾個步驟:
- 獲取視頻流并設(shè)置攝像頭。
- 加載 HandPose 模型,并顯示加載進(jìn)度。
- 通過模型識別手勢。
- 基于手指的狀態(tài)輸出對應(yīng)的手勢數(shù)字(例如“1”代表一個手指,“2”代表兩個手指等)。
獲取視頻流并設(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)行手勢識別,我們需要加載 TensorFlow.js 提供的 HandPose 模型。HandPose 模型是一個用于估計手部關(guān)鍵點位置的深度學(xué)習(xí)模型,可以幫助我們識別手的姿勢。 在加載 HandPose 模型時,我們希望顯示加載進(jìn)度。這是通過 progressCallback
實現(xiàn)的,它會在每次加載進(jìn)度更新時觸發(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); // 計算加載的百分比 showLoadingProgress(progress); // 更新進(jìn)度條 } }); loadingContainer.style.display = 'none'; // 隱藏加載進(jìn)度條 return model; }
通過此函數(shù),可以實時展示模型加載的進(jìn)度條,提升用戶體驗。
識別手勢
在加載完 HandPose 模型之后,我們就可以使用該模型來識別手勢了。模型會估計手部的 21 個關(guān)鍵點位置,并通過這些關(guān)鍵點來推測手指是否伸展。 我們需要遍歷這些關(guān)鍵點,分析每個手指是否伸出。如果手指的末端關(guān)鍵點(例如食指末端)高于相鄰的關(guān)節(jié)點,表示該手指伸展。通過此邏輯,我們可以判斷用戶伸出的手指數(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ù)量。
輸出手勢結(jié)果
通過識別出的手指數(shù)量,我們可以展示一個對應(yīng)的數(shù)字手勢。例如,如果用戶伸出一個手指,我們顯示“1”;如果伸出兩個手指,則顯示“2”,依此類推。手勢識別結(jié)果會實時更新在網(wǎng)頁上:
async function detectGesture(video, model) { const predictions = await model.estimateHands(video); // 如果沒有檢測到手部,則顯示"檢測中..." if (predictions.length === 0) { document.getElementById('result').innerText = "檢測中..."; requestAnimationFrame(() => detectGesture(video, model)); return; } const landmarks = predictions[0].landmarks; // 獲取手部的關(guān)鍵點 const fingers = detectFingers(landmarks); let resultText = "檢測中..."; // 基于手指的狀態(tài)判斷手勢 switch (fingers) { case 1: resultText = "一"; break; case 2: resultText = "二"; break; case 3: resultText = "三"; break; case 4: resultText = "四"; break; case 5: resultText = "五"; break; default: resultText = "無法識別手勢"; break; } // 更新檢測結(jié)果顯示 document.getElementById('result').innerText = resultText; // 每一幀進(jìn)行檢測 requestAnimationFrame(() => detectGesture(video, model)); }
每一幀都會重新檢測手勢,以確保輸出實時更新。
整合所有部分
最后,將所有的功能整合在一起,啟動視頻流并開始手勢識別:
async function main() { // 等待模型加載 const model = await loadHandPoseModel(); // 設(shè)置視頻流并開始播放 const video = await setupCamera(); video.play(); // 開始手勢識別 detectGesture(video, model); } main();
通過以上步驟,實現(xiàn)了一個基于 HandPose 模型的手勢識別系統(tǒng)。利用瀏覽器提供的 getUserMedia
API 獲取視頻流,并通過 TensorFlow.js 提供的 HandPose 模型來估計手的關(guān)鍵點,最終實現(xiàn)了對手勢的實時識別。
完整代碼
// 顯示加載進(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); // 計算加載的百分比 showLoadingProgress(progress); // 更新進(jìn)度條 } }); loadingContainer.style.display = 'none'; // 隱藏加載進(jìn)度條 return model; } // 3. 識別手勢 async function detectGesture(video, model) { const predictions = await model.estimateHands(video); // 如果沒有檢測到手部,則顯示"檢測中..." if (predictions.length === 0) { document.getElementById('result').innerText = "檢測中..."; requestAnimationFrame(() => detectGesture(video, model)); return; } const landmarks = predictions[0].landmarks; // 獲取手部的關(guān)鍵點 const fingers = detectFingers(landmarks); let resultText = "檢測中..."; // 基于手指的狀態(tài)判斷手勢 switch (fingers) { case 1: resultText = "一"; break; case 2: resultText = "二"; break; case 3: resultText = "三"; break; case 4: resultText = "四"; break; case 5: resultText = "五"; break; default: resultText = "無法識別手勢"; break; } // 更新檢測結(jié)果顯示 document.getElementById('result').innerText = resultText; // 每一幀進(jìn)行檢測 requestAnimationFrame(() => detectGesture(video, model)); } // 4. 判斷手勢 function detectFingers(landmarks) { let fingers = 0; // 判斷每個手指是否伸出:如果手指的末端關(guān)鍵點在其他關(guān)鍵點的上方,說明該手指伸出 // 對于每根手指,檢查其關(guā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]) { // 可能"四"或者"五"的手勢識別較弱,可以通過容忍度來改進(jìn) fingers = (fingers === 4) ? 5 : fingers; } return fingers; } async function main() { // 等待模型加載 const model = await loadHandPoseModel(); // 設(shè)置視頻流并開始播放 const video = await setupCamera(); video.play(); // 開始手勢識別 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>手勢識別</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>手勢識別</h1> <video id="video" width="640" height="480" autoplay></video> <div id="result">檢測中...</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實現(xiàn)手勢識別的示例詳解的文章就介紹到這了,更多相關(guān)JavaScript手勢識別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript如何讓select選擇框可輸入和可下拉選擇
我們知道一般select下拉框是只能選擇的,而有時我們會遇到下拉框中沒有要選擇的信息項或者下拉選項特別多時,需要允許用戶輸入想要的內(nèi)容,這篇文章主要給大家介紹了關(guān)于JavaScript如何讓select選擇框可輸入和可下拉選擇的相關(guān)資料,需要的朋友可以參考下2023-10-10JS調(diào)用頁面表格導(dǎo)出excel示例代碼
這篇文章主要介紹了JS調(diào)用頁面表格導(dǎo)出excel的具體實現(xiàn),需要的朋友可以參考下2014-03-03比較詳細(xì)的javascript DOM 學(xué)習(xí)筆記
看了很多的js dom學(xué)習(xí)資料,發(fā)現(xiàn)這個比較詳細(xì),特轉(zhuǎn)載給大家學(xué)習(xí)一下2008-06-06Javascript計算兩個marker之間的距離(Google Map V3)
做地圖開發(fā),最常用到的就是marker一些操作和交互。簡單介紹一下,兩個marker之間的距離計算,感興趣的朋友可以參考下哈,希望對你有所幫助2013-04-04JavaScript進(jìn)制轉(zhuǎn)換實現(xiàn)方法解析
這篇文章主要介紹了JavaScript進(jìn)制轉(zhuǎn)換實現(xiàn)方法,結(jié)合實例形式分析了JavaScript進(jìn)制轉(zhuǎn)換中十進(jìn)制與其他進(jìn)制轉(zhuǎn)換、以及隨機顏色生成相關(guān)操作技巧,需要的朋友可以參考下2020-01-01