Python OpenCV MediaPipe 实现手部追踪与方向检测

在现代计算机视觉领域,手部追踪技术在虚拟现实、增强现实、人机交互等多个领域中发挥着重要作用。本文将介绍如何利用OpenCV和MediaPipe库实现一个实时的手部追踪与方向检测项目。通过详细的项目介绍、所用技术链接、原理解析以及代码讲解,帮助读者深入理解该项目的实现过程。

一、项目介绍

本项目旨在通过摄像头实时捕捉手部动作,利用MediaPipe进行手部关键点检测,并结合OpenCV进行图像处理与显示。具体功能包括:

  • 实时检测手部关键点,特别是食指指尖的位置。
  • 计算指尖的移动方向(上、下、左、右)。
  • 记录并显示手部移动轨迹。

该项目可以应用于手势控制、虚拟绘画、交互式展示等场景。

二、所用技术与链接

  • OpenCV: 一个开源的计算机视觉库,提供了丰富的图像处理功能。
  • MediaPipe: Google开发的跨平台框架,提供高效的机器学习解决方案,特别适用于实时应用中的手部、面部检测等任务。
  • NumPy: 一个支持大规模多维数组与矩阵运算的Python库。
  • Matplotlib: 一个用于绘制静态、动态和交互式图表的Python 2D绘图库。

三、原理解析

1.MediaPipe手部检测

MediaPipe提供了高效的手部检测与追踪模型。通过该模型,可以实时检测手部的21个关键点,包括指尖、关节等。每个关键点由其在图像中的相对坐标(x, y, z)表示。

2.指尖位置与移动方向计算

在本项目中,我们主要关注食指指尖(Landmark 8)的坐标。通过连续帧中指尖位置的变化,可以计算出指尖的移动方向。具体步骤如下:

  1. 位置获取与平滑处理: 获取当前帧中指尖的坐标,并通过滑动窗口对位置进行平滑处理,以减少抖动。
  2. 位移计算: 计算当前帧与前一帧之间的位移(dx, dy)。
  3. 移动方向判断: 根据位移的角度,判断指尖的移动方向:
    • 右: -45° < angle ≤ 45°
    • 下: 45° < angle ≤ 135°
    • 左: angle > 135° 或 angle ≤ -135°
    • 上: -135° < angle ≤ -45°
  4. 轨迹记录与显示: 记录移动方向并绘制轨迹,形成手部移动的可视化路径。

3.移动锁定机制

为了避免频繁的方向判断和减少误判,项目中引入了移动锁定机制:

  • 当指尖的移动幅度超过设定的运动阈值时,锁定当前的移动方向,直到指尖回到中立区域(移动幅度低于中立阈值)才解除锁定。

四、代码讲解

以下是项目的主要代码部分及其详细解释:

1.导入必要的库

import cv2
import mediapipe as mp
from collections import deque
import numpy as np
import math
  • cv2: OpenCV库,用于图像捕捉与处理。
  • mediapipe: MediaPipe库,用于手部关键点检测。
  • deque: 双端队列,用于存储轨迹和位置,支持高效的添加与删除操作。
  • numpy: 用于数值计算,特别是均值计算。
  • math: 提供数学函数,如计算角度。

2.初始化MediaPipe手部检测

mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.7,
    min_tracking_confidence=0.7
)
mp_draw = mp.solutions.drawing_utils
  • mp.solutions.hands: 加载手部解决方案。
  • Hands: 初始化手部检测对象,设置参数:
    • static_image_mode=False: 启用视频模式,提高检测速度。
    • max_num_hands=1: 只检测一只手。
    • min_detection_confidence=0.7: 最小检测置信度。
    • min_tracking_confidence=0.7: 最小跟踪置信度。
  • mp_draw: 用于绘制手部关键点和连接线。

3.初始化摄像头与轨迹存储

cap = cv2.VideoCapture(0)
trajectory = deque(maxlen=1000)  # 用于存储轨迹
trajectory.append((0, 0))
point_x, point_y = 0, 0
  • cv2.VideoCapture(0): 打开默认摄像头。
  • trajectory: 存储手部移动轨迹,最多存储1000个点。
  • point_x, point_y: 当前轨迹点的坐标。

