JavaScript实现手势识别的示例详解

 更新时间:2024年12月24日 11:00:46   作者:小噔小咚什么东东  
这篇文章主要为大家详细介绍了JavaScrip如何利用 TensorFlow.js 中的 HandPose 模型,实现一个基于视频流的手势识别系统,感兴趣的可以了解下

利用 TensorFlow.js 中的 HandPose 模型,实现一个基于视频流的手势识别系统,通过 HTML5 视频流获取摄像头数据,并结合 HandPose 模型来识别用户的手势。最终的目标是能够通过摄像头实时识别并显示手指数量的手势。

项目概述

这个项目的实现包括以下几个步骤:

  • 获取视频流并设置摄像头。
  • 加载 HandPose 模型,并显示加载进度。
  • 通过模型识别手势。
  • 基于手指的状态输出对应的手势数字(例如“1”代表一个手指,“2”代表两个手指等)。

获取视频流并设置摄像头

首先,需要通过浏览器的 navigator.mediaDevices.getUserMedia API 获取用户的摄像头视频流,并在网页上显示该视频流。具体代码如下:

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);
    };
  });
}

此函数将视频流显示在 HTML 中的 <video> 元素上,并在视频加载完成后返回该视频元素。

加载 HandPose 模型并显示加载进度

为了进行手势识别,我们需要加载 TensorFlow.js 提供的 HandPose 模型。HandPose 模型是一个用于估计手部关键点位置的深度学习模型,可以帮助我们识别手的姿势。 在加载 HandPose 模型时,我们希望显示加载进度。这是通过 progressCallback 实现的,它会在每次加载进度更新时触发:

async function loadHandPoseModel() {
  const loadingContainer = document.getElementById('loading-container');
  loadingContainer.style.display = 'block'; // 显示加载进度条

  const model = await handpose.load({
    flipHorizontal: false,  // 不要水平翻转模型
    progressCallback: (fraction) => {
      const progress = Math.round(fraction * 100); // 计算加载的百分比
      showLoadingProgress(progress); // 更新进度条
    }
  });

  loadingContainer.style.display = 'none'; // 隐藏加载进度条
  return model;
}

通过此函数,可以实时展示模型加载的进度条,提升用户体验。

识别手势

在加载完 HandPose 模型之后,我们就可以使用该模型来识别手势了。模型会估计手部的 21 个关键点位置,并通过这些关键点来推测手指是否伸展。 我们需要遍历这些关键点,分析每个手指是否伸出。如果手指的末端关键点(例如食指末端)高于相邻的关节点,表示该手指伸展。通过此逻辑,我们可以判断用户伸出的手指数量:

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;
}

通过此函数,能够判断出当前用户伸出的手指数量。

输出手势结果

通过识别出的手指数量,我们可以展示一个对应的数字手势。例如,如果用户伸出一个手指,我们显示“1”;如果伸出两个手指,则显示“2”,依此类推。手势识别结果会实时更新在网页上:

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; // 获取手部的关键点
  const fingers = detectFingers(landmarks);

  let resultText = "检测中...";

  // 基于手指的状态判断手势
  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;
  }

  // 更新检测结果显示
  document.getElementById('result').innerText = resultText;

  // 每一帧进行检测
  requestAnimationFrame(() => detectGesture(video, model));
}

每一帧都会重新检测手势,以确保输出实时更新。

整合所有部分

最后,将所有的功能整合在一起,启动视频流并开始手势识别:

async function main() {
  // 等待模型加载
  const model = await loadHandPoseModel();
  
  // 设置视频流并开始播放
  const video = await setupCamera();
  video.play();

  // 开始手势识别
  detectGesture(video, model);
}

main();

通过以上步骤,实现了一个基于 HandPose 模型的手势识别系统。利用浏览器提供的 getUserMedia API 获取视频流,并通过 TensorFlow.js 提供的 HandPose 模型来估计手的关键点,最终实现了对手势的实时识别。

完整代码

// 显示加载进度条
function showLoadingProgress(progress) {
  const progressBar = document.getElementById('progress');
  progressBar.style.width = progress + '%';
}

