人体姿态估计是计算机视觉领域的一项重要技术,它可以广泛应用于动作识别、人机交互、虚拟现实等领域。在本文中,我们将介绍如何使用Google开源的MediaPipe框架来实现人体全身骨骼关键点的实时检测。
MediaPipe是一个跨平台的机器学习框架,提供了丰富的计算机视觉模型,包括人体姿态估计、手势识别、人脸检测等。其中的Holistic模型可以同时检测人体的面部、手部和姿态关键点,为我们的应用提供了强大的支持。
MediaPipe框架介绍
MediaPipe 是 Google 推出的一个跨平台的机器学习解决方案框架,主要用于构建和部署多媒体感知管道。它以模块化的Pipeline架构为基础,支持高效的推理引擎,可以在 CPU 和 GPU 上实现实时的计算能力。
与传统的计算机视觉框架不同,MediaPipe 采用了一种全新的设计理念。它将各个功能模块解耦,形成了一个灵活的管道式工作流。开发者可以根据实际需求,自由地组合和裁剪这些模块,快速构建出各种多媒体感知应用。
在人体姿态估计领域,MediaPipe 提供了 Holistic 模型,可以检测全身 33 个关键点,涵盖面部、手掌、身体等部位。这种全面的关键点覆盖,使得 MediaPipe 在实现精准的人体姿态估计方面具有独特优势。接下来,我们就来detailed地探讨 MediaPipe Holistic 模型的使用方法。
骨骼关键点检测
MediaPipe Holistic 模型能够检测全身 33 个关键点,包括:
- 21 个手掌关键点
- 33 个面部关键点
- 25 个身体关键点
-
手部检测:
hands.Hands
: 这是 MediaPipe 中用于手部检测和跟踪的主要接口。它可以检测单只手或双手,并返回手部的关键点信息,包括掌心、手指关节等。hands.HandLandmark
: 这个枚举类型定义了手部的21个关键点,可以用于获取各个关键点的坐标信息。hands.HandDetector
: 这个接口提供了一些手部检测相关的参数设置,如置信度阈值、最大手部数量等。
-
身体检测:
pose.Pose
: 这是 MediaPipe 中用于姿态估计的主要接口。它可以检测全身关键点,包括头部、四肢等30多个关键点。pose.PoseLandmark
: 这个枚举类型定义了身体的33个关键点,可以用于获取各个关键点的坐标信息。pose.PoseDetector
: 这个接口提供了一些姿态估计相关的参数设置,如置信度阈值、模型选择等。
-
人脸检测:
face_detection.FaceDetection
: 这个接口用于检测人脸的位置和面部关键点。它可以同时检测多个人脸。face_mesh.FaceMesh
: 这个接口能够更细致地识别面部的68个关键点,可用于面部表情分析等应用。
环境准备
- 安装Python 3.7+
- 安装OpenCV:
pip install opencv-python
- 安装MediaPipe:
pip install mediapipe
i
代码实现
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领域的前沿动态。同时需要实现代码的可以通过公众号来找我要。