基于MediaPipe的全身骨骼关键点实时检测+手部关键点角度检测

人体姿态估计是计算机视觉领域的一项重要技术,它可以广泛应用于动作识别、人机交互、虚拟现实等领域。在本文中,我们将介绍如何使用Google开源的MediaPipe框架来实现人体全身骨骼关键点的实时检测。

MediaPipe是一个跨平台的机器学习框架,提供了丰富的计算机视觉模型,包括人体姿态估计、手势识别、人脸检测等。其中的Holistic模型可以同时检测人体的面部、手部和姿态关键点,为我们的应用提供了强大的支持。

MediaPipe框架介绍

MediaPipe 是 Google 推出的一个跨平台的机器学习解决方案框架,主要用于构建和部署多媒体感知管道。它以模块化的Pipeline架构为基础,支持高效的推理引擎,可以在 CPU 和 GPU 上实现实时的计算能力。

与传统的计算机视觉框架不同,MediaPipe 采用了一种全新的设计理念。它将各个功能模块解耦,形成了一个灵活的管道式工作流。开发者可以根据实际需求,自由地组合和裁剪这些模块,快速构建出各种多媒体感知应用。

在人体姿态估计领域,MediaPipe 提供了 Holistic 模型,可以检测全身 33 个关键点,涵盖面部、手掌、身体等部位。这种全面的关键点覆盖,使得 MediaPipe 在实现精准的人体姿态估计方面具有独特优势。接下来,我们就来detailed地探讨 MediaPipe Holistic 模型的使用方法。

骨骼关键点检测

MediaPipe Holistic 模型能够检测全身 33 个关键点,包括:

  • 21 个手掌关键点
  • 33 个面部关键点
  • 25 个身体关键点

  1. 手部检测:

    • hands.Hands: 这是 MediaPipe 中用于手部检测和跟踪的主要接口。它可以检测单只手或双手,并返回手部的关键点信息,包括掌心、手指关节等。
    • hands.HandLandmark: 这个枚举类型定义了手部的21个关键点,可以用于获取各个关键点的坐标信息。
    • hands.HandDetector: 这个接口提供了一些手部检测相关的参数设置,如置信度阈值、最大手部数量等。
  2. 身体检测:

    • pose.Pose: 这是 MediaPipe 中用于姿态估计的主要接口。它可以检测全身关键点,包括头部、四肢等30多个关键点。
    • pose.PoseLandmark: 这个枚举类型定义了身体的33个关键点,可以用于获取各个关键点的坐标信息。
    • pose.PoseDetector: 这个接口提供了一些姿态估计相关的参数设置,如置信度阈值、模型选择等。
  3. 人脸检测:

    • face_detection.FaceDetection: 这个接口用于检测人脸的位置和面部关键点。它可以同时检测多个人脸。
    • face_mesh.FaceMesh: 这个接口能够更细致地识别面部的68个关键点,可用于面部表情分析等应用。

环境准备

  1. 安装Python 3.7+
  2. 安装OpenCV: pip install opencv-python
  3. 安装MediaPipe: pip install mediapipei

代码实现

import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector   # 手部检测方法
import mediapipe as mp
import time
import autopy
import cv2
import mediapipe as mp
import math
import cv2
import mediapipe as mp
import math

def vector_2d_angle(v1,v2):
    '''
        求解二维向量的角度
    '''
    v1_x=v1[0]
    v1_y=v1[1]
    v2_x=v2[0]
    v2_y=v2[1]
    try:
        angle_= math.degrees(math.acos((v1_x*v2_x+v1_y*v2_y)/(((v1_x**2+v1_y**2)**0.5)*((v2_x**2+v2_y**2)**0.5))))
    except:
        angle_ =65535.
    if angle_ > 180.:
        angle_ = 65535.
    return angle_
