基于OpenCV+MediaPipe手部追踪

news2025/4/3 3:54:39

一、技术栈

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 time

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

while 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 = cTime
        cv2.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 time

cap = 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.shape

        if 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].label
                handLabel = "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)

        # 计算 FPS
        cTime = time.time()
        fps = 1 / (cTime - pTime)
        pTime = cTime
        cv2.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'):
        break

cap.release()
cv2.destroyAllWindows()

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

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

相关文章

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

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

蓝耘平台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;…

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年加入…

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…

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

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

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

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

【新模型速递】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;通杀图文…

【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;直接进入系统。 因此我们引入了使…

03-SpringBoot3入门-配置文件(自定义配置及读取)

1、自定义配置 # 自定义配置 zbj:user:username: rootpassword: 123456# 自定义集合gfs:- a- b- c2、读取 1&#xff09;User类 package com.sgu.pojo;import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.spring…

【蓝桥杯每日一题】3.28

&#x1f3dd;️专栏&#xff1a; 【蓝桥杯备篇】 &#x1f305;主页&#xff1a; f狐o狸x "今天熬的夜&#xff0c;会变成明天奖状的闪光点&#xff01;" 目录 一、唯一的雪花 题目链接 题目描述 解题思路 解题代码 二、逛画展 题目链接 题目描述 解题思路 解题代…

万字长文详解Text-to-SQL

什么是Text-to-SQL 在各个企业数据量暴涨的现在&#xff0c;Text-to-SQL越来越重要了&#xff0c;所以今天就来聊聊Text-to-SQL。 Text-to-SQL是一种将自然语言查询转换为数据库查询的技术。它可以让用户通过自然语言来查询数据库&#xff0c;而不需要编写复杂的SQL语句。 T…

【Linux】动静态库的制作与使用

一.对软硬链接的补充 1、无法对目录进行硬链接 为什么呢&#xff1f; 首先&#xff0c;我们在访问文件时&#xff0c;每一个文件都会有自己的dentry结构&#xff0c;这些结构会在内存中维护一棵路径树&#xff0c;来快速进行路径查找。但是如果某个节点直接使用硬链接到了根节…

ubuntu22.04 如何安装 ch341 驱动

前言 本篇是介绍ubuntu22.04如何安装 ch341 驱动&#xff0c;并对其中遇到的问题进行整理。 一、流程 1.1 查看CH340驱动 首先是查看ubuntu22.04系统自带的驱动&#xff0c;用以下命令即可 ls /lib/modules/$(uname -r)/kernel/drivers/usb/serial 然后会跳出以下界面&…

个人博客网站从搭建到上线教程

步骤1:设计个人网站 设计个人博客网站的风格样式,可以在各个模板网站上多浏览浏览,以便有更多设计网站风格样式的经验。 设计个人博客网站的内容,你希望你的网站包含哪些内容如你的个人基本信息介绍、你想分享的项目、你想分享的技术文档等等。 步骤2:选择开发技术栈 因…