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

wen python案例 1

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

这是一个计算机视觉+行为分析的复合问题,下面我从技术路线、关键特征、代码实现三个层面给出可落地的方案。

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


整体技术路线

视频输入
   ↓
人体检测/姿态估计 (YOLO + MediaPipe/HRNet)
   ↓
轨迹跟踪 (ByteTrack/DeepSORT)
   ↓
关键特征提取 (运动学 + 姿态 + 交互)
   ↓
规则引擎 + 机器学习分类器
   ↓
假摔/夸张表演判定

判别"假摔"vs"真摔"的关键特征

维度 真摔特征 假摔特征
重心轨迹 突然下坠,无预兆 有减速、屈膝等预备动作
躯干姿态 身体失控、翻滚 手部主动撑地保护自己
加速度 触地瞬间冲击大 触地前有明显减速
接触点 头/背/髋直接着地 手脚先着地缓冲
反应时间 摔倒后短暂"懵" 立刻看向裁判/捂脸喊叫
周围接触 有明显身体对抗力 对方接触轻微或无接触

夸张表演特征:

  • 头部/手部动作幅度异常大
  • 倒地后频繁滚动、抱腿、捂脸
  • 面部表情变化剧烈(可用 FER 检测)
  • 疼痛动作与受击部位不符

核心代码实现

环境依赖

pip install ultralytics mediapipe opencv-python numpy scipy scikit-learn

姿态与关键点提取

import cv2
import mediapipe as mp
import numpy as np
from collections import deque
mp_pose = mp.solutions.pose
class FallAnalyzer:
    def __init__(self, window=30):
        self.pose = mp_pose.Pose(
            static_image_mode=False,
            model_complexity=1,
            min_detection_confidence=0.6
        )
        self.history = deque(maxlen=window)  # 存储历史关键点
    def extract(self, frame):
        rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        res = self.pose.process(rgb)
        if not res.pose_landmarks:
            return None
        lm = res.pose_landmarks.landmark
        return {
            'nose':      (lm[0].x,  lm[0].y),
            'l_shoulder':(lm[11].x, lm[11].y),
            'r_shoulder':(lm[12].x, lm[12].y),
            'l_hip':     (lm[23].x, lm[23].y),
            'r_hip':     (lm[24].x, lm[24].y),
            'l_knee':    (lm[25].x, lm[25].y),
            'r_knee':    (lm[26].x, lm[26].y),
            'l_wrist':   (lm[15].x, lm[15].y),
            'r_wrist':   (lm[16].x, lm[16].y),
            'l_ankle':   (lm[27].x, lm[27].y),
            'r_ankle':   (lm[28].x, lm[28].y),
        }

特征工程(最核心部分)

def compute_features(history):
    """history: 最近N帧关键点序列"""
    if len(history) < 10:
        return None
    arr = {k: np.array([h[k] for h in history]) for k in history[0]}
    # 1. 重心 y 坐标下降速度 & 加速度
    centroid_y = (arr['l_hip'][:,1] + arr['r_hip'][:,1]) / 2
    v_y = np.diff(centroid_y)
    a_y = np.diff(v_y)
    # 2. 躯干角度(肩-髋连线与竖直方向夹角)
    mid_s = (arr['l_shoulder'] + arr['r_shoulder']) / 2
    mid_h = (arr['l_hip'] + arr['r_hip']) / 2
    torso_vec = mid_s - mid_h
    torso_angle = np.degrees(np.arctan2(torso_vec[:,0], -torso_vec[:,1]))
    torso_angle_abs = np.abs(torso_angle)  # 0=直立, 90=横躺
    # 3. 手部保护特征:摔倒前手腕是否低于胸口
    wrist_y = (arr['l_wrist'][:,1] + arr['r_wrist'][:,1]) / 2
    shoulder_y = (arr['l_shoulder'][:,1] + arr['r_shoulder'][:,1]) / 2
    # 手在摔倒过程中下探(保护动作)
    hand_reach_down = np.max(wrist_y - shoulder_y)
    # 4. 触地前减速(真摔触地前速度最大,假摔会刹车)
    if len(v_y) > 5:
        pre_impact_v = np.min(v_y[-8:-3]) if len(v_y)>=8 else np.min(v_y)
        impact_v     = np.min(v_y[-3:])
        braking = impact_v - pre_impact_v  # >0 表示减速
    else:
        braking = 0
    # 5. 手部动作幅度(夸张表演时挥手频繁)
    lw = arr['l_wrist']; rw = arr['r_wrist']
    wrist_motion = np.mean(np.linalg.norm(np.diff(lw, axis=0), axis=1)) \
                 + np.mean(np.linalg.norm(np.diff(rw, axis=0), axis=1))
    # 6. 头/膝踢动幅度(夸张翻滚)
    knee_motion = np.mean(np.linalg.norm(np.diff(arr['l_knee'], axis=0), axis=1))
    return {
        'max_velocity':   float(np.max(v_y)),            # 下坠最大速度
        'max_accel':      float(np.max(np.abs(a_y))),    # 冲击加速度
        'torso_angle':    float(np.max(torso_angle_abs)),# 最大倾倒角度
        'hand_protect':   float(hand_reach_down),
        'braking':        float(braking),
        'wrist_motion':   float(wrist_motion),
        'knee_motion':    float(knee_motion),
        'impact_velocity':float(np.min(v_y[-3:])),
    }

