在Python中实现人体运动识别,最常用且高效的方案是结合姿态估计 (提取人体关键点)和动作分类(识别具体运动)。以下是基于开源工具的完整实现方案,支持识别跑步、走路、跳跃、深蹲等常见运动。
核心思路
- 姿态估计 :使用
MediaPipe Pose提取人体33个关键点(如关节、四肢端点),获取关键点的坐标信息。 - 特征提取:基于关键点计算角度、距离等特征(如膝盖弯曲角度、手臂摆动幅度),这些特征能区分不同运动。
- 动作分类:使用预训练模型(或简单机器学习模型)对特征进行分类,识别具体运动类型。
环境准备
安装依赖库:
bash
pip install mediapipe opencv-python numpy scikit-learn pandas
mediapipe:快速人体姿态估计(轻量、实时)。opencv-python:读取视频/摄像头、图像处理。numpy:特征计算。scikit-learn:简单分类模型训练(可选,也可直接用预定义规则)。
方案1:基于规则的实时运动识别(快速实现)
适合简单场景(如识别跑步、走路、跳跃、静止),无需训练模型,通过关键点的运动特征(速度、角度)判断。
完整代码
python
import cv2
import mediapipe as mp
import numpy as np
from datetime import datetime
# 初始化MediaPipe Pose
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(
static_image_mode=False, # 视频模式(实时)
model_complexity=1, # 模型复杂度(0-2,1平衡速度和精度)
smooth_landmarks=True, # 平滑关键点
min_detection_confidence=0.5,
min_tracking_confidence=0.5
)
mp_drawing = mp.solutions.drawing_utils
# 运动状态定义
class MotionState:
STATIC = "静止"
WALKING = "走路"
RUNNING = "跑步"
JUMPING = "跳跃"
# 存储历史关键点(用于计算速度和变化)
history_landmarks = []
history_time = []
MAX_HISTORY = 10 # 历史帧数
JUMP_THRESHOLD = 0.15 # 跳跃时y方向位移阈值(相对身高)
RUN_THRESHOLD = 0.3 # 跑步时关键点速度阈值
WALK_THRESHOLD = 0.15 # 走路时关键点速度阈值
def calculate_body_height(landmarks):
"""计算人体身高(头顶到脚底的y轴距离)"""
# 头顶(0)和左脚跟(30)、右脚跟(31)的平均y坐标
head_y = landmarks[mp_pose.PoseLandmark.NOSE].y
foot_y = (landmarks[mp_pose.PoseLandmark.LEFT_HEEL].y +
landmarks[mp_pose.PoseLandmark.RIGHT_HEEL].y) / 2
return abs(foot_y - head_y)
def calculate_landmark_speed(landmarks, history_landmarks, history_time):
"""计算关键点的平均运动速度(像素/帧)"""
if len(history_landmarks) < 2:
return 0.0
# 取主要运动关键点(四肢+躯干)
key_landmarks = [
mp_pose.PoseLandmark.LEFT_WRIST, mp_pose.PoseLandmark.RIGHT_WRIST,
mp_pose.PoseLandmark.LEFT_ANKLE, mp_pose.PoseLandmark.RIGHT_ANKLE,
mp_pose.PoseLandmark.LEFT_HIP, mp_pose.PoseLandmark.RIGHT_HIP
]
total_speed = 0.0
count = 0
for idx in key_landmarks:
# 当前坐标和上一帧坐标
curr_x = landmarks[idx].x
curr_y = landmarks[idx].y
prev_x = history_landmarks[-1][idx].x
prev_y = history_landmarks[-1][idx].y
# 计算欧氏距离(归一化到0-1范围)
distance = np.sqrt((curr_x - prev_x)**2 + (curr_y - prev_y)**2)
total_speed += distance
count += 1
return total_speed / count
def detect_jump(landmarks, history_landmarks, body_height):
"""检测跳跃(重心y方向位移超过身高的阈值)"""
if len(history_landmarks) < 5: # 需要足够历史帧
return False
# 重心:骨盆(23,24)和胸部(11,12)的平均y坐标
curr_hip_y = (landmarks[mp_pose.PoseLandmark.LEFT_HIP].y +
landmarks[mp_pose.PoseLandmark.RIGHT_HIP].y) / 2
curr_chest_y = (landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER].y +
landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER].y) / 2
curr_center_y = (curr_hip_y + curr_chest_y) / 2
# 历史重心的最小值(最低位置)
history_centers = []
for hist in history_landmarks[-5:]:
hip_y = (hist[mp_pose.PoseLandmark.LEFT_HIP].y +
hist[mp_pose.PoseLandmark.RIGHT_HIP].y) / 2
chest_y = (hist[mp_pose.PoseLandmark.LEFT_SHOULDER].y +
hist[mp_pose.PoseLandmark.RIGHT_SHOULDER].y) / 2
history_centers.append((hip_y + chest_y) / 2)
min_history_center = min(history_centers)
# 重心上升距离超过身高的阈值 → 跳跃
jump_distance = abs(curr_center_y - min_history_center)
return jump_distance > body_height * JUMP_THRESHOLD
def classify_motion(landmarks, history_landmarks, history_time):
"""分类运动状态"""
if len(history_landmarks) < 3:
return MotionState.STATIC
# 计算人体身高(用于归一化)
body_height = calculate_body_height(landmarks)
if body_height < 0.1: # 异常身高(未检测到完整人体)
return MotionState.STATIC
# 检测跳跃(优先级最高)
if detect_jump(landmarks, history_landmarks, body_height):
return MotionState.JUMPING
# 计算关键点平均速度
avg_speed = calculate_landmark_speed(landmarks, history_landmarks, history_time)
# 根据速度分类跑步/走路/静止
if avg_speed > RUN_THRESHOLD:
return MotionState.RUNNING
elif avg_speed > WALK_THRESHOLD:
return MotionState.WALKING
else:
return MotionState.STATIC
# 读取摄像头(或视频文件)
cap = cv2.VideoCapture(0) # 0=默认摄像头,替换为视频路径可读取文件(如"video.mp4")
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# 转换为RGB(MediaPipe要求输入为RGB)
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
rgb_frame.flags.writeable = False # 提升性能
# 姿态估计
results = pose.process(rgb_frame)
rgb_frame.flags.writeable = True
# 绘制关键点和骨架
if results.pose_landmarks:
mp_drawing.draw_landmarks(
frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
mp_drawing.DrawingSpec(color=(0,255,0), thickness=2, circle_radius=2),
mp_drawing.DrawingSpec(color=(0,0,255), thickness=2)
)
# 存储当前关键点和时间
history_landmarks.append(results.pose_landmarks.landmark)
history_time.append(datetime.now())
# 限制历史帧数
if len(history_landmarks) > MAX_HISTORY:
history_landmarks.pop(0)
history_time.pop(0)
# 分类运动并显示
motion = classify_motion(results.pose_landmarks.landmark, history_landmarks, history_time)
cv2.putText(frame, f"Motion: {motion}", (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255,0,0), 3)
# 显示画面
cv2.imshow("Human Motion Recognition", frame)
# 按ESC退出
if cv2.waitKey(1) & 0xFF == 27:
break
# 释放资源
cap.release()
cv2.destroyAllWindows()
pose.close()
方案2:基于机器学习的运动识别(更高精度)
如果需要识别更复杂的运动(如深蹲、俯卧撑、瑜伽动作),需要通过标注数据训练分类模型。以下是关键步骤:
步骤1:数据采集与标注
- 录制不同运动的视频(如深蹲、俯卧撑、跑步、走路)。
- 使用
MediaPipe提取每个视频帧的33个关键点(x,y,z,visibility)。 - 手动标注每个帧对应的运动类别(如0=深蹲,1=俯卧撑,2=跑步)。
步骤2:特征工程
基于关键点计算更具区分度的特征:
- 关节角度(如膝盖、手肘、肩膀的弯曲角度)。
- 关键点相对位置(如手臂长度/身高比)。
- 运动速度和加速度(相邻帧关键点位移)。
示例:计算膝盖角度
python
def calculate_angle(a, b, c):
"""计算三点a-b-c的角度(a、b、c为关键点坐标(x,y))"""
a = np.array(a)
b = np.array(b)
c = np.array(c)
ab = a - b
bc = c - b
cos_angle = np.dot(ab, bc) / (np.linalg.norm(ab) * np.linalg.norm(bc))
angle = np.degrees(np.arccos(np.clip(cos_angle, -1.0, 1.0)))
return angle
# 计算左膝角度(关键点23=左髋,25=左膝,27=左脚踝)
left_hip = (landmarks[23].x, landmarks[23].y)
left_knee = (landmarks[25].x, landmarks[25].y)
left_ankle = (landmarks[27].x, landmarks[27].y)
knee_angle = calculate_angle(left_hip, left_knee, left_ankle)
步骤3:训练分类模型
使用scikit-learn训练随机森林/支持向量机(SVM)模型:
python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import joblib
# 假设已生成特征数据(columns为特征,label为运动类别)
data = pd.read_csv("motion_features.csv")
X = data.drop("label", axis=1)
y = data["label"]
# 划分训练集/测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 训练随机森林模型
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# 评估模型
y_pred = model.predict(X_test)
print(f"模型准确率:{accuracy_score(y_test, y_pred):.2f}")
# 保存模型
joblib.dump(model, "motion_classifier.pkl")
步骤4:实时推理
加载训练好的模型,替换方案1中的classify_motion函数:
python
# 加载模型
model = joblib.load("motion_classifier.pkl")
def extract_features(landmarks):
"""提取特征(与训练时一致)"""
features = []
# 1. 关节角度(膝、肘、肩、髋)
joints = [
# (髋, 膝, 踝) 左右
(23,25,27), (24,26,28),
# (肩, 肘, 腕) 左右
(11,13,15), (12,14,16)
]
for a, b, c in joints:
angle = calculate_angle(
(landmarks[a].x, landmarks[a].y),
(landmarks[b].x, landmarks[b].y),
(landmarks[c].x, landmarks[c].y)
)
features.append(angle)
# 2. 人体身高
features.append(calculate_body_height(landmarks))
# 3. 其他特征(如手臂长度、重心位置等)
# ... 补充更多特征 ...
return np.array(features).reshape(1, -1)
def classify_motion(landmarks):
"""使用训练好的模型分类"""
features = extract_features(landmarks)
label = model.predict(features)[0]
# 映射标签到运动名称
label_map = {0: "深蹲", 1: "俯卧撑", 2: "跑步", 3: "走路"}
return label_map.get(label, "未知")
关键优化建议
- 实时性 :使用
model_complexity=0(快速模型),减少历史帧数(MAX_HISTORY=5),降低CPU/GPU负载。 - 鲁棒性 :
- 过滤异常关键点(如
visibility<0.5的关键点视为未检测到)。 - 对关键点坐标进行平滑处理(如移动平均)。
- 过滤异常关键点(如
- 扩展运动类型 :
- 增加特征维度(如关节角度的变化率、肢体相对速度)。
- 使用深度学习模型(如LSTM、CNN)处理时序特征(适合连续动作识别)。
- 数据集:如果使用机器学习方案,建议每个运动类别采集至少500帧标注数据,确保模型泛化能力。
常见问题解决
- 关键点检测不稳定 :调整
min_detection_confidence和min_tracking_confidence(如0.3-0.5),确保光线充足。 - 运动误判 :优化阈值(如
JUMP_THRESHOLD),增加特征维度(如结合z轴坐标区分上下运动)。 - 速度慢:关闭不必要的绘制(如只绘制关键关节),使用GPU加速(MediaPipe自动支持CUDA)。
通过以上方案,可快速实现基础运动识别,或扩展为支持复杂动作的高精度识别系统。