4.定义阈值与平滑参数

movement_threshold = 20  # 运动触发阈值
neutral_threshold = 10   # 中立解除阈值
prev_x, prev_y = None, None
movement_locked = False
smoothing_window = 5
positions = deque(maxlen=smoothing_window)
  • movement_threshold: 触发方向判断的最小位移。
  • neutral_threshold: 回到中立区域的位移阈值。
  • prev_x, prev_y: 前一帧的指尖位置。
  • movement_locked: 移动锁定状态标志。
  • smoothing_window: 平滑窗口大小,用于位置平滑。
  • positions: 存储最近的指尖位置,用于计算平滑后的平均位置。

5.角度计算函数

def calculate_angle(dx, dy):
    angle = math.degrees(math.atan2(dy, dx))
    return angle
  • 计算位移向量(dx, dy)的角度,返回角度值(度数)。

6.主循环

while True:
    success, img = cap.read()
    if not success:
        break

    # 翻转图像以获得镜像效果
    img = cv2.flip(img, 1)

    # 将图像转换为RGB
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    # 处理图像并检测手部
    results = hands.process(img_rgb)

    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            # 绘制手部关键点
            mp_draw.draw_landmarks(img, hand_landmarks, mp_hands.HAND_CONNECTIONS)

            # 获取食指指尖的坐标(Landmark 8)
            index_finger_tip = hand_landmarks.landmark[8]
            img_h, img_w, _ = img.shape
            tip_x, tip_y = int(index_finger_tip.x * img_w), int(index_finger_tip.y * img_h)

            # 添加到平滑滤波器
            positions.append((tip_x, tip_y))
            if len(positions) == 0:
                avg_x, avg_y = tip_x, tip_y
            else:
                avg_x = int(np.mean([p[0] for p in positions]))
                avg_y = int(np.mean([p[1] for p in positions]))

            if prev_x is not None and prev_y is not None:
                dx = avg_x - prev_x
                dy = avg_y - prev_y

                magnitude = math.sqrt(dx**2 + dy**2)

                direction = None

                if not movement_locked:
                    if magnitude > movement_threshold:
                        angle = calculate_angle(dx, dy)
                        print(f"Angle: {angle}")

                        # 判断方向
                        if -45 < angle <= 45:
                            direction = '右'
                            point_x += 1
                        elif 45 < angle <= 135:
                            direction = '下'
                            point_y -= 1
                        elif angle > 135 or angle <= -135:
                            direction = '左'
                            point_x -= 1
                        elif -135 < angle <= -45:
                            direction = '上'
                            point_y += 1

                        if direction:
                            print(f"手部向{direction}移动 (dx: {dx}, dy: {dy}, angle: {angle})")
                            trajectory.append((point_x, point_y))

                            # 锁定,直到手部回到中立区域
                            movement_locked = True

                else:
                    # 如果当前移动小于中立阈值,则解除锁定
                    if magnitude < neutral_threshold:
                        movement_locked = False

                prev_x, prev_y = avg_x, avg_y

            else:
                prev_x, prev_y = avg_x, avg_y

    else:
        prev_x, prev_y = None, None
        movement_locked = False

    # 显示摄像头画面
    cv2.imshow("Hand Tracking", img)

    # 按下 'q' 键退出
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放资源
cap.release()
cv2.destroyAllWindows()

