当前位置: 首页 > article >正文

基于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()

相关文章:

基于OpenCV+MediaPipe手部追踪

一、技术栈 1. OpenCV&#xff08;Open Source Computer Vision Library&#xff09; 性质&#xff1a;开源计算机视觉库&#xff08;Library&#xff09; 主要功能&#xff1a; 图像/视频的基础处理&#xff08;读取、裁剪、滤波、色彩转换等&#xff09; 特征检测&#xf…...

美甲预约管理系统基于Spring Boot SSM

目录 摘要 1. 引言‌ 1.1 研究背景与意义 1.2 国内外研究现状 ‌2. 系统需求分析‌ 2.1 功能需求 2.2 非功能需求 ‌3. 系统设计与实现‌ 3.1 系统架构设计 3.2 关键技术实现 3.3 系统模块实现 ‌3.3.1店铺管理‌ ‌3.3.2商品管理‌ ‌3.3.3用户管理‌ ‌3.3.4订…...

XXX软件系统研发技术手册模板

《XXX软件系统研发技术手册》 1. 引言 1.1 编写目的 说明手册的编写背景、目标读者及核心价值&#xff0c;例如&#xff1a; 本文档为开发团队提供完整的技术实现指南&#xff0c;涵盖系统设计、开发规范、部署方案等内容 。 1.2 术语定义 微服务&#xff1a;一种架构风格&a…...

AIGC(生成式AI)试用 29 -- 用AI写读书笔记

看了本书《繁荣与衰退》&#xff0c;电子版的。 没了了纸制的感觉&#xff0c;但笔记还是要写的&#xff0c;多少是个意思。 没有最懒&#xff0c;只有更懒&#xff0c;笔记用AI生成试试看。 >> 个人理解 经济增长与全球化挑战交织时期 以“创造性破坏”为核…...

十五届蓝桥杯省赛Java B组(持续更新..)

目录 十五届蓝桥杯省赛Java B组第一题&#xff1a;报数第二题&#xff1a;类斐波那契数第三题&#xff1a;分布式队列第四题&#xff1a;食堂第五题&#xff1a;最优分组第六题&#xff1a;星际旅行第七题&#xff1a;LITS游戏第八题&#xff1a;拼十字 十五届蓝桥杯省赛Java B…...

OpenAI发布的《Addendum to GPT-4o System Card: Native image generation》文件的详尽笔记

Native_Image_Generation_System_Card 文件基本信息 文件名称&#xff1a;《Addendum to GPT-4o System Card: Native image generation》发布机构&#xff1a;OpenAI发布日期&#xff1a;2025年3月25日主要内容&#xff1a;介绍GPT-4o模型中新增的原生图像生成功能&#xff…...

蓝耘平台API深度剖析:如何高效实现AI应用联动

目录 一、蓝耘平台简介 1.1 蓝耘通义大模型 1.2 蓝耘云计算资源 1.3 蓝耘API与微服务 二、 蓝耘平台应用联动场景 2.1 数据采集与预处理联动 2.2 模型推理与后端服务联动 2.3 跨平台联动 三、蓝耘平台注册体验功能 3.1 注册 3.2 体验蓝耘MaaS平台如何使用海螺AI生成视频…...

缓存 “三剑客”

缓存 “三剑客” 问题 如何保证 Redis 缓存和数据库的一致性&#xff1f; 1. 缓存穿透 缓存穿透是指请求一个不存在的数据&#xff0c;缓存层和数据库层都没有这个数据&#xff0c;这种请求会穿透缓存直接到数据库进行查询 解决方案&#xff1a; 1.1 缓存空值或特殊值 查一…...

ComfyUi教程之阿里的万象2.1视频模型

ComfyUi教程之阿里的万象2.1视频模型 官网Wan 2.1 特点 一、本地安装1.1克隆仓库1.2 安装依赖&#xff08;1.3&#xff09;下载模型&#xff08;1.4&#xff09;CUDA和CUDNN 二、 使用体验&#xff08;2.1&#xff09;官方例子&#xff08;2.2&#xff09;执行过程&#xff08;…...

⭐算法OJ⭐ 戳气球【动态规划】Burst Balloons