def hand_angle(hand_):
    '''
        获取对应手相关向量的二维角度,根据角度确定手势
    '''
    angle_list = {}
    #---------------------------- thumb 大拇指角度
    angle_ = vector_2d_angle(
        ((int(hand_[0][0])- int(hand_[4][0])),(int(hand_[0][1])-int(hand_[4][1]))),
        ((int(hand_[0][0])- int(hand_[5][0])),(int(hand_[0][1])- int(hand_[5][1])))
        )
    angle_list['4']=angle_
    #---------------------------- index 食指角度
    angle_ = vector_2d_angle(
        ((int(hand_[0][0])-int(hand_[6][0])),(int(hand_[0][1])- int(hand_[6][1]))),
        ((int(hand_[0][0])- int(hand_[9][0])),(int(hand_[0][1])- int(hand_[9][1])))
        )
    angle_list['8']=angle_
    #---------------------------- middle 中指角度
    angle_ = vector_2d_angle(
        ((int(hand_[0][0])- int(hand_[10][0])),(int(hand_[0][1])- int(hand_[10][1]))),
        ((int(hand_[0][0])- int(hand_[14][0])),(int(hand_[0][1])- int(hand_[14][1])))
        )
    angle_list['12']=angle_
    #---------------------------- ring 无名指角度
    angle_ = vector_2d_angle(
        ((int(hand_[0][0])- int(hand_[14][0])),(int(hand_[0][1])- int(hand_[14][1]))),
        ((int(hand_[0][0])- int(hand_[18][0])),(int(hand_[0][1])- int(hand_[18][1])))
        )
    angle_list['16']=angle_
    #-----2号点
    angle_ = vector_2d_angle(
        ((int(hand_[1][0])- int(hand_[2][0])),(int(hand_[1][1])- int(hand_[2][1]))),
        ((int(hand_[2][0])- int(hand_[3][0])),(int(hand_[2][1])- int(hand_[3][1])))
        )
    angle_list['2']=angle_
    #-----3号点
    angle_ = vector_2d_angle(
        ((int(hand_[2][0])- int(hand_[3][0])),(int(hand_[2][1])- int(hand_[3][1]))),
        ((int(hand_[3][0])- int(hand_[4][0])),(int(hand_[3][1])- int(hand_[4][1])))
        )
    angle_list['3']=angle_
    #-----5号点
    angle_ = vector_2d_angle(
        ((int(hand_[0][0])- int(hand_[5][0])),(int(hand_[0][1])- int(hand_[5][1]))),
        ((int(hand_[5][0])- int(hand_[6][0])),(int(hand_[5][1])- int(hand_[6][1])))
        )
    angle_list['5']=angle_
    #-----6号点
    angle_ = vector_2d_angle(
        ((int(hand_[5][0])- int(hand_[6][0])),(int(hand_[5][1])- int(hand_[6][1]))),
        ((int(hand_[6][0])- int(hand_[7][0])),(int(hand_[6][1])- int(hand_[7][1])))
        )
    angle_list['6']=angle_
    #-----7号点
    angle_ = vector_2d_angle(
        ((int(hand_[6][0])- int(hand_[7][0])),(int(hand_[6][1])- int(hand_[7][1]))),
        ((int(hand_[7][0])- int(hand_[8][0])),(int(hand_[7][1])- int(hand_[8][1])))
        )
    angle_list['7']=angle_
    #-----9号点
    angle_ = vector_2d_angle(
        ((int(hand_[0][0])- int(hand_[9][0])),(int(hand_[0][1])- int(hand_[9][1]))),
        ((int(hand_[9][0])- int(hand_[10][0])),(int(hand_[9][1])- int(hand_[10][1])))
        )
    angle_list['9']=angle_
    #-----10号点
    angle_ = vector_2d_angle(
        ((int(hand_[9][0])- int(hand_[10][0])),(int(hand_[9][1])- int(hand_[10][1]))),
        ((int(hand_[10][0])- int(hand_[11][0])),(int(hand_[10][1])- int(hand_[11][1])))
        )
    angle_list['10']=angle_
    #-----11号点
    angle_ = vector_2d_angle(
        ((int(hand_[10][0])- int(hand_[11][0])),(int(hand_[10][1])- int(hand_[11][1]))),
        ((int(hand_[11][0])- int(hand_[12][0])),(int(hand_[11][1])- int(hand_[12][1])))
        )
    angle_list['11']=angle_
    #-----13号点
    angle_ = vector_2d_angle(
        ((int(hand_[0][0])- int(hand_[13][0])),(int(hand_[0][1])- int(hand_[13][1]))),
        ((int(hand_[13][0])- int(hand_[14][0])),(int(hand_[13][1])- int(hand_[14][1])))
        )
    angle_list['13']=angle_
    #-----14号点
    angle_ = vector_2d_angle(
        ((int(hand_[13][0])- int(hand_[14][0])),(int(hand_[13][1])- int(hand_[14][1]))),
        ((int(hand_[14][0])- int(hand_[15][0])),(int(hand_[14][1])- int(hand_[15][1])))
        )
    angle_list['14']=angle_
    #-----15号点
    angle_ = vector_2d_angle(
        ((int(hand_[14][0])- int(hand_[15][0])),(int(hand_[14][1])- int(hand_[15][1]))),
        ((int(hand_[15][0])- int(hand_[16][0])),(int(hand_[15][1])- int(hand_[16][1])))
        )
    angle_list['15']=angle_
    #-----17号点
    angle_ = vector_2d_angle(
        ((int(hand_[0][0])- int(hand_[17][0])),(int(hand_[0][1])- int(hand_[17][1]))),
        ((int(hand_[17][0])- int(hand_[18][0])),(int(hand_[17][1])- int(hand_[18][1])))
        )
    angle_list['17']=angle_
    #-----18号点
    angle_ = vector_2d_angle(
        ((int(hand_[17][0])- int(hand_[18][0])),(int(hand_[17][1])- int(hand_[18][1]))),
        ((int(hand_[18][0])- int(hand_[19][0])),(int(hand_[18][1])- int(hand_[19][1])))
        )
    angle_list['18']=angle_
    #-----19号点
    angle_ = vector_2d_angle(
        ((int(hand_[18][0])- int(hand_[19][0])),(int(hand_[18][1])- int(hand_[19][1]))),
        ((int(hand_[19][0])- int(hand_[20][0])),(int(hand_[19][1])- int(hand_[20][1])))
        )
    angle_list['19']=angle_
    return angle_list

