基于OpenCV+MediaPipe手部追踪(1/2)

一、技术栈

1. OpenCV(Open Source Computer Vision Library)

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

  • 主要功能

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

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

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

  • 在项目中的作用

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

    • 图像格式转换(cv2.cvtColor

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

2. MediaPipe

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

  • 主要功能

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

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

  • 在项目中的作用

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

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

二、手部关键点检测

(一)初始化

python 复制代码
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

(二)关键点检测

python 复制代码
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]

重要代码解释:

python 复制代码
 result = hands.process(imgRGB)
  • 底层过程:

图像输入MediaPipe手部模型

模型输出包含:

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

multi_handedness:左右手判断

  • result 数据结构

类型:List(列表)

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

层级关系:

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

(三)可视化

python 复制代码
         # 关键点可视化
        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)  # 用深蓝色实心圆高亮标记拇指尖

重要代码解释:

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

  • 参数详解

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

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

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

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

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

python 复制代码
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

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

(四)完整代码

python 复制代码
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. 四指的判断

python 复制代码
# 处理食指到小指
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. 拇指的判断

python 复制代码
        # 镜像翻转修正左右手问题
        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内部已自动处理坐标转换,开发者直接使用检测到的归一化坐标即可,无需额外计算原始坐标。

(二)完整代码

python 复制代码
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()
相关推荐
星际码仔2 小时前
AutoGLM沉思,仍然没有摆脱DeepResearch产品的通病
人工智能·ai编程·chatglm (智谱)
喝拿铁写前端2 小时前
前端与 AI 结合的 10 个可能路径图谱
前端·人工智能
城电科技3 小时前
城电科技|零碳园区光伏太阳花绽放零碳绿色未来
人工智能·科技·能源
HyperAI超神经3 小时前
Stable Virtual Camera 重新定义3D内容生成,解锁图像新维度;BatteryLife助力更精准预测电池寿命
图像处理·人工智能·3d·数学推理·视频生成·对话语音生成·蛋白质突变
Chaos_Wang_3 小时前
NLP高频面试题(二十三)对抗训练的发展脉络,原理,演化路径
人工智能·自然语言处理
Yeats_Liao3 小时前
华为开源自研AI框架昇思MindSpore应用案例:基于MindSpore框架实现PWCNet光流估计
人工智能·华为
说私域4 小时前
人工智能赋能美妆零售数字化转型:基于开源AI大模型的S2B2C商城系统构建
人工智能·小程序·开源·零售
zew10409945884 小时前
基于深度学习的手势识别系统设计
人工智能·深度学习·算法·数据集·pyqt·yolov5·训练模型
weixin_478689764 小时前
pytorch与其他ai工具
人工智能·pytorch·python
豆芽8194 小时前
核函数(机器学习深度学习)
人工智能·深度学习