问题描述 LeetCode 312. 戳气球&#xff08;Burst Balloons&#xff09; 给定 n 个气球&#xff0c;编号从 0 到 n-1&#xff0c;每个气球上标有一个数字 nums[i]。戳破气球 i 可以获得 nums[left] * nums[i] * nums[right] 的硬币&#xff08;left 和 right 是 i 的相邻气球&…...

Leetcode 寻找两个正序数组的中位数

&#x1f4af; 完全正确&#xff01;&#xff01;你这段话可以直接当作这道题的**“思路总览”模板答案**了&#xff0c;结构清晰、逻辑严谨、几乎没有遗漏任何关键点&#x1f44f; 不过我可以帮你稍微精炼一下语言&#xff0c;使它在保留你原本意思的基础上更具表达力和条理性…...

C#测试Excel开源组件ExcelDataReader

使用微软的com组件Microsoft.office.Interop.Excel读写Excel文件虽然可用&#xff0c;但是列多、行多的时候速度很慢&#xff0c;之前测试过Sylvan.Data.Excel包的用法&#xff0c;如果只是读取Excel文件内容的话&#xff0c;还可以使用ExcelDataReader包&#xff0c;后者是C#开…...

手机零售行业的 AI 破局与创新降本实践 | OceanBase DB大咖说

OceanBase《DB 大咖说》第 20 期&#xff0c;我们邀请了九机与九讯云的技术总负责人&#xff0c;李远军&#xff0c;为我们分享手机零售企业如何借力分布式数据库OceanBase&#xff0c;赋能 AI 场景&#xff0c;并通过简化架构实现成本管控上的突破与创新。 李远军于2016年加入…...

SQL Server 动态构建 SQL 语句学习指南

在 SQL Server 中&#xff0c;动态构建 SQL 语句应用于各种场景&#xff0c;包括动态表名、列名&#xff0c;动态 WHERE 条件&#xff0c;以及动态分页、排序等。本文将详细计划如何在 SQL Server 中最佳实现动态 SQL 语句构建。 一、动态 SQL 的应用场景 动态表名或列名动态…...

Ceph与Bacula运维实战:数据迁移与备份配置优化指南

#作者&#xff1a;猎人 文章目录 1ceph数据迁移&&bacula配置调整1.1ceph数据迁移&&bacula配置调整1.2在备份服务器的ceph-client上mount cephfs文件系统1.2.1迁移数据1.2.2调整bacula-sd配置 1ceph数据迁移&&bacula配置调整 1.1ceph数据迁移&&am…...

Spring Boot分布式项目重试实战:九种失效场景与正确打开方式

在分布式系统架构中&#xff0c;网络抖动、服务瞬时过载、数据库死锁等临时性故障时有发生。本文将通过真实项目案例&#xff0c;深入讲解Spring Boot项目中如何正确实施重试机制&#xff0c;避免因简单粗暴的重试引发雪崩效应。 以下是使用Mermaid语法绘制的重试架构图和决策…...

Android OTA升级中SettingsProvider数据库升级的深度解析与完美解决方案

一、问题场景&#xff1a;OTA升级引发的系统属性"失效"之谜 在某Android 12.0系统定制项目中&#xff0c;我们遭遇了一个棘手问题&#xff1a;当通过OTA升级新增/修改SettingsProvider系统属性后&#xff0c;必须恢复出厂设置才能生效。这不仅导致用户数据丢失风险&…...

[Html]overflow: auto 失效原因,flex 1却未设置min-height overflow的几个属性以及应用场景

一、overflow: auto 失效原因分析 1. 未设置固定高度或宽度 • 当容器未定义具体尺寸时&#xff0c;浏览器无法判断内容是否溢出&#xff0c;导致滚动条不生效。需为容器添加 height 或 width 属性&#xff08;如 height: 300px&#xff09;。 • 示例&#xff1a; css .cont…...

SpringBoot整合LogStash,LogStash采集服务器日志

LogStash 1. 下载 版本支持兼容表https://www.elastic.co/cn/support/matrix 版本: 7.16.x 的最后一个版本 https://www.elastic.co/downloads/past-releases/logstash-7-16-3 需要提前安装好jdk1.8和ES, 此处不在演示 2. 安装 tar -xvf logstash-7.16.3-linux-x86_64.tar.gz…...

LLM - 推理大语言模型 DeepSeek-R1 论文简读

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/146840732 免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。 DeepSeek-R1 通过强化学习,显著提升大语言模型推理能力,使用特殊的训…...

