基于OpenCV+MediaPipe手部追踪

一、技术栈

1. OpenCV(Open Source Computer Vision Library)

  • 性质:开源计算机视觉(Library)

  • 主要功能

    • 图像/视频的基础处理(读取、裁剪、滤波、色彩转换等)

    • 特征检测(边缘、角点等)

    • 摄像头标定、目标跟踪等

  • 在项目中的作用

    • 负责视频流的捕获(cv2.VideoCapture

    • 图像格式转换(cv2.cvtColor

    • 最终结果的渲染显示(cv2.imshow

2. MediaPipe

  • 性质:由Google开发的跨平台机器学习框架(Framework)

  • 主要功能

    • 提供预训练的端到端模型(如手部关键点、人脸网格、姿态估计等)

    • 专注于实时感知任务(低延迟、移动端优化)

  • 在项目中的作用

    • 调用mediapipe.solutions.hands模型实现21个手部关键点检测

    • 输出关键点坐标,并通过mpDraw可视化

二、手部关键点检测

 (一)初始化

cap = cv2.VideoCapture(0)  # 通过OpenCV调用摄像头设备。参数0:默认摄像头(笔记本内置摄像头)。
mpHands = mp.solutions.hands  # MediaPipe的手部关键点检测模型(21个关键点)
hands = mpHands.Hands()  # 创建模型实例
mpDraw = mp.solutions.drawing_utils  # MediaPipe提供的绘图工具,用于在图像上绘制关键点和连线。
handLmsStyle = mpDraw.DrawingSpec(color=(0, 0, 255), thickness=5)  # 点的样式
handConStyle = mpDraw.DrawingSpec(color=(0, 255, 0), thickness=10)  # 线的样式
pTime = 0
cTime = 0

(二)关键点检测

ret, img = cap.read()  # 从摄像头持续读取视频帧。OpenCV默认格式为BGR格式if ret:imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 格式转换,MediaPipe模型要求输入为RGB格式# 手部关键点检测result = hands.process(imgRGB)# print(result.multi_hand_landmarks)imgHeight = img.shape[0]imgWidth = img.shape[1]

 重要代码解释:

 result = hands.process(imgRGB)
  • 底层过程:

        图像输入MediaPipe手部模型

        模型输出包含:

                multi_hand_landmarks:21个关键点的归一化坐标(0~1之间)

                multi_handedness:左右手判断

  • result 数据结构

        类型:List(列表)

        内容:每个元素代表一只手的21个关键点数据(因此result.multi_hand_landmarks最多                  有两个元素)

        层级关系:

result.multi_hand_landmarks[0]  # 第1只手.landmark[0]                  # 第1个关键点.x                          # 归一化x坐标 (0.0~1.0).y                          # 归一化y坐标 (0.0~1.0).z                          # 相对深度(值越小越靠近摄像头)

(三)可视化

         # 关键点可视化if result.multi_hand_landmarks:for handLms in result.multi_hand_landmarks:  # 遍历每只检测到的手mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS, handLmsStyle,handConStyle)  # 绘制手部关键点和骨骼连线for i, lm in enumerate(handLms.landmark):  # 遍历21个关键点xPos = int(lm.x * imgWidth)  # 将归一化x坐标转换为像素坐标yPos = int(lm.y * imgHeight)  # 将归一化y坐标转换为像素坐标cv2.putText(img, str(i), (xPos - 25, yPos + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255),2)  # 在关键点旁标注索引数字if i == 4:cv2.circle(img, (xPos, yPos), 20, (166, 56, 56), cv2.FILLED)print(i, xPos, yPos)  # 用深蓝色实心圆高亮标记拇指尖

  重要代码解释:

mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS, handLmsStyle, handConStyle)
  • 功能:绘制手部关键点和骨骼连线

  • 参数详解

    • img:目标图像(OpenCV格式)

    • handLms:当前手的关键点数据

    • mpHands.HAND_CONNECTIONS:预定义的关键点连接关系(如点0-1相连,点1-2相连等)

    • handLmsStyle:关键点绘制样式(红色圆点,厚度5)

    • handConStyle:连接线样式(绿色线条,厚度10)

xPos = int(lm.x * imgWidth)  # 将归一化x坐标转换为像素坐标
yPos = int(lm.y * imgHeight) # 将归一化y坐标转换为像素坐标
  • 坐标转换公式

像素坐标 = 归一化坐标 × 图像尺寸

        示例:

                若图像宽度imgWidth=640,某点lm.x=0.5 → xPos=320

                若图像高度imgHeight=480,某点lm.y=0.25 → yPos=120

cv2.putText(img, str(i), (xPos - 25, yPos + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255),2)  # 在关键点旁标注索引数字

(四)完整代码

import cv2
import mediapipe as mp
import timecap = cv2.VideoCapture(0)  # 通过OpenCV调用摄像头设备。参数0:默认摄像头(笔记本内置摄像头)。
mpHands = mp.solutions.hands  # MediaPipe的手部关键点检测模型(21个关键点)
hands = mpHands.Hands()  # 创建模型实例
mpDraw = mp.solutions.drawing_utils  # MediaPipe提供的绘图工具,用于在图像上绘制关键点和连线。
handLmsStyle = mpDraw.DrawingSpec(color=(0, 0, 255), thickness=5)  # 点的样式
handConStyle = mpDraw.DrawingSpec(color=(0, 255, 0), thickness=10)  # 线的样式
pTime = 0
cTime = 0while True:ret, img = cap.read()  # 从摄像头持续读取视频帧。OpenCV默认格式为BGR格式if ret:imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 格式转换,MediaPipe模型要求输入为RGB格式# 手部关键点检测result = hands.process(imgRGB)# print(result.multi_hand_landmarks)imgHeight = img.shape[0]imgWidth = img.shape[1]# 关键点可视化if result.multi_hand_landmarks:for handLms in result.multi_hand_landmarks:  # 遍历每只检测到的手mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS, handLmsStyle,handConStyle)  # 绘制手部关键点和骨骼连线for i, lm in enumerate(handLms.landmark):  # 遍历21个关键点xPos = int(lm.x * imgWidth)  # 将归一化x坐标转换为像素坐标yPos = int(lm.y * imgHeight)  # 将归一化y坐标转换为像素坐标cv2.putText(img, str(i), (xPos - 25, yPos + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255),2)  # 在关键点旁标注索引数字if i == 4:cv2.circle(img, (xPos, yPos), 20, (166, 56, 56), cv2.FILLED)print(i, xPos, yPos)  # 用深蓝色实心圆高亮标记拇指尖cTime = time.time()fps = 1 / (cTime - pTime)pTime = cTimecv2.putText(img, f"FPS:{int(fps)}", (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 3)cv2.imshow('img', img)if cv2.waitKey(1) == ord('q'):break

三、识别手指个数

(一)识别原理

1. 四指的判断

# 处理食指到小指
for i in range(1, 5):if handLms.landmark[fingerTips[i]].y < handLms.landmark[fingerTips[i] - 2].y:fingerState.append(1)  # 伸出else:fingerState.append(0)  # 弯曲

关键点:当食指远端指间关节(DIP,索引点8)在图像坐标系中的垂直位置高于近端指间关节(PIP,索引点6)时,即满足:y8<y6。

2. 拇指的判断

        # 镜像翻转修正左右手问题img = cv2.flip(img, 1)...# 处理拇指(默认掌心朝镜头)if handType == 'Right':  # 对于右手if handLms.landmark[fingerTips[0]].x < handLms.landmark[fingerTips[0] - 1].x:fingerState.append(1)  # 右手拇指伸出else:fingerState.append(0)  # 右手拇指弯曲else:  # 对于左手if handLms.landmark[fingerTips[0]].x > handLms.landmark[fingerTips[0] - 1].x:fingerState.append(1)  # 左手拇指伸出else:fingerState.append(0)  # 左手拇指弯曲

镜像翻转的必要性:
MediaPipe基于深度卷积神经网络(CNN)架构,通过学习手部关键点的空间分布模式来区分左手和右手。因此会将拇指在图像左侧的手识别为物理右手。而摄像头原始画面中物理右手拇指实际位于右侧,因此必须通过cv2.flip(img, 1)水平镜像翻转图像,才能使MediaPipe正确识别手型。

坐标判断的底层逻辑:
所有关键点坐标均基于镜像翻转后的图像空间,物理右手在翻转后的坐标系中表现为thumb_tip.x < thumb_ip.x。MediaPipe内部已自动处理坐标转换,开发者直接使用检测到的归一化坐标即可,无需额外计算原始坐标。

(二)完整代码

import cv2
import mediapipe as mp
import timecap = cv2.VideoCapture(0)
mpHands = mp.solutions.hands
hands = mpHands.Hands()
mpDraw = mp.solutions.drawing_utils
handLmsStyle = mpDraw.DrawingSpec(color=(0, 0, 255), thickness=5)  # 关键点样式
handConStyle = mpDraw.DrawingSpec(color=(0, 255, 0), thickness=10)  # 连接线样式
pTime = 0# 定义手指关键点
fingerTips = [4, 8, 12, 16, 20]  # 拇指、食指、中指、无名指、小指的指尖关键点索引while True:ret, img = cap.read()if ret:# 镜像翻转修正左右手问题img = cv2.flip(img, 1)imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)result = hands.process(imgRGB)imgHeight, imgWidth, _ = img.shapeif result.multi_hand_landmarks:for handLms, handInfo in zip(result.multi_hand_landmarks, result.multi_handedness):mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS, handLmsStyle, handConStyle)# 获取手的类型:左手还是右手handType = handInfo.classification[0].labelhandLabel = "Right Hand" if handType == 'Right' else "Left Hand"# 手势计数fingerState = []  # 记录每根手指是否伸出# 处理食指到小指for i in range(1, 5):if handLms.landmark[fingerTips[i]].y < handLms.landmark[fingerTips[i] - 2].y:fingerState.append(1)  # 伸出else:fingerState.append(0)  # 弯曲# 处理拇指(默认掌心朝镜头)if handType == 'Right':  # 对于右手if handLms.landmark[fingerTips[0]].x < handLms.landmark[fingerTips[0] - 1].x:fingerState.append(1)  # 右手拇指伸出else:fingerState.append(0)  # 右手拇指弯曲else:  # 对于左手if handLms.landmark[fingerTips[0]].x > handLms.landmark[fingerTips[0] - 1].x:fingerState.append(1)  # 左手拇指伸出else:fingerState.append(0)  # 左手拇指弯曲# 计算伸出的手指数量fingerCount = sum(fingerState)# 在图像上显示手指数量cv2.putText(img, f"{handLabel}: {fingerCount} Fingers", (50, 100),cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 3)# 计算 FPScTime = time.time()fps = 1 / (cTime - pTime)pTime = cTimecv2.putText(img, f"FPS:{int(fps)}", (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 3)cv2.imshow('Hand Tracking', img)if cv2.waitKey(1) == ord('q'):breakcap.release()
cv2.destroyAllWindows()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/76290.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习ML极简指南

机器学习是现代AI的核心&#xff0c;从推荐系统到自动驾驶&#xff0c;无处不在。但每个智能应用背后&#xff0c;都离不开那些奠基性的模型。本文用最简练的方式拆解核心机器学习模型&#xff0c;助你面试时对答如流&#xff0c;稳如老G。 线性回归 线性回归试图通过"最…

装饰器模式:如何用Java打扮一个对象?

引言装饰器模式具体实例共有接口类具体被装饰类抽象装饰器类具体装饰器类 测试装饰器模式的实际应用Java I/O 体系游戏开发中的角色装备系统 总结 引言 在生活中&#xff0c;我们都知道一句话&#xff0c;“人靠衣装马靠鞍”&#xff0c;如果想要让自己在别人眼里看起来更加好…

【Easylive】HikariCP 介绍

【Easylive】项目常见问题解答&#xff08;自用&持续更新中…&#xff09; 汇总版 HikariCP 是目前 Java 生态中最快、最轻量级的高性能 JDBC 连接池&#xff0c;被 Spring Boot 2.x 及更高版本选为 默认数据库连接池。它的名字来源于日语“光”&#xff08;Hikari&#xf…

清晰易懂的Cursor实现AI编程从安装到实战TodoList开发

一、Cursor简介与安装部署 什么是Cursor&#xff1f; Cursor是一款基于AI的智能代码编辑器&#xff0c;它集成了强大的AI编程助手功能&#xff0c;能够通过自然语言交互帮助开发者生成、优化和调试代码。与传统的代码编辑器不同&#xff0c;Cursor可以理解你的编程意图&#…

【Django】教程-2-前端-目录结构介绍

【Django】教程-1-安装创建项目目录结构介绍 3. 前端文件配置 3.1 目录介绍 在app下创建static文件夹, 是根据setting中的配置来的 STATIC_URL ‘static/’ templates目录&#xff0c;编写HTML模板&#xff08;含有模板语法&#xff0c;继承&#xff0c;{% static ‘xx’ …

注意!ChatGPT 全新 AI 图像功能延迟对免费用户开放

2025 年 3 月 25 日&#xff0c;OpenAI 正式宣布在 ChatGPT 中推出基于 GPT-4o 模型的全新原生图像生成功能。 这一功能允许用户通过对话生成和编辑图像&#xff0c;支持从写实风格到插图风格的多种形式。OpenAI 首席执行官萨姆・奥特曼&#xff08;Sam Altman&#xff09;在社…

优化webpack打包体积思路

Webpack 打包过大的问题通常会导致页面加载变慢&#xff0c;影响用户体验。可以从代码优化、依赖优化、构建优化等多个角度入手来减少打包体积&#xff1a; 代码优化 &#xff08;1&#xff09;按需加载&#xff08;代码拆分&#xff09; ① 路由懒加载 如果你的项目使用 Vu…

HarmonyOS Next~鸿蒙元服务开发指南:核心功能与实践

HarmonyOS Next&#xff5e;鸿蒙元服务开发指南&#xff1a;核心功能与实践 一、元服务核心概念 原子化服务定义 元服务&#xff08;原子服务&#xff09;是鸿蒙系统的核心架构单元&#xff0c;具备独立业务能力的轻量化服务模块&#xff0c;支持免安装、跨设备调用和智能分发…

git错误:fatal: detected dubious ownership in repository at xxxxxx

1、报错说明 这个错误通常是由于Git仓库目录的拥有者或权限问题引起的。Git检测到仓库目录的所有权可能存在不一致或不安全的情况。 通常导致此报错的可能原因&#xff1a; &#xff08;1&#xff09;文件或目录的拥有者不一致&#xff1a; 仓库目录中的某些文件或子目录可能…

【计算机网络】OSI七层模型完全指南:从比特流到应用交互的逐层拆解

OSI模型 导读一、概念二、模型层次结构2.1 物理层&#xff08;Physical Layer&#xff09;2.2 数据链路层&#xff08;Data Link Layer&#xff09;​2.3 ​网络层&#xff08;Network Layer&#xff09;​2.4 ​传输层&#xff08;Transport Layer&#xff09;​2.5 ​会话层&…

零基础被迫参加CTF比赛?CTF高频解题技巧与经验分享

CTF&#xff08;Capture The Flag&#xff09;比赛中的高频解题技巧通常涵盖了以下几类技术&#xff0c;涉及从逆向工程、二进制漏洞利用到Web安全、密码学等多个领域。以下是一些高频解题技巧&#xff1a; 1. 逆向工程&#xff08;Reverse Engineering&#xff09; 静态分析&a…

markdown 文件转 word

将 Markdown 文件转换为 Word 文档&#xff0c;可以使用多种方法。以下是几种常见的方法&#xff1a; 方法1&#xff1a;使用在线转换工具 有许多在线服务可以将 Markdown 文件转换为 Word 文档。例如&#xff1a; Pandoc - 一个非常流行的命令行工具&#xff0c;也可以用来转…

【第十三届“泰迪杯”数据挖掘挑战赛】【2025泰迪杯】【思路篇】A题解题全流程(持续更新)

【第十三届“泰迪杯”数据挖掘挑战赛】【2025泰迪杯】A题解题全流程-思路&#xff08;持续更新&#xff09; 写在前面&#xff1a; 1、A题、C题将会持续更新&#xff0c;陆续更新发布文章 2、赛题交流咨询Q群&#xff1a;1037590285 3、全家桶依旧包含&#xff1a; 代码、…

T11 TensorFlow入门实战——优化器对比实验

&#x1f368; 本文為&#x1f517;365天深度學習訓練營 中的學習紀錄博客&#x1f356; 原作者&#xff1a;K同学啊 | 接輔導、項目定制 一、前期准备 1. 导入数据 # Import the required libraries import pathlib import matplotlib.pyplot as plt import tensorflow as t…

Docker部署sprintboot后端项目

创建Docker网络 docker network create icjs 部署Redis docker run -d \--network icjs \--name redis \-p 6379:6379 \redis:latest数据持久化 docker run --restartalways --network icjs -p 6379:6379 --name redis -v /opt/docker/redis/redis.conf:/etc/redis/redis.c…

01小游戏

问题描述 小明得到了一个长度为 nn 的字符串 ss &#xff0c;该字符串都是由数字 00 和 11 组成&#xff0c;并且下标从 11 开始&#xff0c;小明现在需要对这个字符串进行 qq 次操作&#xff0c;每次操作包含以下两种操作之一&#xff1a; 操作 11 &#xff1a;小明查询该字符…

Androidstudio开发,实现商品分类

文章目录 1. 功能需求2. 代码实现过程1. 编写布局文件2. 创建商品分类&#xff08;Adapter&#xff09;适配器3. 实现商品分类Activity4. 在res/values/ 下新建 array.xml &#xff0c;用于添加商品分类数据5. 效果演示 6. 关于作者其它项目视频教程介绍 1. 功能需求 显示商品分…

Linux快速安装docker和docker-componse步骤

在 CentOS 7 上安装 Docker 和 Docker Compose 的步骤如下&#xff1a; 1. 安装 Docker 1.1. 更新系统 首先&#xff0c;确保你的系统是最新版本&#xff1a; sudo yum update -y1.2. 安装必要的包 安装 yum-utils&#xff0c;这是管理 YUM 源的工具&#xff1a; sudo yu…

VBA代码解决方案第二十三讲 EXCEL中,如何删除工作表中的空白行

《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程&#xff0c;目前已经是第三版修订了。这套教程定位于入门后的提高&#xff0c;在学习这套教程过程中&#xff0c;侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…

Pytorch--tensor.view()

在 PyTorch 中&#xff0c;tensor.view() 是一个常用的方法&#xff0c;用于改变张量&#xff08;Tensor&#xff09;的形状&#xff08;shape&#xff09;&#xff0c;但不会改变其数据本身。它类似于 NumPy 的 reshape()&#xff0c;但有一些关键区别。 1. 基本用法 import …