用Python统计"马赛回旋"使用频率的案例分析
"马赛回旋"(Marseille Turn / La Roulette)是齐达内的标志性过人动作,下面给你一个可运行的完整方案,从数据获取到统计可视化。

整体思路
数据源(比赛事件/视频/文本) → 动作识别/标注 → 频次统计 → 可视化分析
根据你的数据条件,有三种实现路径:
| 数据条件 | 方法 | 难度 |
|---|---|---|
| 有结构化事件数据(如StatsBomb) | 直接统计事件类型 | |
| 只有比赛视频 | CV动作识别 | |
| 只有文字解说/报道 | NLP关键词抽取 |
方案A:基于结构化事件数据(推荐入门)
数据来源
- StatsBomb Open Data(免费,含梅西、世界杯等)
- Wyscout、Opta 付费数据
代码示例
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 假设已有事件数据 events.csv,字段:player, match_id, event_type, minute
# StatsBomb 中类似动作通常标注为 "Turn" 或自定义标签 "Marseille Turn"
df = pd.read_csv("events.csv")
# 筛选马赛回旋事件
mt = df[df["event_type"] == "Marseille Turn"]
# 1) 按球员统计使用频率
freq_by_player = (
mt.groupby("player")
.size()
.sort_values(ascending=False)
.head(10)
)
print(freq_by_player)
# 2) 按90分钟归一化(考虑出场时间)
minutes = df.groupby("player")["minute"].max()
freq_per90 = (mt.groupby("player").size() / minutes * 90).dropna()
# 3) 可视化
plt.rcParams["font.sans-serif"] = ["SimHei"] # 中文显示
plt.rcParams["axes.unicode_minus"] = False
fig, ax = plt.subplots(1, 2, figsize=(14, 5))
freq_by_player.plot(kind="barh", ax=ax[0], color="steelblue")
ax[0].set_title("马赛回旋使用次数 TOP10")
ax[0].set_xlabel("次数")
freq_per90.sort_values(ascending=False).head(10).plot(
kind="barh", ax=ax[1], color="coral")
ax[1].set_title("每90分钟马赛回旋频率 TOP10")
ax[1].set_xlabel("次/90min")
plt.tight_layout()
plt.savefig("marseille_turn.png", dpi=150)
方案B:基于比赛视频(计算机视觉)
技术栈
- 姿态估计:MediaPipe / YOLOv8-Pose
- 动作分类:ST-GCN / SlowFast / Transformer
- 跟踪:ByteTrack
核心流程
import cv2
import mediapipe as mp
import numpy as np
mp_pose = mp.solutions.pose
pose = mp_pose.Pose()
def extract_keypoints(video_path):
"""提取每帧人体关键点"""
cap = cv2.VideoCapture(video_path)
seq = []
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
res = pose.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
if res.pose_landmarks:
kp = np.array([[l.x, l.y, l.z] for l in res.pose_landmarks.landmark])
seq.append(kp.flatten())
return np.array(seq) # shape: (T, 99)
# 判定马赛回旋的启发式规则(简化版):
# 1. 身体绕垂直轴旋转 > 180°
# 2. 单脚支撑 (另一只脚抬起)
# 3. 球在脚下控制
def is_marseille_turn(seq, fps=25, window=1.0):
"""基于髋部横向位移+肩部朝向变化做粗略判断"""
n = int(fps * window)
for i in range(len(seq) - n):
win = seq[i:i+n].reshape(n, 33, 3)
# 肩部向量角度变化
left_shoulder, right_shoulder = win[:, 11, :2], win[:, 12, :2]
vec = right_shoulder - left_shoulder
angles = np.arctan2(vec[:, 1], vec[:, 0])
delta = np.abs(np.unwrap(angles)[-1] - np.unwrap(angles)[0])
if np.degrees(delta) > np.pi: # 旋转超180°
return True
return False
# 批量统计
video_count = 0
for path in ["match1.mp4", "match2.mp4"]:
seq = extract_keypoints(path)
if is_marseille_turn(seq):
video_count += 1
print(f"视频中出现马赛回旋次数: {video_count}")
⚠️ 实际生产需训练专用分类模型(收集带标签的切片)才能达到可用精度,启发式规则仅作 demo。
方案C:基于文字报道/解说(NLP)
import re, jieba
from collections import Counter
keywords = ["马赛回旋", "拉球转身", "马赛转身", "roulette", "marseille turn"]
def count_marseille(texts):
"""统计文本库中马赛回旋出现次数 + 关联球员"""
cnt = Counter()
for t in texts:
for kw in keywords:
for m in re.finditer(kw, t):
# 提取前后10字作为上下文
start = max(0, m.start()-10)
ctx = t[start:m.end()+10]
cnt[kw] += 1
print(f"[{kw}] ...{ctx}...")
return cnt
# 示例
articles = [
"齐达内在中场完成一次经典马赛回旋摆脱防守",
"内马尔禁区前沿马赛回旋过两人,引爆全场"
]
print(count_marseille(articles))
结果呈现(推荐分析维度)
一个完整的分析报告通常包含:
- 球员维度:TOP10使用次数 / 每90min频率
- 场景维度:前场/中场/后场、比分领先/落后时频率
- 时间维度:赛季趋势、比赛第几十分钟最常用
- 成功率:马赛回旋后是否成功推进/传球(需事件链)
- 对比分析:齐达内 vs 内马尔 vs 现代球员
示例可视化:
# 热力图:不同球员×赛季 的使用频率
pivot = mt.pivot_table(index="player", columns="season",
values="event_id", aggfunc="count", fill_value=0)
sns.heatmap(pivot, cmap="YlOrRd", annot=True)"马赛回旋使用频率热力图")
plt.show()
现实难点与建议
| 难点 | 建议 |
|---|---|
| "马赛回旋"非标准化事件 | 建立自定义标注规范(旋转≥180°+单脚支撑+控球) |
| 视频识别精度低 | 用标注数据微调 ST-GCN,配合球员跟踪 |
| 数据获取成本高 | 先用 StatsBomb 公开数据练手 |
| 样本少 | 交叉验证 + 置信区间,避免过度解读 |
如果你告诉我手头有什么数据(CSV事件表?比赛视频?新闻文本?),我可以帮你把上面某一方案改成直接可跑的完整脚本。