Python实战项目:自学OpenCV手势识别AI功能

一、项目概述

通过摄像头实时捕捉手部动作,实现通过食指与中指捏合手势拖拽屏幕方块的效果。使用以下核心技术:

  • MediaPipe手部关键点检测

  • OpenCV图像处理

  • 几何距离计算

  • 透明物体绘制

  • 二、环境准备

    需安装的库

    python

    pip install opencv-python mediapipe numpy

    硬件要求

  • 普通USB摄像头

  • 支持Python3的环境

  • 三、代码结构解析

    3.1 初始化模块

    # MediaPipe初始化
    mp_drawing = mp.solutions.drawing_utils
    mp_hands = mp.solutions.hands
    hands = mp_hands.Hands(
        model_complexity=1,        # 平衡精度模式
        min_detection_confidence=0.5,
        min_tracking_confidence=0.5
    )
    
    # 摄像头初始化
    cap = cv2.VideoCapture(0)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

    此处我们可以改变参数会得到不一样效果,一般默认0.5。

    model_complexity 作用:控制手部检测模型的复杂度等级 取值范围:0(轻量级)、1(平衡)、2(高精度)。默认值为 1。
    min_detection_confidence 作用:手部检测的置信度阈值。 取值范围:[0.0, 1.0],默认值为 0.5。
    min_tracking_confidence 作用:手部跟踪的置信度阈值。 取值范围:[0.0, 1.0],默认值为 0.5

    3.2 核心逻辑实现

    手部关键点检测
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    results = hands.process(frame)
    
    # 绘制关键点连线
    mp_drawing.draw_landmarks(
        frame,
        hand_landmarks,
        mp_hands.HAND_CONNECTIONS,
        mp_drawing_styles.get_default_hand_landmarks_style(),
        mp_drawing_styles.get_default_hand_connections_style()
    )
    手势判定逻辑

    利用两点之间距离公式即可计算两指距离

    # 计算双指距离
    finger_len = math.hypot((index_finger_x-middle_finger_x),
                            (index_finger_y-middle_finger_y))
    
    # 拖拽判定条件
    if finger_len < 30:
        if (index_finger_x > square_x and 
            index_finger_x < (square_x+square_width) and
            index_finger_y > square_y and 
            index_finger_y < (square_y+square_width)):
            on_square = True
    透明方块实现(为了更好的看见手势放在方块上)
    overlay = frame.copy()
    cv2.rectangle(overlay, (square_x,square_y), 
                 (square_x+square_width, square_y+square_height),
                 square_color, -1)
    frame = cv2.addWeighted(overlay, 0.5, frame, 0.5, 0)

    四、技术原理解析

    4.1 MediaPipe工作机制

  • 21个手部关键点:每个手掌检测到21个关键点(0-20号)

  • 关键点索引

  • 8号点:食指指尖

  • 12号点:中指指尖

  • 置信度参数:通过调整检测/跟踪置信度平衡性能与精度

  • 4.2 拖拽算法

    1. 距离计算:使用math.hypot计算欧氏距离

    2. 位置偏移补偿:通过L1/L2记录初始接触偏移量(只要保持相对位置不变,那么方块就可以随着手移动)

    3. 状态机控制:on_square标志位管理拖拽状态

    # 记录初始偏移
    L1 = abs(index_finger_x - square_x)
    L2 = abs(index_finger_y - square_y)
    
    # 更新方块位置
    square_x = index_finger_x - L1
    square_y = index_finger_y - L2

    五、效果演示

    特征说明:

  • 双指间距<30时触发抓取

  • 蓝色表示拖拽中状态

  • 半透明视觉效果

  • 实时60FPS+的流畅度

  • 六、优化方向

    1. 手势优化:增加张开/握拳状态识别

    2. 多物体交互:实现多个可拖拽物体

    3. 3D控制:结合深度信息实现Z轴控制

    4. 碰撞检测:添加边界限制与物体碰撞

    七、完整代码

    import math
    import cv2
    import  numpy as np
    #mp相关参数
    import mediapipe as mp
    mp_drawing=mp.solutions.drawing_utils
    mp_drawing_styles=mp.solutions.drawing_styles
    mp_hands=mp.solutions.hands
    #model_complexity 作用:控制手部检测模型的复杂度等级 取值范围:0(轻量级)、1(平衡)、2(高精度)。默认值为 1。
    #min_detection_confidence 作用:手部检测的置信度阈值。 取值范围:[0.0, 1.0],默认值为 0.5。
    #min_tracking_confidence 作用:手部跟踪的置信度阈值。 取值范围:[0.0, 1.0],默认值为 0.5
    hands=mp_hands.Hands(
        model_complexity=1,
        min_detection_confidence=0.5,
        min_tracking_confidence=0.5
    )
    #获取摄像头视频流
    cap=cv2.VideoCapture(0)
    #获取画面宽度和高度(因为mediapipe框架中对长宽进行了计算)
    width=int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height=int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    #方块、坐标相关参数
    square_x=100
    square_y=100
    square_width=100
    square_height=100
    L1=0
    L2=0
    on_square=False
    square_color=(255,0,255)
    while True:
        #获取每一帧
        ret,frame=cap.read()
        #对图像进行处理
        frame=cv2.flip(frame,1)
        #mediapipe处理
        frame.flags.writeable=False
        frame=cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
        results=hands.process(frame)
        frame.flags.writeable=True
        frame=cv2.cvtColor(frame,cv2.COLOR_RGB2BGR)
        if results.multi_hand_landmarks:
            for hand_landmarks in results.multi_hand_landmarks:
                mp_drawing.draw_landmarks(
                    frame,
                    hand_landmarks,
                    mp_hands.HAND_CONNECTIONS,
                    mp_drawing_styles.get_default_hand_landmarks_style(),
                    mp_drawing_styles.get_default_hand_connections_style()
                )
            x_list=[]
            y_list=[]
            for landmark in hand_landmarks.landmark:
                x_list.append(landmark.x)
                y_list.append(landmark.y)
            #获取食指坐标
            index_finger_x=int(x_list[8]*width)
            index_finger_y=int(y_list[8]*height)
            #获取中指坐标
            middle_finger_x=int(x_list[12]*width)
            middle_finger_y=int(y_list[12]*height)
            #计算食指与中指指尖距离(两点之间坐标公式)
            finger_len=math.hypot((index_finger_x-middle_finger_x),(index_finger_y-middle_finger_y))
            #画一个圆来验证是否可以显示在屏幕上
            #cv2.circle(frame,(index_finger_x,index_finger_y),20,(255,0,255),-1)
            #如果距离小于30才开始激活
            if finger_len<30:
                #判断食指指尖在不在方块上
                if (index_finger_x > square_x and index_finger_x<(square_x+square_width)
                  and index_finger_y>square_y and index_finger_y<(square_y+square_width)):
                    if on_square==False:
                        #print("在方块上")
                        L1=abs(index_finger_x-square_x)
                        L2=abs(index_finger_y-square_y)
                        on_square=True
                    else:
                        pass
            else:
                on_square=False
                square_color=(255,0,255)
            if on_square:
                square_x=index_finger_x-L1
                square_y=index_finger_y-L2
                square_color=(255,0,0)
        #注意颜色是BGR的顺序,方块参数是对角线上的两点坐标,-1表示实心
        #cv2.rectangle(frame,(square_x,square_y),(square_x+square_width,square_height+square_y),(255,0,0),-1)
        #画一个半透明的方块
        overplay=frame.copy()
        cv2.rectangle(frame,(square_x,square_y),(square_x+
        square_width,square_height+square_y),square_color,-1)
        frame=cv2.addWeighted(overplay,0.5,frame,0.5,0)
        #使鼠标可以调整窗口的大小
        #cv2.namedWindow('window', cv2.WINDOW_NORMAL)
        cv2.imshow('window', frame)
        #当按esc键时退出窗口
        if cv2.waitKey(10) & 0xFF ==27:
            break
    #释放并关闭 窗口
    cap.release()
    cv2.destroyWindow('window')

    注意事项

    1. 手指尽量保持竖直状态便于识别

    2. 保证环境光照充足

    3. 可根据摄像头分辨率调整检测阈值

    以下是gitee上的源码:creative: 本科大学生个人学习与练习阶段 

    注:本人普通双非大学生一名,出于个人兴趣网上学习opencv,听从老师建议决定记录自己学习成果,如有问题欢迎各位点评。同时,如有一些不专业或是其他差错,请谅解! 

    作者:YBR-crime

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python实战项目:自学OpenCV手势识别AI功能

    发表回复