def h_gesture(angle_list):
    '''
        # 二维约束的方法定义手势
        # fist five gun love one six three thumbup yeah
    '''
    thr_angle = 65.
    thr_angle_thumb = 53.
    thr_angle_s = 49.
    gesture_str = None
    if 65535. not in angle_list:
        if (angle_list[0]>thr_angle_thumb) and (angle_list[1]>thr_angle) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
            gesture_str = "fist"
        elif (angle_list[0]<thr_angle_s) and (angle_list[1]<thr_angle_s) and (angle_list[2]<thr_angle_s) and (angle_list[3]<thr_angle_s) and (angle_list[4]<thr_angle_s):
            gesture_str = "five"
        elif (angle_list[0]<thr_angle_s)  and (angle_list[1]<thr_angle_s) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
            gesture_str = "gun"
        elif (angle_list[0]<thr_angle_s)  and (angle_list[1]<thr_angle_s) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]<thr_angle_s):
            gesture_str = "love"
        elif (angle_list[0]>5)  and (angle_list[1]<thr_angle_s) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
            gesture_str = "one"
        elif (angle_list[0]<thr_angle_s)  and (angle_list[1]>thr_angle) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]<thr_angle_s):
            gesture_str = "six"
        elif (angle_list[0]>thr_angle_thumb)  and (angle_list[1]<thr_angle_s) and (angle_list[2]<thr_angle_s) and (angle_list[3]<thr_angle_s) and (angle_list[4]>thr_angle):
            gesture_str = "three"
        elif (angle_list[0]<thr_angle_s)  and (angle_list[1]>thr_angle) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
            gesture_str = "thumbUp"
        elif (angle_list[0]>thr_angle_thumb)  and (angle_list[1]<thr_angle_s) and (angle_list[2]<thr_angle_s) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
            gesture_str = "two"
    return gesture_str


