Python 判断假摔和夸张表演行为的技术方案
这是一个计算机视觉 + 行为识别的综合问题,下面我从技术可行性、算法选型和实战代码三个层面给你完整方案。

问题拆解
"假摔"和"夸张表演"本质上是运动人体行为识别问题,需要捕捉:
| 特征维度 | 具体表现 |
|---|---|
| 姿态异常 | 倒地前无明显碰撞、突然失衡 |
| 时间特征 | 倒地动作过快/过慢、有预备动作 |
| 接触检测 | 与对手有无真实身体接触、接触力度 |
| 面部表情 | 夸张痛苦表情(可选) |
| 物理合理性 | 倒地加速度、角度不符合力学规律 |
技术方案选型
方案 A:基于骨架点的行为识别(推荐 ⭐)
- 工具:MediaPipe Pose / YOLOv8-Pose / OpenPose
- 模型:ST-GCN(时空图卷积)、LSTM、Transformer
- 优势:对假摔判断最直接,可分析动作时序
方案 B:基于视频分类
- 工具:SlowFast、I3D、TimeSformer
- 优势:端到端,但需要大量标注数据
方案 C:多模态融合(最完整)
骨架 + 光流 + 音频(惨叫)+ 面部表情
完整实战代码
骨架提取 + 假摔判定
import cv2
import mediapipe as mp
import numpy as np
from collections import deque
mp_pose = mp.solutions.pose
mp_draw = mp.solutions.drawing_utils
class DiveDetector:
def __init__(self, window=30):
self.pose = mp_pose.Pose(
min_detection_confidence=0.5,
min_tracking_confidence=0.5
)
# 保存最近 N 帧关键角度,用于时序分析
self.angle_history = deque(maxlen=window)
self.hip_history = deque(maxlen=window)
self.velocity_history = deque(maxlen=window)
@staticmethod
def calculate_angle(a, b, c):
"""计算三点夹角(b 为顶点)"""
a, b, c = np.array(a), np.array(b), np.array(c)
rad = np.arctan2(c[1]-b[1], c[0]-b[0]) - \
np.arctan2(a[1]-b[1], a[0]-b[0])
angle = np.abs(np.degrees(rad))
return 360 - angle if angle > 180 else angle
def detect(self, frame):
h, w = frame.shape[:2]
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
result = self.pose.process(rgb)
if not result.pose_landmarks:
return None
lm = result.pose_landmarks.landmark
def pt(idx): return (lm[idx].x * w, lm[idx].y * h)
# 关键点
l_sh, r_sh = pt(11), pt(12)
l_hip, r_hip = pt(23), pt(24)
l_ank, r_ank = pt(27), pt(28)
l_knee, r_knee = pt(25), pt(26)
# 特征1:躯干与地面夹角(倒地时显著)
shoulder_mid = ((l_sh[0]+r_sh[0])/2, (l_sh[1]+r_sh[1])/2)
hip_mid = ((l_hip[0]+r_hip[0])/2, (l_hip[1]+r_hip[1])/2)
dy = hip_mid[1] - shoulder_mid[1]
dx = hip_mid[0] - shoulder_mid[0]
torso_angle = abs(np.degrees(np.arctan2(dy, dx)))
# 特征2:髋部高度
hip_y = hip_mid[1]
# 特征3:髋部垂直速度
if self.hip_history:
vel = hip_y - self.hip_history[-1]
else:
vel = 0
self.hip_history.append(hip_y)
self.velocity_history.append(vel)
self.angle_history.append(torso_angle)
# ===== 假摔判定规则 =====
scores = {}
# 规则1:躯干接近水平 (< 30° 或 > 150° 视为倒地)
scores['torso_horizontal'] = 1.0 if (torso_angle < 30 or torso_angle > 150) else 0.0
# 规则2:髋部高度骤降(快速倒地)
if len(self.velocity_history) >= 5:
recent_vel = list(self.velocity_history)[-5:]
acc = sum(recent_vel) # 累计位移
scores['rapid_fall'] = 1.0 if acc > h * 0.15 else 0.0
# 规则3:倒地前速度突增后骤停(戏剧化预备动作)
if len(self.velocity_history) >= 15:
vels = list(self.velocity_history)
early_speed = np.mean(np.abs(vels[:5]))
late_speed = np.mean(np.abs(vels[-5:]))
# 起始慢、突然快,然后骤停 → 典型表演特征
if late_speed > early_speed * 2.5:
scores['dramatic_motion'] = 1.0
else:
scores['dramatic_motion'] = 0.0
# 规则4:身体蜷缩/夸张姿态(膝盖离髋部很远)
knee_disp = np.hypot(l_knee[0]-hip_mid[0], l_knee[1]-hip_mid[1])
scores['exaggerated_pose'] = 1.0 if knee_disp > h * 0.35 else 0.0
# 综合评分
total = sum(scores.values()) / len(scores)
return {
'torso_angle': torso_angle,
'hip_y': hip_y,
'velocity': vel,
'scores': scores,
'dive_score': total,
'verdict': 'SUSPECTED DIVE' if total > 0.6 else 'NORMAL',
'landmarks': result.pose_landmarks
}
# ============ 使用示例 ============
def run_on_video(video_path):
cap = cv2.VideoCapture(video_path)
detector = DiveDetector()
alert_frames = []
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
output = detector.detect(frame)
if output:
mp_draw.draw_landmarks(
frame, output['landmarks'], mp_pose.POSE_CONNECTIONS
)
color = (0, 0, 255) if output['dive_score'] > 0.6 else (0, 255, 0)
cv2.putText(frame, f"{output['verdict']} {output['dive_score']:.2f}",
(20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
if output['dive_score'] > 0.6:
alert_frames.append(cap.get(cv2.CAP_PROP_POS_FRAMES))
cv2.imshow('Dive Detection', frame)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
return alert_frames
# run_on_video('football_clip.mp4')
升级版:LSTM 时序分类器
规则法容易误判,工业级做法是在骨架序列上训练 LSTM。
import torch
import torch.nn as nn
class DiveLSTM(nn.Module):
"""
输入: (batch, seq_len, num_keypoints*2) 30帧 × 33点×2维
输出: 二分类(正常/假摔)或多分类
"""
def __init__(self, input_dim=66, hidden=128, num_classes=2):
super().__init__()
self.lstm = nn.LSTM(input_dim, hidden, num_layers=2,
batch_first=True, bidirectional=True,
dropout=0.3)
self.attn = nn.Linear(hidden*2, 1)
self.fc = nn.Sequential(
nn.Linear(hidden*2, 64),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(64, num_classes)
)
def forward(self, x):
# x: (B, T, F)
out, _ = self.lstm(x) # (B, T, 2H)
attn = torch.softmax(self.attn(out), dim=1) # (B, T, 1)
ctx = (out * attn).sum(dim=1) # (B, 2H)
return self.fc(ctx)
# ===== 训练数据准备伪代码 =====
"""
数据集建议:
1. 公开数据:SoccerNet-v2、Diving-48(含跳水动作)、NTU RGB+D
2. 自建:从比赛视频中裁剪"被犯规" vs "假摔"片段,人工标注
类别建议:
0 = 正常运动
1 = 真实受伤倒地
2 = 假摔/夸张表演
"""
接触检测(关键增强)
假摔的核心反驳证据是"是否有真实接触",可用 YOLOv8 检测人体做 IoU 分析:
from ultralytics import YOLO
model = YOLO('yolov8n.pt')
def detect_contact(frame, prev_persons=None):
"""检测两球员是否发生接触(bbox 相交)"""
results = model(frame, classes=[0]) # person class
boxes = results[0].boxes.xyxy.cpu().numpy()
contacts = []
for i in range(len(boxes)):
for j in range(i+1, len(boxes)):
x1 = max(boxes[i][0], boxes[j][0])
y1 = max(boxes[i][1], boxes[j][1])
x2 = min(boxes[i][2], boxes[j][2])
y2 = min(boxes[i][3], boxes[j][3])
iou = max(0, x2-x1) * max(0, y2-y1)
if iou > 100: # 像素面积阈值
contacts.append((i, j, iou))
return boxes, contacts
逻辑融合:
摔倒 + 无接触 → 高度疑似假摔
摔倒 + 轻微接触 → 疑似夸张表演
摔倒 + 强烈接触 → 可能真实
工程落地建议
数据标注方案
| 说明 | |
|---|---|
| 0 | 正常 |
| 1 | 真实倒地 |
| 2 | 疑似假摔 |
| 3 | 夸张表演 |
特征工程清单
- 骨架特征:17/33 关键点坐标、速度、加速度、关节角
- 交互特征:与最近对手的欧氏距离及变化率
- 物理特征:倒地瞬时垂直加速度、脊柱与地面夹角变化率
- 面部特征:可选,用 FER 模型识别夸张痛苦表情
模型选择对比
| 模型 | 准确率 | 速度 | 数据需求 |
|---|---|---|---|
| 规则+阈值 | 60% | 极快 | 无 |
| LSTM | 80% | 快 | 中 |
| ST-GCN | 85% | 中 | 中 |
| SlowFast+骨架 | 90%+ | 慢 | 大 |
伦理与法律提醒
⚠️ 重要:
- 此类系统只能作为辅助工具,不能作为判罚依据
- 存在偏见风险(不同体型、风格的动作差异大)
- 真实赛场裁判需结合慢动作多机位人工复核
- 用于公开比赛判罚需符合体育组织(FIFA/IFAB)规定
快速上手路径
第1周:MediaPipe 提取骨架,可视化关键角度
第2周:规则法做基线(上面代码可直接跑)
第3周:收集 200+ 样本,标注 4 类
第4周:训 LSTM / ST-GCN,替换规则
第5周:加接触检测,做多模态融合
需要我针对某个具体场景(足球/篮球/摔跤/电影特效识别)给出更定制化的方案吗?