【mediapipe】实现卷腹运动识别(视频或摄像头)并计数
介绍
本人机器学习小白,通过语言大模型进行搜索,只用两小时不到就完成了该功能的初步效果,非常惊讶!比以往百度搜索的效率更高,结果也更准确。
思路
1.先通过mediapipe识别出人体关键节点
2.找到运动中会变化的节点,例如:我想实现卷腹计数,变化很大的节点就是膝盖
3.找到开始与结束时,关键节点的值,用来判断是否完成了一次动作
代码
from time import sleep
import cv2
import mediapipe as mp
# 识别摄像头
def readCamara():
# 初始化MediaPipe姿态检测对象
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.5, min_tracking_confidence=0.5)
# 打开摄像头获取视频流
cap = cv2.VideoCapture(1)
# 获取视频的帧率、宽度、高度等信息,用于设置输出视频参数
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 设置输出视频的编码格式(这里使用XVID编码,常用的还有MJPG等,根据系统支持情况选择)
fourcc = cv2.VideoWriter_fourcc(*'XVID')
# 创建VideoWriter对象,用于保存输出视频,指定输出文件名、编码格式、帧率、视频尺寸
out = cv2.VideoWriter('output_video.avi', fourcc, fps, (width, height))
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# 将图像转换为RGB格式(MediaPipe要求)
image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 进行人体姿态检测
results = pose.process(image_rgb)
if results.pose_landmarks:
# 遍历所有的人体姿态地标点
for idx, landmark in enumerate(results.pose_landmarks.landmark):
# 获取地标点的坐标(这里坐标是归一化的,范围0-1,后续可根据图像宽高转换为实际像素坐标)
x = int(landmark.x * frame.shape[1])
y = int(landmark.y * frame.shape[0])
# 在画面上绘制地标点(用小圆圈表示)
#cv2.circle(frame, (x, y), 5, (0, 255, 0), -1)
# 在地标点旁边显示对应的编号
#cv2.putText(frame, str(idx), (x + 10, y + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# 显示视频帧
cv2.imshow('Frame', frame)
# 将处理后的帧写入输出视频文件
out.write(frame)
if cv2.waitKey(1) & 0xFF == 27: # 按ESC键退出
break
cap.release()
cv2.destroyAllWindows()
pose.close()
# 识别视频
def readVedio():
# 用于记录初始膝盖位置纵坐标(这里简单以膝盖点24为例,也可综合考虑25等情况)
initial_knee_y = 150
# 卷腹计数
complete_count = 0
# 定义膝盖上升距离阈值(可根据实际情况调整),当膝盖上升超过此阈值认为可能完成一次卷腹
distance_threshold = 150
isCheck = False
count = 0
# 初始化MediaPipe姿态检测对象
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.5, min_tracking_confidence=0.5)
# 打开摄像头获取视频流
cap = cv2.VideoCapture("./output_video.avi")
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
count += 1
# 这里是为了控制视频在指定的帧数范围才进行识别,提高调试的效率
if count < 230 or count > 1000:
continue
print(count)
# 将图像转换为RGB格式(MediaPipe要求)
image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 进行人体姿态检测
results = pose.process(image_rgb)
if results.pose_landmarks:
# 遍历所有的人体姿态地标点
for idx, landmark in enumerate(results.pose_landmarks.landmark):
# 获取地标点的坐标(这里坐标是归一化的,范围0-1,后续可根据图像宽高转换为实际像素坐标)
x = int(landmark.x * frame.shape[1])
y = int(landmark.y * frame.shape[0])
if idx == 26 or idx == 25:
if isCheck == False:
if x <= 150:
isCheck = True
if isCheck == True:
if x - initial_knee_y > distance_threshold:
isCheck = False
complete_count += 1
print(idx,x,y)
# 在画面上绘制地标点(用小圆圈表示)
cv2.circle(frame, (x, y), 5, (0, 255, 0), -1)
# 在地标点旁边显示对应的编号
cv2.putText(frame, str(idx), (x + 10, y + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
mp.solutions.drawing_utils.draw_landmarks(frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)
# 在画面上显示计数结果
cv2.putText(frame, f"Count: {complete_count}", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
# 显示视频帧
cv2.imshow('Frame', frame)
if cv2.waitKey(1) & 0xFF == 27: # 按ESC键退出
break
#sleep(0.3)
cap.release()
cv2.destroyAllWindows()
pose.close()
if __name__ == '__main__':
readVedio()
效果
后续优化
目前是根据视频中膝盖最低点与最高点的差值来计算出是否完成一次运动,还存在一些弊端:
1.对视频的识别效果很好,但是通用性不强,当人离摄像头的远近不同时,其点位的值会同步发生变化
2.可以采用其他方式进行判断:
1.高地差的绝对值判断改为百分比判断
2.辅助其他节点进行判断,例如:膝盖的点(25,26)的x值大于等于臀部的点(23,24)的时候,记为完成一次运动
后记
根据该思路,可实现其他很多类的运动检测,如:俯卧撑,深蹲,等等,可通过计算百分比的值,播放不同的音乐,来让运动过程更有趣,就像 switch的健身环大冒险一样!包括也搜了一些市面上成熟的产品,例如:魔镜(一个卖大几千),看着是同样的效果