7.主要步骤解析

  1. 捕获图像帧:

    • 通过cap.read()从摄像头获取当前帧。
    • 若获取失败,退出循环。
  2. 图像预处理:

    • 翻转图像以获得镜像效果,增强用户体验。
    • 将BGR图像转换为RGB,因为MediaPipe需要RGB格式的图像。
  3. 手部检测与关键点绘制:

    • 使用hands.process(img_rgb)进行手部检测。
    • 若检测到手部关键点,使用mp_draw.draw_landmarks在图像上绘制关键点和连接线。
  4. 获取食指指尖位置:

    • 获取Landmark 8(食指指尖)的相对坐标,并转换为图像中的像素坐标(tip_x, tip_y)。
  5. 位置平滑处理:

    • 将当前指尖位置添加到positions队列中,计算最近五帧的平均位置(avg_x, avg_y),以减少抖动影响。
  6. 位移与方向计算:

    • 若有前一帧的位置(prev_x, prev_y),计算位移向量(dx, dy)及其幅度(magnitude)。
    • 若未锁定移动状态,且幅度超过movement_threshold,则计算移动方向并更新轨迹。
    • 若已锁定移动状态,且幅度低于neutral_threshold,则解除锁定。
  7. 轨迹记录与显示:

    • 记录移动方向对应的点(point_x, point_y)到trajectory队列中,可用于后续的轨迹绘制与分析。
  8. 显示图像与退出机制:

    • 使用cv2.imshow显示处理后的图像。
    • 通过按下'q'键退出程序。

8.总代码

以下是整合后的完整代码,确保所有功能模块协同工作:

import cv2
import mediapipe as mp

from collections import deque
import numpy as np
import math

# 初始化MediaPipe手部检测
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.7,
    min_tracking_confidence=0.7
)
mp_draw = mp.solutions.drawing_utils

# 初始化摄像头
cap = cv2.VideoCapture(0)
# 设置图像的宽度和高度为512x384
# cap.set(cv2.CAP_PROP_FRAME_WIDTH, 512)  # 设置宽度为512
# cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 384)  # 设置高度为384
# 初始化Matplotlib

trajectory = deque(maxlen=1000)  # 用于存储轨迹
trajectory.append((0, 0))
point_x, point_y = 0, 0

# 定义移动阈值
movement_threshold = 20  # 运动触发阈值
neutral_threshold = 10   # 中立解除阈值

# 前一帧的位置
prev_x, prev_y = None, None

# 动作锁定状态
movement_locked = False

# 平滑参数
smoothing_window = 5
positions = deque(maxlen=smoothing_window)

def calculate_angle(dx, dy):
    angle = math.degrees(math.atan2(dy, dx))
    return angle

while True:
    success, img = cap.read()
    if not success:
        break

    # 翻转图像以获得镜像效果
    img = cv2.flip(img, 1)

    # 将图像转换为RGB
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    # 处理图像并检测手部
    results = hands.process(img_rgb)

    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            # 绘制手部关键点
            mp_draw.draw_landmarks(img, hand_landmarks, mp_hands.HAND_CONNECTIONS)

            # 获取食指指尖的坐标(Landmark 8)
            index_finger_tip = hand_landmarks.landmark[8]
            img_h, img_w, _ = img.shape
            tip_x, tip_y = int(index_finger_tip.x * img_w), int(index_finger_tip.y * img_h)

            # 添加到平滑滤波器
            positions.append((tip_x, tip_y))
            if len(positions) == 0:
                avg_x, avg_y = tip_x, tip_y
            else:
                avg_x = int(np.mean([p[0] for p in positions]))
                avg_y = int(np.mean([p[1] for p in positions]))

            if prev_x is not None and prev_y is not None:
                dx = avg_x - prev_x
                dy = avg_y - prev_y

                # 打印当前位移值
                #print(f"dx: {dx}, dy: {dy}")

                magnitude = math.sqrt(dx**2 + dy**2)

                direction = None

                if not movement_locked:
                    if magnitude > movement_threshold:
                        angle = calculate_angle(dx, dy)
                        print(f"Angle: {angle}")

                        # 判断方向
                        if -45 < angle <= 45:
                            direction = '右'
                            point_x += 1
                        elif 45 < angle <= 135:
                            direction = '下'
                            point_y -= 1
                        elif angle > 135 or angle <= -135:
                            direction = '左'
                            point_x -= 1
                        elif -135 < angle <= -45:
                            direction = '上'
                            point_y += 1

                        if direction:
                            print(f"手部向{direction}移动 (dx: {dx}, dy: {dy}, angle: {angle})")
                            trajectory.append((point_x, point_y))
                            # 更新绘图

                            xs, ys = zip(*trajectory)


                            # 锁定,直到手部回到中立区域
                            movement_locked = True

                else:
                    # 如果当前移动小于中立阈值,则解除锁定
                    if magnitude < neutral_threshold:
                        movement_locked = False

                prev_x, prev_y = avg_x, avg_y

            else:
                prev_x, prev_y = avg_x, avg_y

    else:
        prev_x, prev_y = None, None
        movement_locked = False

    # 显示摄像头画面
    cv2.imshow("Hand Tracking", img)

    # 按下 'q' 键退出
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放资源
cap.release()
cv2.destroyAllWindows()