// 1. 获取视频流并设置摄像头
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 模型并显示加载进度
async function loadHandPoseModel() {
  const loadingContainer = document.getElementById('loading-container');
  loadingContainer.style.display = 'block'; // 显示加载进度条

  const model = await handpose.load({
    flipHorizontal: false,  // 不要水平翻转模型
    progressCallback: (fraction) => {
      const progress = Math.round(fraction * 100); // 计算加载的百分比
      showLoadingProgress(progress); // 更新进度条
    }
  });

  loadingContainer.style.display = 'none'; // 隐藏加载进度条
  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; // 获取手部的关键点
  const fingers = detectFingers(landmarks);

  let resultText = "检测中...";

  // 基于手指的状态判断手势
  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;
  }

  // 更新检测结果显示
  document.getElementById('result').innerText = resultText;

  // 每一帧进行检测
  requestAnimationFrame(() => detectGesture(video, model));
}

// 4. 判断手势
function detectFingers(landmarks) {
  let fingers = 0;

  // 判断每个手指是否伸出:如果手指的末端关键点在其他关键点的上方,说明该手指伸出
  // 对于每根手指,检查其关键点位置
  // 手指顺序: [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]) {
    // 可能"四"或者"五"的手势识别较弱,可以通过容忍度来改进
    fingers = (fingers === 4) ? 5 : fingers;
  }

  return fingers;
}

async function main() {
  // 等待模型加载
  const model = await loadHandPoseModel();
  
  // 设置视频流并开始播放
  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>

效果展示

到此这篇关于JavaScript实现手势识别的示例详解的文章就介绍到这了,更多相关JavaScript手势识别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JavaScript之移动端H5生成图片解决方案讲解

    JavaScript之移动端H5生成图片解决方案讲解

    这篇文章主要介绍了JavaScript之移动端H5生成图片解决方案讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • 使用JS给静态页面添加搜索功能的实现方法

    使用JS给静态页面添加搜索功能的实现方法

    静态页面通常由HTML、CSS 和 JavaScript 等静态文件组成,这些文件在服务器上不会动态生成或修改,所以加载速度通常比较快,本文给大家介绍了如何只使用JS给静态网页添加站内全局搜索功能,文中有详细的解决方案,需要的朋友可以参考下
    2023-11-11
  • LayerClose弹窗关闭刷新方法

    LayerClose弹窗关闭刷新方法

    今天小编就为大家分享一篇LayerClose弹窗关闭刷新方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • JavaScript中for循环与forEach的区别详解

    JavaScript中for循环与forEach的区别详解

    最近在代码审查中发现,团队成员对数组遍历方式的选择存在不少困惑,有人坚持使用传统的for循环,有人则偏爱forEach方法,双方各执一词,今天,我们就来深入探讨这个问题,帮助你在实际开发中做出更合理的选择,需要的朋友可以参考下
    2025-04-04
  • 原生JS实现拖拽功能

    原生JS实现拖拽功能

    这篇文章主要为大家介绍了JS实现拖拽功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • javascript里绝对用的上的字符分割函数总结

    javascript里绝对用的上的字符分割函数总结

    本节主要介绍了javascript里比较实用的字符分割函数的使用,需要的朋友可以参考下
    2014-07-07
  • js数字转中文两种实现方法

    js数字转中文两种实现方法

    在前端开发中有时候会需要到将阿拉伯数字转化为中文,当前做个记录,提供自己之后翻阅,这篇文章主要给大家介绍了关于js数字转中文两种实现方法的相关资料,需要的朋友可以参考下
    2023-10-10
  • 谈谈JavaScript的New关键字

    谈谈JavaScript的New关键字

    这篇文章主要和大家聊一聊JavaScript的New关键字,一般我们通过New关键字来创建一个类的实例对象,感兴趣的小伙伴们可以参考一下
    2016-08-08
  • js下拉选择框与输入框联动实现添加选中值到输入框的方法

    js下拉选择框与输入框联动实现添加选中值到输入框的方法

    这篇文章主要介绍了js下拉选择框与输入框联动实现添加选中值到输入框的方法,涉及javascript中onchange事件及页面元素遍历的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-08-08
  • 小程序开发实现access_token统一管理

    小程序开发实现access_token统一管理

    本文主要介绍了小程序开发实现access_token统一管理,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07

最新评论