python案例统计克鲁伊夫转身做了几次?

wen python案例 4

统计克鲁伊夫转身次数 - Python案例

这是一个足球数据分析的典型案例,通过骨骼关键点或球员姿态识别来统计"克鲁伊夫转身"(Cruyff Turn)动作的次数。

python案例统计克鲁伊夫转身做了几次?

克鲁伊夫转身动作特征

克鲁伊夫转身是足球中的经典假动作,具有以下特征:

  • 假射/假传:一只脚做出射门或传球的姿势
  • 扣球变向:用同一只脚的脚内侧将球从身后扣到另一侧
  • 身体转向:支撑脚为轴,身体180°转向
  • 方向突变:球员运动方向与球的方向相反

基于骨骼关键点的识别思路

关键特征提取

import numpy as np
import pandas as pd
from scipy.signal import find_peaks
# 假设已通过 MediaPipe / OpenPose 提取了骨骼关键点
# 每帧包含: 左/右髋、膝、踝、脚、肩等坐标
def extract_features(keypoints_df):
    """从关键点数据中提取克鲁伊夫转身相关特征"""
    features = pd.DataFrame()
    # 1. 脚踝相对髋部的位置(判断支撑脚)
    features['left_ankle_rel'] = keypoints_df['left_ankle_x'] - keypoints_df['left_hip_x']
    features['right_ankle_rel'] = keypoints_df['right_ankle_x'] - keypoints_df['right_hip_x']
    # 2. 膝盖弯曲角度(假射腿会明显弯曲)
    features['left_knee_angle'] = compute_angle(
        keypoints_df['left_hip'], keypoints_df['left_knee'], keypoints_df['left_ankle']
    )
    features['right_knee_angle'] = compute_angle(
        keypoints_df['right_hip'], keypoints_df['right_knee'], keypoints_df['right_ankle']
    )
    # 3. 躯干朝向(用于检测身体旋转)
    features['torso_orientation'] = np.arctan2(
        keypoints_df['left_shoulder_y'] - keypoints_df['right_shoulder_y'],
        keypoints_df['left_shoulder_x'] - keypoints_df['right_shoulder_x']
    )
    # 4. 脚部速度(扣球时脚速突然变化)
    features['left_ankle_speed'] = keypoints_df['left_ankle_x'].diff().abs()
    features['right_ankle_speed'] = keypoints_df['right_ankle_x'].diff().abs()
    return features
def compute_angle(p1, p2, p3):
    """计算三点构成的角度"""
    v1 = p1 - p2
    v2 = p3 - p2
    cos_angle = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2) + 1e-6)
    return np.degrees(np.arccos(np.clip(cos_angle, -1, 1)))

动作识别状态机

class CruyffTurnDetector:
    """克鲁伊夫转身动作检测器(状态机模式)"""
    def __init__(self, fps=30):
        self.fps = fps
        self.state = 'IDLE'
        self.count = 0
        self.events = []          # 记录每次转身的时间戳
        self.state_start_frame = 0
        self.phase_frames = []    # 记录各阶段持续帧数
    def detect(self, features_df):
        """逐帧检测状态转换"""
        for i, row in features_df.iterrows():
            self._update_state(i, row)
        return self.count
    def _update_state(self, frame_idx, row):
        state_duration = (frame_idx - self.state_start_frame) / self.fps
        # 状态1: 空闲 → 假动作准备(脚抬高、膝盖弯曲)
        if self.state == 'IDLE':
            if (row['right_knee_angle'] < 100 and 
                row['right_ankle_rel'] > 0.15):
                self.state = 'FAKE_SHOT'
                self.state_start_frame = frame_idx
        # 状态2: 假动作 → 扣球(脚速突增 + 脚踝轨迹方向反转)
        elif self.state == 'FAKE_SHOT':
            if (row['right_ankle_speed'] > 0.05 and 
                state_duration > 0.2 and state_duration < 1.5):
                self.state = 'PULL_BACK'
                self.state_start_frame = frame_idx
            elif state_duration > 2.0:   # 超时重置
                self._reset()
        # 状态3: 扣球 → 身体转向(躯干朝向变化 > 90°)
        elif self.state == 'PULL_BACK':
            if state_duration > 3.0:
                self._reset()
        # 状态4: 转向 → 完成(方向突变 + 速度恢复)
        # ... 后续状态判断
    def _reset(self):
        self.state = 'IDLE'
        self.state_start_frame = 0

简化版:基于方向突变检测

如果只有球员位置跟踪数据(更常见的场景):

