如何用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)
进阶方案(工业级)
- ST-GCN / PoseC3D:直接在关键点时序上做图卷积,比手工特征更鲁棒
- 接触检测:用 YOLO 做双人检测 + IoU 判断是否有实际身体接触(无接触倒地=强假摔信号)
- 多模态融合:视频(3D-CNN)+ 姿态(骨架)+ 音频(痛苦呻吟声)+ 生理(如有可穿戴数据)
- 面部 AU 分析:用 OpenFace 提取面部动作单元,夸张表情 → 可疑
- 时序 Transformer:将接触时刻前后 3 秒的姿态特征输入 Transformer 建模"接触→反应"因果
# 接触检测思路
def detect_contact(frame, player_box, opponent_box):
iou = compute_iou(player_box, opponent_box)
return iou > 0.05 # 有重叠/接近即视为接触
注意事项
- 主观性高:裁判都是主观判断,模型只能提供参考,不能替代人
- 数据稀缺:假摔标注数据少,建议用半监督/弱监督
- 隐私与合规:比赛视频需授权使用
- 区分"夸张表演"与"真受伤":本质是概率问题,输出应提供置信度
- 上下文重要:建议结合比赛时间、比分、球员历史造假率等元特征
快速验证 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 训练脚本。