目前市场上,好用的校招系统是哪个?

在数字化浪潮的推动下&#xff0c;校园招聘已从传统的“海投简历线下宣讲”模式全面转向智能化、数据化。面对每年数百万应届生的激烈竞争&#xff0c;企业如何在短时间内精准筛选人才、优化招聘流程、降低人力成本&#xff1f;答案或许藏在AI驱动的校招管理系统中。而在这场技…...

Oracle logminer详解

Oracle LogMiner 是 Oracle 数据库提供的一个内置工具&#xff0c;用于分析和挖掘数据库的在线重做日志文件&#xff08;Online Redo Log Files&#xff09;​和归档日志文件&#xff08;Archive Log Files&#xff09;​。通过 LogMiner&#xff0c;用户可以查看数据库的历史操…...

SharpBrowser:用C#打造超快的个性化开源浏览器!

推荐一个基于.Net 8 和 CefSharp开发的开源浏览器。 01 项目简介 SharpBrowser 是一个用 C# 和 CefSharp 开发的全功能网页浏览器。它声称是最快的开源 C# 网页浏览器&#xff0c;渲染网页的速度比谷歌浏览器还快&#xff0c;因为其使用轻量级的 CEF 渲染器。 经过比较所有可…...

【企业级Web应用中的文件下载处理:从S3预签名URL到压缩状态管理】

企业级Web应用中的文件下载处理&#xff1a;从S3预签名URL到压缩状态管理 1. 引言&#xff1a;一个看似简单的下载功能背后 在开发企业级Web应用时&#xff0c;文件下载功能看似简单&#xff0c;却常常隐藏着诸多技术挑战。近期&#xff0c;我们在一个xx申报系统项目中&#…...

【新模型速递】PAI一键云上零门槛部署DeepSeek-V3-0324、Qwen2.5-VL-32B

DeepSeek近期推出了“DeepSeek-V3-0324”版本&#xff0c;据测试在数学推理和前端开发方面的表现已优于 Claude 3.5 和 Claude 3.7 Sonnet。 阿里也推出了多模态大模型Qwen2.5-VL的新版本--“Qwen2.5-VL-32B-Instruct”&#xff0c;32B参数量实现72B级性能&#xff0c;通杀图文…...

[原创](Modern C++)现代C++的关键性概念: 如何利用多维数组的指针安全地遍历所有元素

[作者] 常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C、80x86ASM、Object Pascal、Objective-C、C#、R、Python、PHP、Perl、 开发工具: Visual Studio、Delphi、XCode、C …...

flask开发中设置Flask SQLAlchemy 的 db.Column 只存储非负整数(即 0 或正整数)

如果你想控制一个 Flask SQLAlchemy 的 db.Column 只存储非负整数&#xff08;即 0 或正整数&#xff09;&#xff0c;你可以在模型中使用验证来确保这一点。一种常见的方法是使用模型的 validate 方法或者在执行插入或更新操作时进行检查。 以下是实现这一目标的几种方法&…...

【Elasticsearch基础】基本核心概念介绍

Elasticsearch作为当前最流行的分布式搜索和分析引擎&#xff0c;其强大的功能背后是一套精心设计的核心概念体系。本文将深入解析Elasticsearch的五大核心概念&#xff0c;帮助开发者构建坚实的技术基础&#xff0c;并为高效使用ES提供理论支撑。 1 索引&#xff08;Index&…...

Github 热点项目 awesome-mcp-servers MCP 服务器合集,3分钟实现AI模型自由操控万物!

【今日推荐】超强AI工具库"awesome-mcp-servers"星数破万&#xff01; ① 百宝箱式服务模块&#xff1a;AI能直接操作浏览器、读文件、连数据库&#xff0c;比如让AI助手自动整理Excel表格&#xff0c;三分钟搞定全天报表&#xff1b; ② 跨领域实战利器&#xff1a;…...

SpringMVC 拦截器(Interceptor)

一.拦截器 假设有这么一个场景&#xff0c;一个系统需要用户登录才能进入&#xff0c;在检验完用户的信息后对页面进行了跳转。但是如果我们直接输入跳转的url&#xff0c;可以绕过用户信息校验&#xff08;用户登录&#xff09;&#xff0c;直接进入系统。 因此我们引入了使…...