def detect():
    mp_drawing = mp.solutions.drawing_utils
    mp_hands = mp.solutions.hands
    hands = mp_hands.Hands(
            static_image_mode=False,
            max_num_hands=1,
            min_detection_confidence=0.75,
            min_tracking_confidence=0.75)
    cap = cv2.VideoCapture(0)
    while True:
        ret,frame = cap.read()
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        frame= cv2.flip(frame,1)
        results = hands.process(frame)
        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)
                hand_local = []
                for i in range(21):
                    x = hand_landmarks.landmark[i].x*frame.shape[1]
                    y = hand_landmarks.landmark[i].y*frame.shape[0]
                    hand_local.append((x,y))
                if hand_local:
                    angle_list = hand_angle(hand_local)
                    gesture_str = h_gesture(angle_list)
                    cv2.putText(frame,gesture_str,(0,100),0,1.3,(0,0,255),3)
        cv2.imshow('MediaPipe Hands', frame)
        if cv2.waitKey(1) & 0xFF == 27:
            break
    cap.release()


def calculate_distance(p1, p2):
    """
    计算两个点之间的距离
    """
    distance = np.linalg.norm(p2 - p1)
    return distance
#(1)导数视频数据
wScr, hScr = autopy.screen.size()   # 返回电脑屏幕的宽和高(1920.0, 1080.0)
wCam, hCam = 1280, 720   # 视频显示窗口的宽和高
pt1, pt2 = (100,100), (1100, 500)   # 虚拟鼠标的移动范围,左上坐标pt1,右下坐标pt2

cap = cv2.VideoCapture(r'C:\\Users\\Administrator\\Desktop\\mouse\\1.mp4')  # 0代表自己电脑的摄像头
cap.set(3, wCam)  # 设置显示框的宽度1280
cap.set(4, hCam)  # 设置显示框的高度720


pTime = 0  # 设置第一帧开始处理的起始时间

pLocx, pLocy = 0, 0  # 上一帧时的鼠标所在位置

smooth = 4  # 自定义平滑系数,让鼠标移动平缓一些

# (2)接收手部检测方法
detector = HandDetector(staticMode=False,  # 视频流图像
                        maxHands=1,  # 最多检测一只手
                        detectionCon=0.8,  # 最小检测置信度
                        minTrackCon=0.5)  # 最小跟踪置信度

