python案例如何判断假摔和夸张表演行为?

wen python案例 3

如何用Python判断假摔和夸张表演行为

这是一个计算机视觉+行为分析的综合性问题,通常用于体育赛事分析、视频内容审核等场景,下面我给你一个可落地的技术方案和代码案例。

python案例如何判断假摔和夸张表演行为?


核心思路

假摔(Diving/Simulation)和夸张表演的判断本质是多模态行为识别,需要综合:

维度 特征 判断依据
姿态 身体角度、接触点、重心变化 无明显接触却倒地
运动学 加速度、角速度、倒地时间 倒地过于"平滑"或"戏剧化"
时序 接触→反应的延迟 反应延迟过长(表演痕迹)
面部 表情夸张、痛苦表情 痛苦程度与冲击不匹配
上下文 比赛阶段、球员历史 惯犯、关键时段

完整技术架构

视频输入 → 人体姿态估计 → 关键点时序 → 特征工程 → 分类模型 → 判定结果
                ↓
          目标检测(球员/接触)
                ↓
          面部表情识别(可选)

代码实现案例

环境准备

pip install mediapipe opencv-python numpy scikit-learn torch

姿态关键点提取(MediaPipe)

import cv2
import mediapipe as mp
import numpy as np
mp_pose = mp.solutions.pose
def extract_pose_sequence(video_path, max_frames=300):
    """提取视频中人体关键点的时序序列"""
    cap = cv2.VideoCapture(video_path)
    poses = []
    with mp_pose.Pose(
        static_image_mode=False,
        model_complexity=1,
        min_detection_confidence=0.5,
        min_tracking_confidence=0.5
    ) as pose:
        while cap.isOpened() and len(poses) < max_frames:
            ret, frame = cap.read()
            if not ret:
                break
            rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            result = pose.process(rgb)
            if result.pose_landmarks:
                # 33个关键点,每个(x,y,z,visibility)
                frame_kp = np.array([
                    [lm.x, lm.y, lm.z, lm.visibility]
                    for lm in result.pose_landmarks.landmark
                ])
                poses.append(frame_kp)
            else:
                poses.append(np.zeros((33, 4)))
    cap.release()
    return np.array(poses)  # shape: (T, 33, 4)

假摔关键特征工程

def compute_dive_features(poses, fps=30):
    """
    从姿态序列中提取假摔判别特征
    poses: (T, 33, 4)
    """
    import math
    T = len(poses)
    # MediaPipe关键点索引
    LEFT_SHOULDER, RIGHT_SHOULDER = 11, 12
    LEFT_HIP, RIGHT_HIP = 23, 24
    LEFT_ANKLE, RIGHT_ANKLE = 27, 28
    LEFT_KNEE, RIGHT_KNEE = 25, 26
    NOSE = 0
    features = {}
    # --- 1. 重心高度变化(假摔特征:重心突然下降) ---
    hips_y = (poses[:, LEFT_HIP, 1] + poses[:, RIGHT_HIP, 1]) / 2
    features['center_of_mass_drop'] = float(hips_y.max() - hips_y.min())
    features['drop_speed'] = float(np.max(np.abs(np.diff(hips_y))) * fps)
    # --- 2. 头部加速度(表演型倒地头部动作夸张) ---
    nose_y = poses[:, NOSE, 1]
    nose_accel = np.diff(nose_y, n=2) * fps**2
    features['head_max_accel'] = float(np.max(np.abs(nose_accel)))
    features['head_accel_variance'] = float(np.var(nose_accel))
    # --- 3. 躯干角度变化(是否主动前扑/后仰) ---
    shoulder_mid = (poses[:, LEFT_SHOULDER, :2] + poses[:, RIGHT_SHOULDER, :2]) / 2
    hip_mid = (poses[:, LEFT_HIP, :2] + poses[:, RIGHT_HIP, :2]) / 2
    torso_vec = shoulder_mid - hip_mid
    torso_angle = np.arctan2(torso_vec[:, 1], torso_vec[:, 0])
    features['torso_angle_change'] = float(np.abs(np.diff(torso_angle)).sum())
    # --- 4. 四肢"对称性"------真受伤常单侧,表演常夸张双侧 ---
    left_ankle_y = poses[:, LEFT_ANKLE, 1]
    right_ankle_y = poses[:, RIGHT_ANKLE, 1]
    leg_symmetry = np.abs(left_ankle_y - right_ankle_y)
    features['leg_symmetry_var'] = float(np.var(leg_symmetry))
    # --- 5. 倒地时间(慢镜感觉) ---
    # 检测重心从站立高度跌至峰值的持续时间
    standing_y = np.percentile(hips_y, 20)  # 站立时y较小(图像坐标)
    ground_y = hips_y.max()
    ground_idx = int(np.argmax(hips_y))
    # 找最后一次接近站立高度的时间点
    near_standing = np.where(hips_y < standing_y + 0.05)[0]
    start_idx = near_standing[near_standing < ground_idx][-1] if len(near_standing) else 0
    features['fall_duration_s'] = (ground_idx - start_idx) / fps
    # --- 6. 倒地后静止时长(夸张表演常"捂地不起") ---
    post_fall = hips_y[ground_idx:]
    features['post_fall_static_ratio'] = float(
        np.mean(np.abs(np.diff(post_fall)) < 0.002)
    )
    # --- 7. 手部自触(揉腿/抱头)动作频率 ---
    left_wrist, right_wrist = 15, 16
    left_knee, right_knee = 25, 26
    def dist(a, b):
        return np.linalg.norm(poses[:, a, :2] - poses[:, b, :2], axis=1)
    hand_leg_dist = dist(left_wrist, left_knee) + dist(right_wrist, right_knee)
    features['hand_touching_limb_freq'] = float(
        np.sum(np.abs(np.diff(hand_leg_dist)) > 0.02) / T
    )
    return features

