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. 移动方向判断: 根据位移的角度,判断指尖的移动方向:
    4. 右: -45° < angle ≤ 45°
    5. 下: 45° < angle ≤ 135°
    6. 左: angle > 135° 或 angle ≤ -135°
    7. 上: -135° < angle ≤ -45°
    8. 轨迹记录与显示: 记录移动方向并绘制轨迹,形成手部移动的可视化路径。

    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. 捕获图像帧:

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

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

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

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

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

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

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

    21. 使用cv2.imshow显示处理后的图像。
    22. 通过按下'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实现一个基础的手部追踪与方向检测系统。该项目不仅具有实际应用价值,还为深入学习计算机视觉与机器学习技术提供了良好的实践平台。欢迎读者在此基础上进行更多的探索与创新!

    作者:岳麓未名

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python OpenCV MediaPipe 实现手部追踪与方向检测

    发表回复