实用脚本如何量化球员的跑动覆盖面积?

wen 实用脚本 2

本文目录导读:

实用脚本如何量化球员的跑动覆盖面积?

  1. 核心概念:什么是“覆盖面积”?
  2. 方案一:基于位置数据(GPS/北斗)—— 最精确(推荐)
  3. 方案二:基于视频(OpenCV + YOLO)—— 低成本实用方案
  4. 脚本实现建议(实用工具链)
  5. 几个工程上的注意点(避免踩坑)

量化球员的跑动覆盖面积(通常称为覆盖热区控制面积)在职业足球中通常依赖昂贵的GPS追踪和EPTS(电子性能与追踪系统),但如果你需要一套实用、可操作的脚本方案,可以从两个维度入手:基于视频(计算机视觉)基于位置数据(GPS/坐标)

以下我为你设计一套从数据获取到计算的核心算法逻辑,并附上Python伪代码/核心片段。

核心概念:什么是“覆盖面积”?

我们通常不计算球员跑过的“线条长度”,而是计算他实际影响的二维区域,最科学的量化方法是Voronoi(沃罗诺伊)图凸包(Convex Hull)算法。


基于位置数据(GPS/北斗)—— 最精确(推荐)

如果你能拿到球队的GPS数据(每秒输出X/Y坐标)或通过视频抽帧得到的坐标,这是最标准的做法。

数据清洗与坐标转换

  • 输入(timestamp, player_id, x, y) 数组,坐标通常为0-1的相对值或球场米制(105m x 68m)。
  • 处理:剔除静止或异常跳变点(GPS漂移)。

核心算法:凸包(Convex Hull)+ 栅格化

这是计算“活动范围”最直接的方法,它描绘了球员跑动足迹的最外围轮廓。

import numpy as np
from scipy.spatial import ConvexHull, Voronoi, voronoi_plot_2d
import matplotlib.pyplot as plt
from shapely.geometry import Polygon, Point
def calculate_activity_area(points_xy, field_length=105, field_width=68):
    """
    计算球员跑动的活动面积(基于凸包)
    points_xy: list of (x, y),单位应为米(若为0-1则乘以场地长宽)
    """
    if len(points_xy) < 3:
        return 0.0, None
    points = np.array(points_xy)
    # 将相对坐标转换为绝对米数(如果输入是0-1)
    # points[:, 0] *= field_length
    # points[:, 1] *= field_width
    # 计算凸包(最外围的脚印)
    hull = ConvexHull(points)
    area = hull.volume  # 2D下为面积
    # 可视化(可选)
    # plt.plot(points[:,0], points[:,1], 'o')
    # for simplex in hull.simplices:
    #     plt.plot(points[simplex,0], points[simplex,1], 'k-')
    return area, hull

注意:单纯凸包会包含很多球员从未进入的空白区(比如横向跑动时中间的大空当),这在比赛分析中叫跑动足迹面积

进阶算法:Voronoi 空间分割(真实“控制力”面积)

这是更高级的分析,用于衡量传接球瞬间球员的控制范围,如果你有双方所有球员的坐标,可以计算每个球员的专属控制空间面积。

def calculate_voronoi_ownership(all_players_coords, field_bounds=(0,0,105,68)):
    """
    输入:all_players_coords: dict {player_id: [(x1,y1), (x2,y2)...]}
    输出:每帧每个球员的控制面积时间序列
    """
    # 需要按帧处理(每个时间戳),这里仅展示单帧算法
    # 假设 frame_coords: list of [(x,y), player_id]
    # 核心逻辑:创建Voronoi图,然后裁剪到球场边界
    # from scipy.spatial import Voronoi
    # vor = Voronoi(points_cords)
    # 对每个区域进行多边形裁剪,计算面积。
    # 这个计算较复杂,通常需要shapely库配合。
    pass

可视化热力图(叠加面积)

为了直观展示“哪里跑得多”,使用高斯核密度估计(KDE)或简单的Hexbin(六边形分箱)来量化密度最高的区域面积。

