我正在参加「掘金·启航计划」
分享一个AI小应用,借助于mediapipe的能力,对人体姿态进行识别,实现自动数单杠。
一、背景
引体向上是反应学生上肢肌肉力量和耐力的常用指标,这个项目简单实用,适用于初中至大学各个年级的同学。数单杠是一门技术活,下来手臂要伸直,上去下巴要过杠,因为主观因素的加入,导致标准不统一。有没有可能使用AI技术来智能判别单杠动作是否标准,直接用机器计数,这样既节省了人力,又统一了标准
前段时间了解学习了一下mediapipe,这是谷歌开源的一个框架,粗略看了一下,十分强大,特别是跨端实现的能力,【姿态估计】MediaPipe部分solution(手势,人体姿态,面部动作)的用法_mp.solutions.pose_Sciengineer-Mike的博客-CSDN博客 。
二、思路
联想到人体姿态估计的模型正好可以应用在单杠计数上面,因为这个模型可以给出33个人体关节点,几乎所有可以活动的大关节都有识别。
如果用这些点来判断单杠的动作,应该是完全行得通的,我们只要关注手臂的状态以及手臂和嘴巴的相对高度就可以。整个数单杠的功能大概想了一下有这么几步:
- 启动姿态识别,获取人体的关键点位坐标,主要涉及到(9,11,13,15,16)
- 一个完整的单杠过程包括:双臂自然伸直 >> 发力引体 >> 下颌过杠 >> 还原至手臂自然悬垂
- 如此循环至体测结束,显示结果
关于动作的判定是这么考虑的:全程手掌比手腕位置要高,双臂自然伸直的话,手肘的角度(即12-14-16)需要大于150°;下巴过杠的话,需要将左手和右手手腕连线(即15-16)作为水平线,下巴以嘴巴向下偏移x为准(即9),这个x根据实际再做调整。
👉需要注意的地方
需要注意的是,拉单杠是一个连续的过程,但是我们识别都是针对每一帧进行的,需要设置一个标记变量f=0,整个有点类似于斯密特触发器,当f=0时,从手臂悬垂到满足下巴过杠且时,置f=1(即为有效状态),然后等待手臂放下达到手臂悬垂条件时,置f=0,即为完成一个。
三、代码部分
有兴趣的同学可以看看mediapipe官方例子
这里使用python环境,安装mediapipe,pip install mediapipe
,就不展开了。
第一步导入模块:
import mediapipe as mp
import cv2
mpPose = mp.solutions.pose # 检测人体姿态
pose_mode = mpPose.Pose(min_detection_confidence=0.5,min_tracking_confidence=0.5) # 模式参数设置
mpDraw = mp.solutions.drawing_utils # 绘图
up=0 #单杠初始值为0
第二步设置循环:
while True:
# 读取摄像头
cap = cv2.VideoCapture('./test.mp4')
success, img = cap.read()
# 识别主进程
results = pose_mode.process(img)
# 判断是否有结果
if results.pose_landmarks:
...
...对结果进行动作判别,绘制识别结果,具体实现看下面步骤
cv2.imshow("img", img)
if cv2.waitKey(1)&0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
第三步动作判别并绘制识别结果:
# 绘制识别的结果
mpDraw.draw_landmarks(img, results.pose_landmarks,
mpPose.POSE_CONNECTIONS)
# 这里将手部的水平线用特殊颜色画出来
cv2.line(img, (points[3][0], points[3][1]),(points[4][0], points[4][1]), (0, 0, 255), 2)
# 判断 下颌是否超过手部,代表下巴过杠,则是一个合格,这里可以设置一个阈值,用于平衡角度的影响
distance, degree,angle,wrist_gt_elbow = pull_up_check(points)
if distance > -15 and degree < 10 and wrist_gt_elbow:
if biaoji == 1:
up += 1
i += 1
biaoji = 0
else:
# 判断由下巴过杠到手放直的工作
if biaoji == 0 and angle > 150:
biaoji = 1
cv2.putText(img, "pull-up:{}".format(up), (10, 50),
cv2.FONT_HERSHEY_PLAIN, 3, (0, 0, 255), 3)
def pull_up_check(list):
"""
判断拉上去和放下来
拉上去的话:满足下颌超过双手连线并且双手连线基本水平,且手掌是比手肘关节高的
放下来的话:满足手肘关节角度大于一定程度,这里直接返回角度,在主程序中判断
# 计算3个点的高度比较值,返回下颌-手部的像素值,分别是9-嘴角,15、16-左右手的手掌
[9, 11, 13, 15, 16, ]
"""
hands_top = min(list[3][1],list[4][1])
hands_degree = math.fabs(round(tan_2pots(list[3:5] ) ))
underjaw = list[0][1] | 0
angle = round(cal_ang(list[1],list[2],list[3]))
wrist_gt_elbow = list[2][1] > list[3][1]
return hands_top - underjaw ,hands_degree,angle,wrist_gt_elbow
def tan_2pots(list):
"""
#根据两个点的坐标,计算两个点连线的斜率
"""
return math.atan((list[0][1]-list[1][1]) / (list[0][0] - list[1][0]+ 0.01))*57.3
def cal_ang(point_1, point_2, point_3):
"""
根据三点坐标计算夹角
:param point_1: 点1坐标
:param point_2: 点2坐标
:param point_3: 点3坐标
:return: 返回任意角的夹角值,这里只是返回点2的夹角
"""
a=math.sqrt((point_2[0]-point_3[0])*(point_2[0]-point_3[0])+(point_2[1]-point_3[1])*(point_2[1] - point_3[1]))
b=math.sqrt((point_1[0]-point_3[0])*(point_1[0]-point_3[0])+(point_1[1]-point_3[1])*(point_1[1] - point_3[1]))
c=math.sqrt((point_1[0]-point_2[0])*(point_1[0]-point_2[0])+(point_1[1]-point_2[1])*(point_1[1]-point_2[1]))
# A=math.degrees(math.acos((a*a-b*b-c*c)/(-2*b*c)))
B=math.degrees(math.acos((b*b-a*a-c*c)/(-2*a*c)))
# C=math.degrees(math.acos((c*c-a*a-b*b)/(-2*a*b)))
return B
进一步完善
以上就是主要功能的代码实现,为了使用方便,还要再完善一下,添加几个小功能
- 一个开始计数的按钮:不用一直开着;
- 一个重置计数的按钮:抢杠或者换人的时候;
- 一个暂停计数的按钮:结束的时候,需要暂停,将结果保留下来
- 保留最后的结果画面
- 便于调试,将一些状态量直接显示在屏幕上
本文是通过绑定键盘按键来设置按钮的,当然,你也可以自行实现,添加在画面中。
按键 | 功能 |
---|---|
crtl+a | 暂停 |
crtl+z | 重置 |
space | 暂停/运行 |
crtl+c | 保存当前画面 |
ctrl+f | 退出 |
这部分是用了keyboard这个库,实现起来也不太复杂,就是控制几个标志变量,代码如下
import keyboard
up = 0
pause = 0
over = 0
active = 0
def key_press(arg):
print(arg)
global pause, over,active,up
if arg == 'pause':
pause = 1 - pause
elif arg == 'run':
pause = 0
elif arg == 'exit':
over = 1
elif arg == 'clear':
up = 0
elif arg == 'save':
cv2.imwrite('.output'+str(time.time())+'.jpg', img)
elif arg == 'active':
active = 1 - active
# 添加热键
keyboard.add_hotkey(hotkey='space', callback=key_press, args=('pause',))
keyboard.add_hotkey(hotkey='ctrl+z', callback=key_press, args=('clear',))
keyboard.add_hotkey(hotkey='ctrl+f', callback=key_press, args=('exit',))
keyboard.add_hotkey(hotkey='ctrl+a', callback=key_press, args=('active',))
keyboard.add_hotkey(hotkey='ctrl+c', callback=key_press, args=('save',))
测试
从小破站借了两个拉单杠的视频,测试了一下,总的效果还是不错的,主要看pull-up值,就是统计的个数:
结语
这个小应用不算复杂,加上一些辅助代码也只有一百多行,特别适合初学者。这次的分享就到这里,希望能帮助到大家。有兴趣的朋友可以进一步封装成一个软件,可以直接在笔记本上运行,只要外接一个usb摄像头就可以考核啦。
代码可以直接拷贝到本地运行测试一下:视频链接:https://code.juejin.cn/pen/7247105201889345594
参考资料
Google for Developers Blog – News about Web, Mobile, AI and Cloud (googleblog.com)
GitHub – google/mediapipe: Cross-platform, customizable ML solutions for live and streaming media.
【姿态估计】MediaPipe部分solution(手势,人体姿态,面部动作)的用法_mp.solutions.pose_Sciengineer-Mike的博客-CSDN博客
python 根据三点坐标计算夹角_python给定三个点求夹角_qq_1096260969的博客-CSDN博客