五、结语

通过本文的介绍与代码讲解,读者可以了解到如何利用OpenCV和MediaPipe实现一个基础的手部追踪与方向检测系统。该项目不仅具有实际应用价值,还为深入学习计算机视觉与机器学习技术提供了良好的实践平台。欢迎读者在此基础上进行更多的探索与创新!

### 使用OpenCV实现手指检测和手势识别 #### 准备工作 为了使用OpenCV进行手指检测和手势识别,需要安装一些必要的库。这些库包括`cv2`(OpenCV),这是一个开源计算机视觉库,提供了多种计算机视觉算法;`imutils`,该库包含了许多简化OpenCV函数调用的帮助函数,非常适合于图像处理任务;还有`numpy`,这是用来处理多维数组和矩阵运算的强大库[^2]。 #### 加载并预处理视频流 通常情况下,会从摄像头读取实时视频帧作为输入源。对于每一帧图片,在执行任何操作之前都需要做一些基本的预处理工作,比如转换颜色空间、应用高斯模糊来减少噪声等。 ```python import cv2 import numpy as np cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() # 将BGR色彩模式转成HSV色彩模式 hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 对图像做高斯滤波去除噪音 blurred_frame = cv2.GaussianBlur(hsv_frame, (5, 5), 0) ``` #### 定义肤色范围并提取手部轮廓 接下来定义一个合理的阈值区间以分离出手掌部分。这一步骤依赖于具体的光照条件和个人皮肤色调的不同而有所变化。一旦确定好合适的参数后就可以利用此信息创建掩码,并从中找到最大的连通域代表手掌区域。 ```python # 假设已知目标肤色在HSV下的大致范围 lower_skin = np.array([0, 20, 70], dtype=np.uint8) upper_skin = np.array([20, 255, 255], dtype=np.uint8) mask = cv2.inRange(blurred_frame, lower_skin, upper_skin) contours, _ = cv2.findContours(mask.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) if contours: max_contour = max(contours, key=cv2.contourArea) ``` #### 计算凸包缺陷点数判断伸出几根手指 当获得了最大面积的手掌轮廓之后,可以进一步计算其外接矩形以及围绕着它的最小包围盒——即所谓的“凸包”。通过分析两者之间的差异(也就是所谓的‘凹陷’),能够推断出当前有多少个指尖超出了这个理想化的形状之外,从而得知究竟伸展了多少只指头出来。 ```python hull = cv2.convexHull(max_contour, returnPoints=False) defects = cv2.convexityDefects(max_contour, hull) finger_count = 0 for i in range(defects.shape[0]): s,e,f,d = defects[i][0] start = tuple(max_contour[s][0]) end = tuple(max_contour[e][0]) far = tuple(max_contour[f][0]) a = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2) b = math.sqrt((far[0] - start[0])**2 + (far[1] - start[1])**2) c = math.sqrt((end[0] - far[0])**2 + (end[1] - far[1])**2) angle = math.acos((b**2 + c**2 - a**2)/(2*b*c)) * 57 if angle <= 90 : finger_count += 1 print(f'Fingers detected: {finger_count}') ``` #### 利用手部关键点模型提高精度 除了上述基于传统方法的方式以外,还可以借助更先进的深度学习技术来进行更加精准的手势分类。例如采用MediaPipe Hands这样的预训练网络可以直接给出每一只手上的21个3D关键点位置,进而方便地解析复杂的手势动作[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

守正创新哦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值