def generate_heatmap_area(points_xy, grid_x=100, grid_y=68, threshold=0.5):
    """
    通过核密度估计,计算“高强度”覆盖面积(跑动多的区域)
    """
    from sklearn.neighbors import KernelDensity
    xy = np.array(points_xy)
    # 网格化
    x_grid = np.linspace(0, 105, grid_x)
    y_grid = np.linspace(0, 68, grid_y)
    X, Y = np.meshgrid(x_grid, y_grid)
    grid_pts = np.vstack([X.ravel(), Y.ravel()]).T
    # 计算密度
    kd = KernelDensity(kernel='gaussian', bandwidth=3.0) # 带宽3米
    kd.fit(xy)
    log_density = kd.score_samples(grid_pts)
    density = np.exp(log_density).reshape(grid_x, grid_y)
    # 定义阈值:只计算密度高于最高密度50%的区域面积
    threshold_val = density.max() * threshold
    active_area = np.sum(density > threshold_val) * (105/grid_x) * (68/grid_y)
    return active_area

基于视频(OpenCV + YOLO)—— 低成本实用方案

如果你没有GPS,可以通过单摄像机或双摄像机视频抽帧,使用目标检测(yolov8-pose或yolov8)识别球员,然后计算像素距离换算成米。

步骤:

  1. 相机标定:找到视频中球场四条边线的透视变换矩阵(Homography),将像素坐标映射到米制坐标(这一步是精度关键)。
  2. 目标检测与跟踪:使用YOLO检测球员,使用ByteTrack或DeepSORT跟踪,获取每帧每个球员的“脚部”像素坐标(通常取检测框底部中心)。
  3. 坐标映射:应用计算的单应性矩阵(H矩阵)投影到标准球场坐标。
  4. 调用上述方案一的算法(凸包 / 热力图)。

关键代码片段(透视变换):

# 假设你已通过点击4个角点得到 src_points(像素)和 dst_points(米制)
H, _ = cv2.findHomography(src_points, dst_points)
# 对于每个检测到的脚部坐标 (px, py)
# 转为齐次坐标
point = np.array([px, py, 1.0])
# 投影到球场平面
projected = np.dot(H, point)
projected = projected / projected[2]  # 归一化
x_m, y_m = projected[0], projected[1]

脚本实现建议(实用工具链)

为了确保实用,建议使用以下库组合,并封装成命令行工具:

pip install numpy scipy pandas matplotlib opencv-python ultralytics scikit-learn shapely

完整的“实用脚本”伪代码结构:

import argparse
import pandas as pd
from scipy.spatial import ConvexHull
def main(csv_path, output_path):
    # 1. 读取数据
    # 假设CSV有列: timestamp, player_id, x, y (单位: 米)
    df = pd.read_csv(csv_path)
    results = []
    for player_id, group in df.groupby('player_id'):
        points = group[['x', 'y']].values
        # 过滤掉站位时间过长的静止点(速度<0.3m/s认为静止)
        # 核心计算
        area, hull = calculate_activity_area(points)
        # 额外计算:总跑动距离
        distance = np.sum(np.sqrt(np.sum(np.diff(points, axis=0)**2, axis=1)))
        results.append({
            'player_id': player_id,
            'activity_area_m2': round(area, 2),  # 平方米
            'total_distance_m': round(distance, 2),
            'area_per_minute': round(area / (len(group) / 25), 2)  # 假设25帧/秒
        })
    output_df = pd.DataFrame(results)
    output_df.to_csv(output_path, index=False)
    print(output_df)
if __name__ == "__main__":
    main('player_positions.csv', 'analysis_output.csv')

几个工程上的注意点(避免踩坑)

  1. 数据粒度:运动轨迹数据采样频率至少1Hz(每秒1次),推荐5Hz以上,否则凸包面积会偏小20%左右。
  2. 边界裁切:计算出的凸包必须裁切在球场边界内(105x68m),否则会出现球员跑出界外导致面积虚高。
  3. “面积”的陷阱:单纯用凸包面积无法反映跑动有效性,建议输出时配合加权密度面积(跑动速度>14km/h的冲刺面积)一起分析。
  4. 坐标系偏移:不同队进攻方向不同,建议将进攻方向统一为从左到右,否则计算出的面积不对称,难以比较。
方法 精度 成本 实时性
GPS/坐标法(凸包) 高(±1m) 需设备
视频法(Homography) 中(±3m) 需标定
Voronoi 控制面积 非常高 需全部球员数据

如果你手头有现成的Data(比如从StatsBomb或Wyscout导出的自由数据),直接使用方案一的凸包算法是最快出结果的方式,如果需要跑通整个流程,可以参考我上面的代码框架进行二次开发。

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