MediaPipe 在Python中实现人体运动识别,最常用且高效的方案是结合**姿态估计**(提取人体关键点)和**动作分类**(识别具体运动)

在Python中实现人体运动识别,最常用且高效的方案是结合姿态估计 (提取人体关键点)和动作分类(识别具体运动)。以下是基于开源工具的完整实现方案,支持识别跑步、走路、跳跃、深蹲等常见运动。

核心思路

  1. 姿态估计 :使用MediaPipe Pose提取人体33个关键点(如关节、四肢端点),获取关键点的坐标信息。
  2. 特征提取:基于关键点计算角度、距离等特征(如膝盖弯曲角度、手臂摆动幅度),这些特征能区分不同运动。
  3. 动作分类:使用预训练模型(或简单机器学习模型)对特征进行分类,识别具体运动类型。

环境准备

安装依赖库:

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:数据采集与标注

  1. 录制不同运动的视频(如深蹲、俯卧撑、跑步、走路)。
  2. 使用MediaPipe提取每个视频帧的33个关键点(x,y,z,visibility)。
  3. 手动标注每个帧对应的运动类别(如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, "未知")

关键优化建议

  1. 实时性 :使用model_complexity=0(快速模型),减少历史帧数(MAX_HISTORY=5),降低CPU/GPU负载。
  2. 鲁棒性
    • 过滤异常关键点(如visibility<0.5的关键点视为未检测到)。
    • 对关键点坐标进行平滑处理(如移动平均)。
  3. 扩展运动类型
    • 增加特征维度(如关节角度的变化率、肢体相对速度)。
    • 使用深度学习模型(如LSTM、CNN)处理时序特征(适合连续动作识别)。
  4. 数据集:如果使用机器学习方案,建议每个运动类别采集至少500帧标注数据,确保模型泛化能力。

常见问题解决

  • 关键点检测不稳定 :调整min_detection_confidencemin_tracking_confidence(如0.3-0.5),确保光线充足。
  • 运动误判 :优化阈值(如JUMP_THRESHOLD),增加特征维度(如结合z轴坐标区分上下运动)。
  • 速度慢:关闭不必要的绘制(如只绘制关键关节),使用GPU加速(MediaPipe自动支持CUDA)。

通过以上方案,可快速实现基础运动识别,或扩展为支持复杂动作的高精度识别系统。

相关推荐
滨HI02 小时前
C++ opencv拟合直线
开发语言·c++·opencv
沐浴露z2 小时前
详解JDK21新特性【虚拟线程】
java·开发语言·jvm
山顶听风2 小时前
分页条初始化
python
艾莉丝努力练剑2 小时前
【C++:红黑树】深入理解红黑树的平衡之道:从原理、变色、旋转到完整实现代码
大数据·开发语言·c++·人工智能·红黑树
l1t3 小时前
利用DeepSeek优化SQLite求解数独SQL用于DuckDB
开发语言·数据库·sql·sqlite·duckdb
_OP_CHEN3 小时前
C++进阶:(七)红黑树深度解析与 C++ 实现
开发语言·数据结构·c++·stl·红黑树·红黑树的旋转·红黑树的平衡调整
NewsMash3 小时前
PyTorch之父发离职长文,告别Meta
人工智能·pytorch·python
硅农深芯3 小时前
如何使用ptqt5实现进度条的动态显示
开发语言·python·qt