规则+打分模型判别

def judge_dive(feat):
    """返回 (is_dive, score, reasons)"""
    score = 0
    reasons = []
    # 真摔的打分(负分表示倾向真摔)
    if abs(feat['max_velocity']) > 0.02:       # 下坠速度快
        score -= 2; reasons.append("高速下坠")
    if feat['max_accel'] > 0.01:               # 触地冲击大
        score -= 2; reasons.append("强冲击")
    if feat['torso_angle'] > 60:               # 躯干大幅倾倒
        score -= 1
    if feat['braking'] > 0.005:                # 触地前减速
        score += 2; reasons.append("触地前刹车")
    if feat['hand_protect'] > 0.15:            # 手主动撑地
        score += 2; reasons.append("主动护体")
    if feat['wrist_motion'] > 0.03:            # 手腕挥动大
        score += 2; reasons.append("手部夸张")
    if feat['knee_motion'] > 0.02:             # 膝关节翻动大
        score += 1; reasons.append("夸张翻滚")
    is_dive = score >= 3
    return is_dive, score, reasons

训练机器学习分类器(推荐做法)

from sklearn.ensemble import RandomForestClassifier
import joblib
# 采集标注数据:0=真摔, 1=假摔
# X = [feat 向量...], y = [标签...]
X = np.array([...])   # 每行一个特征向量
y = np.array([...])
clf = RandomForestClassifier(n_estimators=200, max_depth=8, random_state=42)
clf.fit(X, y)
joblib.dump(clf, 'dive_detector.pkl')
# 预测
proba = clf.predict_proba(feat_vec[None])[0][1]  # 假摔概率
print(f"假摔概率: {proba:.2f}")

视频主流程

def run(video_path, clf):
    cap = cv2.VideoCapture(video_path)
    analyzer = FallAnalyzer(window=30)
    dive_frame_count = 0
    while cap.isOpened():
        ret, frame = cap.read()
        if not ret: break
        kp = analyzer.extract(frame)
        if kp is None: continue
        analyzer.history.append(kp)
        if len(analyzer.history) == analyzer.history.maxlen:
            feat = compute_features(analyzer.history)
            if feat:
                vec = np.array(list(feat.values()))
                prob = clf.predict_proba(vec[None])[0][1]
                is_dive, score, rs = judge_dive(feat)
                # 融合判断
                label = "假摔嫌疑" if (prob > 0.6 or is_dive) else "正常"
                if label == "假摔嫌疑":
                    dive_frame_count += 1
                    cv2.putText(frame, f"{label} p={prob:.2f}",
                                (30,50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
                    for i, r in enumerate(rs):
                        cv2.putText(frame, r, (30, 90+i*30),
                                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,255), 2)
        cv2.imshow("Fall Analysis", frame)
        if cv2.waitKey(1) & 0xFF == 27: break
    cap.release()
    cv2.destroyAllWindows()
    return dive_frame_count > 15   # 连续多帧判为假摔才输出结论

进阶增强方案

需求 技术方案
更强判别力 加入骨架时序模型:ST-GCN / PoseC3D
交互判断 加入对方球员检测,判断接触力度
面部夸张 用 FER / DeepFace 检测痛苦表情持续时长
真实数据训练 用 SoccerNet / 足球假摔数据集微调
多模态融合 视频 + 音频(惨叫)+ 裁判视角

ST-GCN 简要结构(骨架节点分类)

# 使用开源库 pyskl 或 mmaction2
# 输入:T帧 × V关节 × C坐标
# 输出:真摔 / 假摔 概率
from mmaction.apis import init_recognizer, inference_recognizer
model = init_recognizer('stgcn_dive.py', 
                        'stgcn_dive.pth', device='cuda:0')
result = inference_recognizer(model, 'skeleton.pkl')
# result: [[真摔概率, 假摔概率]]

注意事项

  1. 不能100%准确:假摔判定本身就是足球判罚的难点,AI 只能做辅助决策。
  2. 数据偏差:公开假摔数据集少,建议自建数据集(可从比赛录像标注)。
  3. 视角影响:单目视角判断接触容易出错,多机位融合更可靠。
  4. 合规性:在正式比赛中应用需符合体育仲裁与隐私要求。

推荐起步路径:

  1. 用 MediaPipe + 上述规则特征做一个 demo,验证关键特征是否有效。
  2. 收集 200~500 个标注片段(真摔/假摔),训随机森林或 XGBoost。
  3. 若效果不够,升级为 ST-GCN 时序骨架模型。
  4. 最后接入多机位+音频融合。

需要我给出具体某个环节的完整代码(如 ST-GCN 训练、接触力判断、面部表情融合),可以继续告诉我。

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