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

wen python案例 3

Python 判断假摔和夸张表演行为的技术方案

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

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 夸张表演

特征工程清单

  1. 骨架特征:17/33 关键点坐标、速度、加速度、关节角
  2. 交互特征:与最近对手的欧氏距离及变化率
  3. 物理特征:倒地瞬时垂直加速度、脊柱与地面夹角变化率
  4. 面部特征:可选,用 FER 模型识别夸张痛苦表情

模型选择对比

模型 准确率 速度 数据需求
规则+阈值 60% 极快 无
LSTM 80% 快 中
ST-GCN 85% 中 中
SlowFast+骨架 90%+ 慢 大

伦理与法律提醒

⚠️ 重要:

  1. 此类系统只能作为辅助工具,不能作为判罚依据
  2. 存在偏见风险(不同体型、风格的动作差异大)
  3. 真实赛场裁判需结合慢动作多机位人工复核
  4. 用于公开比赛判罚需符合体育组织(FIFA/IFAB)规定

快速上手路径

第1周:MediaPipe 提取骨架,可视化关键角度
第2周:规则法做基线(上面代码可直接跑)
第3周:收集 200+ 样本,标注 4 类
第4周:训 LSTM / ST-GCN,替换规则
第5周:加接触检测,做多模态融合

需要我针对某个具体场景(足球/篮球/摔跤/电影特效识别)给出更定制化的方案吗?

上一篇python案例给出的让球指数合理吗?

下一篇当前分类已是最新一篇

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