# (3)处理每一帧图像
while True:

    # 图片是否成功接收、img帧图像
    success, img = cap.read()

    # 翻转图像,使自身和摄像头中的自己呈镜像关系
    img = cv2.flip(img, flipCode=1)  # 1代表水平翻转,0代表竖直翻转

    # 在图像窗口上创建一个矩形框,在该区域内移动鼠标
    cv2.rectangle(img, pt1, pt2, (0, 255, 255), 5)

    # (4)手部关键点检测
    # 传入每帧图像, 返回手部关键点的坐标信息(字典),绘制关键点后的图像
    hands, img = detector.findHands(img, flipType=False)  # 上面反转过了,这里就不用再翻转了

    # print(hands)

    # 如果能检测到手那么就进行下一步
    if hands:

        # 获取手部信息hands中的21个关键点信息
        lmList = hands[0]['lmList']  # hands是由N个字典组成的列表,字典包每只手的关键点信息
        #每个节点的夹角
        angle_list = hand_angle(lmList)
        pp=[4,8,12,16,2,3,5,6,7,9,10,11,13,14,15,17,18,19]
        for i in pp:
            font = cv2.FONT_HERSHEY_SIMPLEX
            org = (int(lmList[i][0]), int(lmList[i][1]))
            fontScale = 1
            color = (255, 255, 255)
            text=angle_list['{}'.format(i)]
            text= "{:.2f}".format(text)
            text=str(text)
            thickness = 2
            img = cv2.putText(img, text, org, font, fontScale, color, thickness, cv2.LINE_AA)
        # 获取食指指尖坐标,和食指指尖坐标
        x1, y1 = lmList[8][0:2]  # 食指尖的关键点索引号为8
        x2, y2 = lmList[4][0:2]  # 中指指尖索引12
        m=np.array([x1,y1])
        s=np.array([x2,y2])

        #大拇指和食指距离
        dist=calculate_distance(m,s)
        # (5)检查哪个手指是朝上的
        fingers = detector.fingersUp(hands[0])  # 传入
        # print(fingers) 返回 [0,1,1,0,0] 代表 只有食指和中指竖起

        # 如果食指竖起且中指弯下,就认为是移动鼠标
        if fingers[1] == 1 and fingers[2] == 0:
            # 开始移动时,在食指指尖画一个圆圈,看得更清晰一些
            cv2.circle(img, (x1, y1), 15, (255, 255, 0), cv2.FILLED)  # 颜色填充整个圆

            # (6)确定鼠标移动的范围
            # 将食指的移动范围从预制的窗口范围,映射到电脑屏幕范围
            x3 = np.interp(x1, (pt1[0], pt2[0]), (0, wScr))
            y3 = np.interp(y1, (pt1[1], pt2[1]), (0, hScr))

            # (7)平滑,使手指在移动鼠标时,鼠标箭头不会一直晃动
            cLocx = pLocx + (x3 - pLocx) / smooth  # 当前的鼠标所在位置坐标
            cLocy = pLocy + (y3 - pLocy) / smooth

            # (8)移动鼠标
            autopy.mouse.move(cLocx, cLocy)  # 给出鼠标移动位置坐标

            # 更新前一帧的鼠标所在位置坐标,将当前帧鼠标所在位置,变成下一帧的鼠标前一帧所在位置
            pLocx, pLocy = cLocx, cLocy

        # (9)如果食指和中指都竖起,指尖距离小于某个值认为是点击鼠标
        if fingers[1] == 1 and fingers[2] == 1:  # 食指和中指都竖起

            # 计算食指尖和中指尖之间的距离distance,绘制好了的图像img,指尖连线的信息info
            distance, info, img = detector.findDistance((x1, y1), (x2, y2), img)
            # print(distance)

            # 当指间距离小于50(像素距离)就认为是点击鼠标
            if distance < 50:
                # 在食指尖画个绿色的圆,表示点击鼠标
                cv2.circle(img, (x1, y1), 15, (0, 255, 0), cv2.FILLED)

                # 点击鼠标
                autopy.mouse.click()

    # (10)显示图像
    # 查看FPS
    cTime = time.time()  # 处理完一帧图像的时间
    fps = 1 / (cTime - pTime)
    pTime = cTime  # 重置起始时间

    # 在视频上显示fps信息,先转换成整数再变成字符串形式,文本显示坐标,文本字体,文本大小
    cv2.putText(img, str(int(fps)), (70, 50), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)

    # 显示图像,输入窗口名及图像数据
    cv2.imshow('image', img)
    if cv2.waitKey(1) & 0xFF == 27:  # 每帧滞留20毫秒后消失,ESC键退出
        break

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

本文详细介绍了如何使用 Google 开源的 MediaPipe 框架实现全身骨骼关键点检测,并展示了在静态图像和动态视频中的检测效果。我们发现,MediaPipe Holistic 模型凭借其出色的性能和全面的关键点覆盖,在实现精准的人体姿态估计方面具有独特优势。

如果您对Transuent模型的改进和深度学习技术感兴趣,欢迎关注我的微信公众号 "AI代码 Insights"。在这里,我会定期分享最新的人工智能技术、深度学习算法和实践经验,与大家共同探讨AI领域的前沿动态。同时需要实现代码的可以通过公众号来找我要。

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值