def count_cruyff_turns(player_positions, fps=30, 
                        min_angle_change=120, 
                        speed_drop_ratio=0.4,
                        window_sec=1.0):
    """
    通过轨迹方向突变检测克鲁伊夫转身
    - 位置突变: 方向改变 > 120度
    - 速度下降: 转身瞬间速度下降
    - 转身前后轨迹呈"回头"形态
    """
    window = int(window_sec * fps)
    velocities = np.diff(player_positions, axis=0)
    speeds = np.linalg.norm(velocities, axis=1)
    headings = np.arctan2(velocities[:, 1], velocities[:, 0])
    turn_events = []
    for i in range(window, len(headings) - window):
        # 转弯角度 = 前后两段速度方向夹角
        v_before = velocities[i-window:i].mean(axis=0)
        v_after  = velocities[i:i+window].mean(axis=0)
        cos_theta = np.dot(v_before, v_after) / (
            np.linalg.norm(v_before) * np.linalg.norm(v_after) + 1e-6)
        angle_change = np.degrees(np.arccos(np.clip(cos_theta, -1, 1)))
        # 判断是否为克鲁伊夫式转身
        v_avg = speeds[max(0,i-5):i+5].mean()
        if (angle_change > min_angle_change
            and speeds[i] < v_avg * speed_drop_ratio
            and np.linalg.norm(v_before + v_after) < np.linalg.norm(v_before) * 0.5):
            turn_events.append(i)
    # 去重(同一转身只计一次)
    turn_events = deduplicate_events(turn_events, min_gap=int(1.5*fps))
    return len(turn_events), turn_events
def deduplicate_events(events, min_gap):
    if not events:
        return []
    result = [events[0]]
    for e in events[1:]:
        if e - result[-1] > min_gap:
            result.append(e)
    return result

完整使用示例

import cv2
import mediapipe as mp
# ============ 处理视频 ============
def analyze_video(video_path):
    cap = cv2.VideoCapture(video_path)
    mp_pose = mp.solutions.pose
    pose = mp_pose.Pose()
    all_kp = []
    fps = cap.get(cv2.CAP_PROP_FPS)
    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break
        result = pose.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
        if result.pose_landmarks:
            kp = {lm.name: [lm.x, lm.y] 
                  for lm in result.pose_landmarks.landmark}
            all_kp.append(kp)
        else:
            all_kp.append(None)
    cap.release()
    # 转成 DataFrame 并提取特征
    kp_df = pd.DataFrame([k for k in all_kp if k is not None])
    features = extract_features(kp_df)
    # 检测
    detector = CruyffTurnDetector(fps=fps)
    n_turns = detector.detect(features)
    print(f"克鲁伊夫转身次数: {n_turns}")
analyze_video('player_highlight.mp4')
# 输出示例: 克鲁伊夫转身次数: 3

统计结果可视化

import matplotlib.pyplot as plt
def plot_turn_analysis(player_positions, turn_events, fps=30):
    """可视化球员轨迹与转身点"""
    plt.figure(figsize=(12, 8))
    plt.plot(player_positions[:, 0], player_positions[:, 1], 'b-', alpha=0.5)
    for i, evt in enumerate(turn_events):
        plt.scatter(player_positions[evt, 0], player_positions[evt, 1],
                    c='red', s=200, marker='*', 
                    label='克鲁伊夫转身' if i == 0 else '')
        plt.annotate(f'#{i+1} ({evt/fps:.1f}s)',
                     player_positions[evt], fontsize=10)
    plt.title(f'球员轨迹与克鲁伊夫转身检测(共 {len(turn_events)} 次)')
    plt.xlabel('X 位置')
    plt.ylabel('Y 位置')
    plt.legend()
    plt.gca().invert_yaxis()
    plt.show()

关键难点与优化建议

难点 解决方案
与普通变向区分 阈值要求转向角 ≥120° + 速度剧降
与马赛回旋混淆 检测是否双脚交替踩球(马赛特征)
与假射区分 需检测"扣球"而非"直接射门"(球轨迹)
左右脚识别 判断哪只脚在做动作
上身遮挡 结合多角度摄像头/IMU传感器

提升精度的方法:

  1. 加入球轨迹(球位置检测 YOLO)——球的突然反向是克鲁伊夫转身的最强信号
  2. 时序模型:用 LSTM / ST-GCN / Transformer 直接做动作分类,而非规则
  3. 数据标注:收集 100+ 个克鲁伊夫转身片段训练分类器

预期输出

=== 视频分析报告 ===
文件: cruyff_highlights.mp4
时长: 45.2s (1356 帧)
检测动作:
  - 克鲁伊夫转身: 4 次
  - 时间戳: [3.2s, 12.5s, 28.1s, 41.7s]
每个动作平均耗时: 0.85s
左/右脚分布: 右 3 / 左 1

如果需要针对性实现某一部分(如 MediaPipe 关键点提取、LSTM 模型、球检测),可以告诉我具体场景,我可以给出更完整的代码。

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