★★★ 本文源自AI Studio社区精品项目,【点击此处】查看更多精品内容 >>>


避免每逢佳节胖三斤,我们一起来引体向上

使用PaddleHub人体姿态模型进行引体向上计数

1.项目介绍

马上过年了,大鱼大肉吃的High,肥膘也跟着一起来了,让我们一起来做引体向上保持良好的体型吧。在完成一个完整的引体向上的过程中需要众多背部骨骼肌和上肢骨骼肌的共同参与做功,是一项多关节复合动作练习,是较好的锻炼上肢的方法,是所有发展背部骨骼肌肌力和肌耐力的练习方式中参与肌肉最多、运动模式最复杂、发展背部骨骼肌的肌力和肌耐力最有效的练习方式,是最基本的锻炼背部的方法,是中考和高中体育会考的考试选择项目之一,是衡量男性体质的重要参考标准和项目之一。

本项目利用PaddleHub进行人体姿态识别,提取人体关键点后判断关键点运动轨迹,从而实现运动的技术。效果如下:

【使用AI进行引体向上实时计数】 https://www.bilibili.com/video/BV1484y1h7tE/?share_source=copy_web&vd_source=3f0a663ba8acf151e621b1c1515ea6c0
使用AI进行引体向上实时计数

该视频素材来自于【女生练引体向上两个月,看效果】 https://www.bilibili.com/video/BV1qe4y127pB/?share_source=copy_web&vd_source=3f0a663ba8acf151e621b1c1515ea6c0

2.详细说明

现有的开源项目中,都是对视频进行事后分析,计数大多都是通过matplotlib绘图后,本项目通过抽帧技术实现引体向上的实时计数与显示。
该项目的主要思路是:

  • 提取人体运动关键点
  • 确定引体向上评判逻辑
  • 显示并显示计数值

2.1 关键点检测

本项目使用了PaddleHub的human_pose_estimation_resnet50_mpii模型进行人体骨骼关键点检测(Pose Estimation),它是计算机视觉的基础算法之一,在很多cv任务中起到了基础性的作用,如行为识别、人物跟踪、步态识别等领域。模型描述如下:

