** 使用python绘制球场和传球事件**

1.绘制一个标准足球场

1.工具:matplotlib库

matplotlib库为我们在python中提供了一套绘图的工具。

基本概念

figure:用于绘制的面板(一个窗口)

axis:子图(在窗口内指定位置的绘图区域)

2.绘制思路

1. 初始化创建面板及子图

*初始使用plt.figure时发现最终得到的面板大小不是固定的,阅读doc发现存在参数dpi可以调整显示大小

image-20200928231732962.png

测试后调整为200以在1080p分辨率获得相对合适的窗口大小

2. 两个矩形的绘制

这里调用plot函数绘制连续点(即线),color控制线条颜色,为了美观设定线条宽度为2

绘制效果如图:

image-20200928235057831.png

 

3. 绘制足球场正中的圆形区域和罚球点

这里使用plt.circle函数直接绘制圆形,设定坐标为球场中点后确定半径使用参数fill=False避免填充,罚球点则默认填充

然后使用add_patch函数将两个圆形对象添加到子图ax中

效果如图:

image-20200928235208969.png

4. 绘制左右侧禁区及圆弧

绘制圆弧时用到arc函数,传入圆心位置(不实际存在)等参数后,同样通过add_patch添加到子图中,其他区域的绘制与前面相似

绘制结果如图:

image-20200928235632502.png

5.绘制完成后发现的问题

绘制结束后发现拖动窗口大小会导致坐标轴比例不一致,在阅读doc并利用搜索引擎后,找到了解决办法:

在初始化子图时加入此参数将默认保持比例相等到结束

2.用matplotlib画出所有传球事件并生成视频

1.工具:matplotlib库&pandas库

matplotlib自带的animation库可以满足绘制动画的需求,尝试使用python自带的csv库后发现pandas提供的csv文件读取功能更加强大,于是使用pandas

2.读取passingevents.csv的数据

首先分析我们需要用到的数据:

传球球员的ID 球员传球时所在的x,y坐标
接球球员的ID 球员接球时所在的x,y坐标
当前比赛的场次ID当前事件发生的时间
当前时间所在上下半场传球者团队ID

使用pandas.read_csv,添加参数index_col=None取消以第一列(MatchID)作为索引,读取到的各列数据以数组形式返回

3.使用matplotlib.animation.FuncAnimation绘制视频

1.函数认知

image-20200929003352415.png

阅读matplotlib的doc可以看到大量参数,其中关键的参数有:

  1. image-20200929003553779.png

    fig,用于传入要使用的面板

  2. image-20200929003728142.png

    func,用于绘制每一帧画面,FuncAnimation将在执行过程中反复调用该函数直到结束以绘制每一帧图像,也是逐帧绘图的核心函数

  3. image-20200929004322865.png

    init_func,绘制第一帧画面,如果没有该参数,将会以func中的第一帧作为初始化帧(在搜索引擎得知,有时不启用此参数可能会导致第一帧无法刷新)

*其他的参数在初始考虑时并没有涉及到,故等到使用时再作解释

2.伟大的第一步,试图绘制第一帧!

第一帧的绘制其实相当简单,整个过程的图形建立在一个足球场的背景下,所以我们使用之前绘制的标准足球场的代码直接建立init_func()函数:

3.更加伟大的第二步,依据表格数据与绘制建立联系绘制每一帧

一开始我的绘制思路是,在每一次func调用中绘制出传球者、接球者的位置(用圆点表示)

通过对csv数据的读取,我们很容易得到了每一次传球事件两个球员的坐标等数据,利用plot函数,首先初始化各对象(最终要以元组的方式返回):

update_frames(num)传入的参数num是可迭代的,在

中,interval作为两帧之间的间隔,单位为毫秒(ms),blit=True确定了每一帧的刷新方式,即只绘制变化的内容,这样可以避免已绘制的帧仍然留在画面上的问题(并一定程度减轻性能消耗),repeat参数确定在播放完所有帧后是否重复播放。

然后依据获得的数据,调用之前读取的数组进行绘制:

最初版本绘制的效果其实很不理想,我们观看到的动画非常僵硬地逐帧显示了两个不明所以的附带名称的圆点以及位于画面左右上角的时间等数据,以下是我依据回忆复刻代码的100帧gif实例:

plswork.gif

在这里我发现,球员的位置似乎总是在左半场运动,在启用blit=False参数后,我看到了每一帧存留的记录,显示结果确实如此,回看csv表格,才发现x,y坐标均在(0,100),此时才想起来修改Ox*1.3Oy*0.9(与我绘制的球场尺寸匹配)

4.优化实现结果

按照题目的要求,此时已经算得上“绘制出所有的传球事件”

但是这样的视频显然缺乏可读性,所以,还要继续优化这个结果。

考虑到事件发生的非连续性,直接绘制运动的球员显然不太显示,但是可以让图像显示的顺序来暗示球传递的方向,依据这个原理,我们可以把原先的n帧画面拆分成3n帧画面:

在3n帧画面中,可分为n组,每一组由3帧组成,依照传球球员、传球轨迹、接球球员的顺序逐帧刷新,同时保留该组之前的帧,在结果看起来,就可以产生传球的方向感:

实现起来也很简单(为了更加易读将函数尽可能分块,但应该还有更高效的方法):

*其中还添加了一段线段line用以更加清晰地表示传球路径,并在line中点处添加注释表示传球类型

5.完整代码及结果实例

*因为数据量较大,在保存过程中输出保存进度百分比以了解保存状态

实例(取500帧,间隔150ms,速度较快):

plswork-2.gif

6.优化更新

采用每一帧不自动擦除的方式绘制,减少了代码量,也提高了可读性,从177行缩减到了120行(含注释空格)

可以做到完全相同的效果,而且似乎降低了占用:

abc

3.绘制所有球员的所有信息统计

1.确定球员所有的信息

翻看readme可以发现,球员具有的事件信息有:

于是想到用条形图来绘制所有的事件,又因为球员数量较大,所以还是可以使用逐帧绘制动画的方法来完成。

2.绘制每个球员的事件

首先将事件信息存储起来便于直接使用:

读取文件:

每一帧的绘制函数:

绘制动画:

最终的效果如图:

status.gif

完整代码如下: