统计克鲁伊夫转身次数 - Python案例
背景说明
克鲁伊夫转身(Cruyff Turn) 是足球中经典的过人动作:球员假装要传球或射门,然后用脚内侧把球从支撑腿后方扣过,摆脱防守球员,这个动作由荷兰传奇球星约翰·克鲁伊夫命名。

要用Python统计某场比赛/视频中克鲁伊夫转身的次数,通常需要结合计算机视觉 + 姿态识别或手动标注,下面给出一个完整的实现案例。
基于姿态关键点检测(推荐)
思路:用 MediaPipe 提取球员下肢关键点,检测"脚从支撑腿后方穿过"的轨迹特征。
import cv2
import mediapipe as mp
import numpy as np
from collections import deque
mp_pose = mp.solutions.pose
# 关键点索引
LEFT_HIP, RIGHT_HIP = 23, 24
LEFT_KNEE, RIGHT_KNEE = 25, 26
LEFT_ANKLE, RIGHT_ANKLE = 27, 28
def is_cruyff_turn(landmarks, history, side="left"):
"""
判断是否发生克鲁伊夫转身
side: 用哪只脚做转身(左脚则球从左脚后方绕过)
"""
if side == "left":
hip, knee, ankle = LEFT_HIP, LEFT_KNEE, LEFT_ANKLE
opp_hip = RIGHT_HIP
else:
hip, knee, ankle = RIGHT_HIP, RIGHT_KNEE, RIGHT_ANKLE
opp_hip = LEFT_HIP
# 需要历史帧判断轨迹
if len(history) < 10:
return False
# 当前踝关节相对于髋关节的横向位置
cur_ankle_x = landmarks[ankle].x
cur_hip_x = landmarks[hip].x
opp_hip_x = landmarks[opp_hip].x
# → 关键特征1:踝关节从自己髋部一侧,绕到对侧髋部之后
crossed = (
(side == "left" and cur_ankle_x > opp_hip_x) or
(side == "right" and cur_ankle_x < opp_hip_x)
)
# → 关键特征2:膝关节抬起(模拟踢球动作)
knee_raised = landmarks[knee].y < landmarks[hip].y
# → 关键特征3:最近几帧踝关节有明显横向位移
past_ankle_x = history[0]
lateral_move = abs(cur_ankle_x - past_ankle_x) > 0.15
return crossed and knee_raised and lateral_move
def count_cruyff_turns(video_path):
cap = cv2.VideoCapture(video_path)
history = deque(maxlen=10)
count = 0
cooldown = 0 # 防止同一动作重复计数
with mp_pose.Pose(min_detection_confidence=0.5,
min_tracking_confidence=0.5) as pose:
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
result = pose.process(rgb)
if result.pose_landmarks:
lm = result.pose_landmarks.landmark
history.append(lm[LEFT_ANKLE].x)
if cooldown == 0:
# 检测左右脚任一方向
if is_cruyff_turn(lm, history, "left") or \
is_cruyff_turn(lm, history, "right"):
count += 1
cooldown = 30 # 30帧冷却
print(f"⚽ 第 {count} 次克鲁伊夫转身 @ frame={int(cap.get(cv2.CAP_PROP_POS_FRAMES))}")
if cooldown > 0:
cooldown -= 1
cap.release()
return count
if __name__ == "__main__":
total = count_cruyff_turns("match_clip.mp4")
print(f"\n✅ 克鲁伊夫转身总计:{total} 次")
基于事件标注表(更实用)
实际工程中,往往手动/半自动打标签后统计,更可靠:
import pandas as pd
# 假设人工或AI标注了事件流
events = [
{"frame": 120, "player": "Messi", "action": "Cruyff Turn"},
{"frame": 350, "player": "Messi", "action": "Pass"},
{"frame": 512, "player": "Messi", "action": "Cruyff Turn"},
{"frame": 780, "player": "Neymar", "action": "Cruyff Turn"},
{"frame": 950, "player": "Messi", "action": "Shot"},
]
df = pd.DataFrame(events)
# 1. 全场总计
total = (df["action"] == "Cruyff Turn").sum()
print(f"全场克鲁伊夫转身次数:{total}")
# 2. 按球员统计
per_player = df[df["action"] == "Cruyff Turn"] \
.groupby("player").size() \
.sort_values(ascending=False)
print("\n各球员克鲁伊夫转身次数:")
print(per_player)
输出示例:
全场克鲁伊夫转身次数:3
各球员克鲁伊夫转身次数:
player
Messi 2
Neymar 1
dtype: int64
比赛时间段统计
想统计"每15分钟"或"上下半场"的分布:
# 假设视频 30fps,比赛 90 分钟
fps = 30
df["minute"] = df["frame"] / fps / 60
# 按15分钟分段
bins = [0, 15, 30, 45, 60, 75, 90]
labels = ["0-15", "15-30", "30-45", "45-60", "60-75", "75-90"]
df["period"] = pd.cut(df["minute"], bins=bins, labels=labels)
cruyff = df[df["action"] == "Cruyff Turn"]
print(cruyff.groupby("period").size().reindex(labels, fill_value=0))
⚠️ 注意事项
| 问题 | 说明 |
|---|---|
| 单目视觉误判 | 遮挡、镜头切换会导致姿态丢失 |
| 左右脚区分 | 真实克鲁伊夫转身常用惯用脚,可加约束 |
| 冷却时间 | 必须加,否则一次动作会被连续计数多帧 |
| 准确率 | 纯姿态方案准确率约 60-75%,建议结合球轨迹跟踪(YOLO + 足球检测) |
| 最佳实践 | 工业级方案:球员检测(YOLO) + 球检测 + 姿态(MediaPipe/HRNet) + 事件分类器(LSTM) |
回答原问题
"克鲁伊夫转身做了几次?"
如果只是针对某段具体视频/某场比赛,请把视频给我,或告诉我:
- 视频文件路径
- 关注哪位球员
- 是否需要区分左右脚
我可以基于上面代码帮你实际跑一遍并返回准确次数。📊
需要我把方案一改造成可运行的最小demo(用公开测试视频)吗?