模型名称human_pose_estimation_resnet50_mpii
类别图像-关键点检测
网络Pose_Resnet50
数据集MPII
是否支持Fine-tuning
模型大小121M
#导入库文件
import cv2
import paddlehub as hub
import time
from tqdm import tqdm
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0'                                    #设置使用GPU进行训练推理
pose_estimation = hub.Module(name="human_pose_estimation_resnet50_mpii")    #使用human_pose_estimation_resnet50_mpii模型
Download https://bj.bcebos.com/paddlehub/paddlehub_dev/human_pose_estimation_resnet50_mpii_1_2_0.zip
[##################################################] 100.00%
Decompress /home/aistudio/.paddlehub/tmp/tmpnz879h_c/human_pose_estimation_resnet50_mpii_1_2_0.zip
[##################################################] 100.00%


[2023-01-19 13:10:49,271] [    INFO] - Successfully installed human_pose_estimation_resnet50_mpii-1.2.0

pose_estimation.keypoint_detection函数API介绍

  • def keypoint_detection(images=None,
                           paths=None,
                           batch_size=1,
                           use_gpu=False,
                           output_dir='output_pose',
                           visualization=False):
    
    • 预测API,识别出人体骨骼关键点。
    • 参数
      • images (list[numpy.ndarray]): 图片数据,ndarray.shape 为 [H, W, C];
      • paths (list[str]): 图片的路径;
      • batch_size (int): batch 的大小;
      • use_gpu (bool): 是否使用 GPU,注意如果调用GPU必须指定GPU环境
      • visualization (bool): 是否将识别结果保存为图片文件;
      • output_dir (str): 图片的保存路径,默认设为 output_pose。
    • 返回
      • res (list): 识别元素的列表,列表元素为 dict,关键字为 ‘path’, ‘data’,相应的取值为:
        • path (str): 原图的路径;
        • data (OrderedDict): 人体骨骼关键点的坐标。

该函数返回的人体关键关键点有16个,分别是left_ankle、left_knee、left_hip、right_hip、right_knee、right_ankle、pelvis、thorax、upper_neck、head_top、right_wrist、right_elbow、right_shoulder、left_shoulder、left_elbow、left_wrist。
可以通过下面的代码来查看一下

img=cv2.imread('ytxs1-1.jpg') 
results = pose_estimation.keypoint_detection(images=[img],use_gpu=True,visualization=False)
ls=results[0]['data']['left_shoulder']
rs=results[0]['data']['right_shoulder']
un=results[0]['data']['upper_neck']
print(ls,rs,un)
for i in results[0]['data']:
    print(i,":",results[0]['data'][i])
if results[0]['data']:
    print("ok")
[170, 310] [255, 300] [210, 320]
left_ankle : [225, 720]
left_knee : [230, 600]
left_hip : [230, 470]
right_hip : [190, 470]
right_knee : [170, 600]
right_ankle : [170, 730]
pelvis : [215, 470]
thorax : [210, 300]
upper_neck : [210, 320]
head_top : [220, 190]
right_wrist : [285, 130]
right_elbow : [270, 280]
right_shoulder : [255, 300]
left_shoulder : [170, 310]
left_elbow : [145, 190]
left_wrist : [130, 120]
ok

2.2 引体向上判断逻辑

引体向上的判定首先是下颌是否过杠,每当下颌通过一次杠则判断为一次运动,此时需要判定两个要素:

-杠的判定

虽然杠的判定可以通过YOLO等识别模型来判定,但作为一个轻量级的模型来说,不应集成过多的模型。我们可以认为当双手握杠稳定后,两个腕关节的连接线即为横杠。可以通过cv连接两个腕关节关键点left_wrist、right_wrist直线标识出横杠位置。但是为了更好的优化显示,我们提取左右手腕的重点作为判据点。显示效果如下:

-头部的判定

模型中未给出下颌的关键点,只给出了head_top和upper_neck两个关键点,那么我们可以取两者的y轴关键点的平均值为头部判断依据。

示例代码如下:

count=0                                               #全局计数用
img = cv2.imread("ytxs1-1.jpg")
results = pose_estimation.keypoint_detection(images=[img],use_gpu=True,visualization=False)
head_top =results[0]['data']['head_top']            #获取头顶关键点
upper_neck = results[0]['data']['upper_neck']       #获取颈顶关键点
left_wrist=results[0]['data']['left_wrist']         #获取左腕关键点
right_wrist=results[0]['data']['right_wrist']       #获取右腕关键点
middle_point_x = int((head_top[0]+upper_neck[0])/2) #头部抽象点x
middle_point_y = int((head_top[1]+upper_neck[1])/2) #头部抽象点y
mw_x = int((left_wrist[0]+right_wrist[0])/2)        #横杠判据点x
mw_y = int((left_wrist[1]+right_wrist[1])/2)        #横杠判据点y
img = cv2.circle(img,(middle_point_x,middle_point_y), 10, (0,0,255), -1)
img = cv2.circle(img,(mw_x,mw_y), 10, (0,255,0), -1)
img  = cv2.line(img,left_wrist,right_wrist,(0,255,255),2,8)
if middle_point_y<mw_y:
           count+=1
img = cv2.putText(img, 'COUNT  '+str(count), (25 * 1, 50 * 1), cv2.FONT_HERSHEY_SIMPLEX, 1.25 * 1, (255, 0, 255), 2 * 1)
cv2.imwrite('out.jpg', img)
True

从上例的运行我们可以看到count的计数并不能准确,原因是当我们头部在判据点下的时候一直在跟着帧数在计数,python不像c语言一样具有静态变量,故需要一个解决的方案。
我们可以设置当头部关键点y值高于判据点时返回False,当低于判据点时返回True。则一个完整的计数周期内应当是如下序列:

F-F-F-F-F-F-T-T-T-T-T-F-F-F-F-F-T-T-T-T-T-T

用语言描述就是头y值超过判据点y值,然后头y值低于判据点y值即为一个完整的引体向上动作。
将判断逻辑写成函数如下:

def process_frame(img):
    flag=False
    # 将RGB图像输入模型,获取预测结果
    results = pose_estimation.keypoint_detection(images=[img],use_gpu=True,visualization=False)
    if results[0]['data']:
        #画出腕关节链接线
        head_top =results[0]['data']['head_top']
        upper_neck = results[0]['data']['upper_neck'] 
        left_wrist=results[0]['data']['left_wrist']
        right_wrist=results[0]['data']['right_wrist']
        middle_point_x = int((head_top[0]+upper_neck[0])/2)
        middle_point_y = int((head_top[1]+upper_neck[1])/2)
        mw_x = int((left_wrist[0]+right_wrist[0])/2)
        mw_y = int((left_wrist[1]+right_wrist[1])/2)
        img = cv2.circle(img,(middle_point_x,middle_point_y), 10, (0,0,255), -1)
        img = cv2.circle(img,(mw_x,mw_y), 10, (0,255,0), -1)
        img  = cv2.line(img,left_wrist,right_wrist,(0,255,255),2,8)
        if middle_point_y>mw_y:
            flag=False
        else:
            flag=True      
    return img,flag
# frame = cv2.imread("ytxs1-1.jpg")
# frame,new_flag = process_frame(frame)
# print(new_flag)
False
def generate_video(input_path):
    filehead = input_path.split('/')[-1]
    output_path = "out-" + filehead
    count=0
    last_flag=False
    print('视频开始处理',input_path)
    
    # 获取视频总帧数
    cap = cv2.VideoCapture(input_path) #调用视频进行时可以使用时使用
    # cap=cv2.VideoCapture(0)          #调用摄像头进行时可以使用时使用
    frame_count = 0
    while(cap.isOpened()):
        success, frame = cap.read()
        frame_count += 1
        if not success:
            break
    cap.release()
    print('视频总帧数为',frame_count)
    
    cap = cv2.VideoCapture(input_path)
    frame_size = (cap.get(cv2.CAP_PROP_FRAME_WIDTH), cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    fps = cap.get(cv2.CAP_PROP_FPS)

    out = cv2.VideoWriter(output_path, fourcc, fps, (int(frame_size[0]), int(frame_size[1])))
    
    # 进度条绑定视频总帧数
    with tqdm(total=frame_count-1) as pbar:
        try:
            while(cap.isOpened()):
                success, frame = cap.read()
                if not success:
                    break

                # 处理帧
                try:
                    frame,new_flag = process_frame(frame)
                    if new_flag != last_flag and last_flag==True: #判断动作完成逻辑
                        count+=1
                    frame = cv2.putText(frame, 'COUNT  '+str(count), (25 * 1, 50 * 1), cv2.FONT_HERSHEY_SIMPLEX, 1.25 * 1, (255, 0, 255), 2 * 1)
                    last_flag = new_flag
                except:
                    print('error')
                    pass
                
                if success == True:
                    # cv2.imshow('Video Processing', frame)
                    out.write(frame)

                    # 进度条更新一帧
                    pbar.update(1)

                # if cv2.waitKey(1) & 0xFF == ord('q'):
                    # break
        except:
            print('中途中断')
            pass

    cv2.destroyAllWindows()
    out.release()
    cap.release()
    print('视频已保存', output_path)
generate_video(input_path='work/Ytxs1.mp4')
视频开始处理 work/Ytxs1.mp4
视频总帧数为 3196


100%|██████████| 3195/3195 [01:28<00:00, 36.04it/s]


视频已保存 out-Ytxs1.mp4



视频已保存 out-Ytxs1.mp4

3、部署细节

该模型需要运行在GPU的环境下,否则可能会存在卡滞的情况。
单机安装

当依赖环境建立完毕后,将本项目的最后3段代码合成py文件在cmd环境下运行即可。

python test.py

4.总结

该项目使用了PaddleHub提供的human_pose_estimation_resnet50_mpii模型进行人体关键点识别后,使用判定引体向上判断逻辑进行动作判定,实现了引体向上动作的实时计数。

祝大家新年快乐,身体健康。

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