python案例如何量化球员的跑动覆盖面积?

wen python案例 2

Python量化球员跑动覆盖面积

下面我从数据来源 → 核心算法 → 可视化完整讲一个可落地的方案。

python案例如何量化球员的跑动覆盖面积?


问题拆解

"跑动覆盖面积"通常有两种含义:

  1. 轨迹包围面积:球员轨迹形成的多边形面积(凸包/凹包)
  2. 网格覆盖率:将球场划分网格,统计球员经过的格子数 × 单元格面积

比赛中一般用第 2 种(更贴近"覆盖"含义),本案例两种都实现。


数据准备

真实场景可来自:

  • Sportradar / StatsBomb / Metrica 的 tracking data(25fps 的 x, y 坐标)
  • 自采 GPS 背心数据
  • 视频 AI 追踪(YOLO + 多目标跟踪)

这里模拟一份数据:

import numpy as np
import pandas as pd
np.random.seed(42)
# 模拟 5 分钟、10Hz 的球员轨迹(105m x 68m 球场)
n = 3000
t = np.arange(n) / 10
# 用布朗运动 + 朝向漂移,模拟一段带方向的跑动
x = np.cumsum(np.random.randn(n) * 0.3) + 20
y = np.cumsum(np.random.randn(n) * 0.3) + 34
# 加一个折返跑片段(增加覆盖感)
x[1000:1500] += np.linspace(0, 30, 500)
y[1000:1500] += np.sin(np.linspace(0, 4*np.pi, 500)) * 10
# 裁剪到球场范围
x = np.clip(x, 0, 105)
y = np.clip(y, 0, 68)
df = pd.DataFrame({"t": t, "x": x, "y": y})
print(df.head())

方法一:凸包 / 凹包面积

from scipy.spatial import ConvexHull
from shapely.geometry import MultiPoint
points = df[["x", "y"]].values
# --- 凸包(快速,但会高估覆盖)---
hull = ConvexHull(points)
convex_area = hull.volume   # 2D 时 volume 就是面积
print(f"凸包面积: {convex_area:.1f} m²")
# --- 凹包(更贴合真实轨迹)---
mp = MultiPoint(points)
concave_poly = mp.convex_hull  # shapely 没有直接凹包,用 alpha shape 近似

凹包(Alpha Shape)实现:

import alphashape  # pip install alphashape
alpha = 0.15                      # 越小越贴合,越大越接近凸包
shape = alphashape.alphashape(points, alpha)
print(f"凹包面积: {shape.area:.1f} m²")

方法二:网格覆盖率(推荐)

def coverage_grid(df, cell=1.0, pitch=(105, 68)):
    """将球场按 cell 米划分网格,统计球员经过的格子数"""
    nx = int(pitch[0] / cell)
    ny = int(pitch[1] / cell)
    ix = np.clip((df["x"] / cell).astype(int), 0, nx - 1)
    iy = np.clip((df["y"] / cell).astype(int), 0, ny - 1)
    cells = set(zip(ix, iy))
    return cells, len(cells) * cell * cell, (nx, ny)
cells, area, shape = coverage_grid(df, cell=1.0)
print(f"覆盖格数: {len(cells)}")
print(f"覆盖面积: {area:.1f} m²")
print(f"球场总面积: {105*68} = 7140 m²")
print(f"覆盖率: {area/7140*100:.2f}%")

参数选择建议: | 场景 | cell 大小 | |------|-----------| | 精细分析单个球员 | 0.5 – 1 m | | 团队阵型分析 | 2 – 5 m | | 转播可视化 | 1 m |


可视化

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# --- 左图:轨迹 + 凸包 ---
ax = axes[0]
ax.plot(df["x"], df["y"], lw=0.8, alpha=0.6, color="steelblue")
hull_pts = points[hull.vertices]
ax.fill(hull_pts[:, 0], hull_pts[:, 1],
        alpha=0.25, color="orange", label=f"Hull {convex_area:.0f} m²")
ax.add_patch(Rectangle((0, 0), 105, 68, fill=False, ec="black"))
ax.set_title("轨迹 + 凸包")
ax.set_xlim(-2, 107); ax.set_ylim(-2, 70)
ax.set_aspect("equal"); ax.legend()
# --- 右图:网格覆盖热力图 ---
ax = axes[1]
ax.add_patch(Rectangle((0, 0), 105, 68, fill=False, ec="black"))
for (i, j) in cells:
    ax.add_patch(Rectangle((i, j), 1, 1, color="green", alpha=0.4))
ax.set_title(f"网格覆盖 {area:.0f} m²({area/7140*100:.1f}%)")
ax.set_xlim(-2, 107); ax.set_ylim(-2, 70)
ax.set_aspect("equal")
plt.tight_layout()
plt.show()

进阶:时间加权覆盖面积

实际问题里"站 1 秒"和"冲刺经过"权重不同,可按停留时间加权:

from collections import Counter
ix = (df["x"]).astype(int).clip(0, 104)
iy = (df["y"]).astype(int).clip(0, 67)
visit = Counter(zip(ix, iy))
# 每帧 0.1s,加权面积 = 每个格子停留时间占比
total_time = df["t"].iloc[-1] - df["t"].iloc[0]
weight = df["t"].diff().fillna(0.1).mean()  # 采样间隔
weighted_area = sum(min(c * weight, 1.0) for c in visit.values())
print(f"时间加权有效覆盖: {weighted_area:.1f} m²")

完整工程化建议

  1. 数据清洗:剔除静止帧(速度 < 0.5 m/s)会显著影响"跑动"覆盖面积
  2. 多目标:每个球员 ID 单独计算,再聚合为团队覆盖
  3. 攻防分区:把面积按前/中/后场切分,分析球员在哪片区域跑动多
  4. 对比分析:与同位置均值对比 → 生成雷达图(覆盖面积、冲刺距离、高强度跑)

速度过滤示例:

dt = 0.1
dx = df["x"].diff().fillna(0)
dy = df["y"].diff().fillna(0)
speed = np.hypot(dx, dy) / dt         # m/s
moving = df[speed > 0.5]              # 只保留跑动帧
cells_moving, area_moving, _ = coverage_grid(moving, cell=1.0)
print(f"仅跑动时的覆盖面积: {area_moving:.1f} m²")

小结

方法 优点 缺点 适用
凸包 一行代码 严重高估(外场大片空白会算进去) 粗略对比
凹包 Alpha Shape 贴合形状 需调 α 单球员展示
网格覆盖率 直观、可加权 受网格大小影响 推荐生产使用

需要我继续扩展视频 AI 追踪(YOLOv8 + ByteTrack 提取球员坐标)或者多球员对比可视化吗?

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