关键启发式评分器

def dive_score(features):
    """
    基于领域规则的假摔评分(0~1,越高越可疑)
    """
    score = 0.0
    # 倒地速度过快但接触点无冲突(高速跌倒更可疑)
    if features['drop_speed'] > 0.8:
        score += 0.15
    # 头部加速度过大 → 主动甩头
    if features['head_max_accel'] > 5.0:
        score += 0.20
    # 倒地时间过短(<0.4s)→ 主动倒地;过长(>1.5s)→ 表演
    if features['fall_duration_s'] < 0.4 or features['fall_duration_s'] > 1.5:
        score += 0.15
    # 倒地后长时间静止(捂地不起)
    if features['post_fall_static_ratio'] > 0.7:
        score += 0.15
    # 频繁触摸腿部(夸张疼痛表现)
    if features['hand_touching_limb_freq'] > 0.3:
        score += 0.20
    # 躯干角度剧烈变化(戏剧化扭转)
    if features['torso_angle_change'] > 1.5:
        score += 0.15
    return min(score, 1.0)

训练一个学习模型(有标注数据时)

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
def train_dive_classifier(X, y):
    """
    X: 特征矩阵 (N, F)
    y: 标签 0=正常对抗 1=假摔/夸张表演
    """
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42, stratify=y
    )
    clf = RandomForestClassifier(n_estimators=200, max_depth=10)
    clf.fit(X_train, y_train)
    print(f"Accuracy: {clf.score(X_test, y_test):.3f}")
    return clf

端到端调用

def judge_dive(video_path):
    poses = extract_pose_sequence(video_path)
    if len(poses) < 30:
        return {"error": "视频太短"}
    feats = compute_dive_features(poses)
    score = dive_score(feats)
    verdict = "正常" if score < 0.4 else ("可疑" if score < 0.7 else "疑似假摔/表演")
    return {
        "score": round(score, 3),
        "verdict": verdict,
        "features": feats
    }
# 使用示例
# result = judge_dive("player_fall.mp4")
# print(result)

进阶方案(工业级)

  1. ST-GCN / PoseC3D:直接在关键点时序上做图卷积,比手工特征更鲁棒
  2. 接触检测:用 YOLO 做双人检测 + IoU 判断是否有实际身体接触(无接触倒地=强假摔信号)
  3. 多模态融合:视频(3D-CNN)+ 姿态(骨架)+ 音频(痛苦呻吟声)+ 生理(如有可穿戴数据)
  4. 面部 AU 分析:用 OpenFace 提取面部动作单元,夸张表情 → 可疑
  5. 时序 Transformer:将接触时刻前后 3 秒的姿态特征输入 Transformer 建模"接触→反应"因果
# 接触检测思路
def detect_contact(frame, player_box, opponent_box):
    iou = compute_iou(player_box, opponent_box)
    return iou > 0.05  # 有重叠/接近即视为接触

注意事项

  1. 主观性高:裁判都是主观判断,模型只能提供参考,不能替代人
  2. 数据稀缺:假摔标注数据少,建议用半监督/弱监督
  3. 隐私与合规:比赛视频需授权使用
  4. 区分"夸张表演"与"真受伤":本质是概率问题,输出应提供置信度
  5. 上下文重要:建议结合比赛时间、比分、球员历史造假率等元特征

快速验证 Demo(不用真实视频)

# 用随机姿态序列测试流程
import numpy as np
fake_poses = np.random.rand(120, 33, 4)
fake_poses[:, :, 1] = np.clip(np.linspace(0.3, 0.9, 120)[:, None] + 
                                np.random.randn(120, 33) * 0.02, 0, 1)
feats = compute_dive_features(fake_poses)
print(feats)
print("假摔评分:", dive_score(feats))

如果你告诉我具体应用场景(比赛分析?短视频审核?足球/篮球?),我可以帮你把方案进一步聚焦,比如给出足球假摔的专用特征或完整 ST-GCN 训练脚本。

抱歉,评论功能暂时关闭!