Skip to content
Permalink
Browse files

Added team to player rows

Calculate ball possession for each player and for each team
Visualized ball possession
  • Loading branch information...
andreasschmidtjensen committed Mar 15, 2019
1 parent 1b1f723 commit 25335dce610c37fa3302e73ce1dfe1194c2e2caf
Showing with 76 additions and 9 deletions.
  1. +2 −2 tablesoccer/Detector.py
  2. +44 −3 tablesoccer/Players.py
  3. +30 −4 tablesoccer/SoccerField.py
@@ -101,7 +101,7 @@ def calculate_field(self, frame):

if players is None:
# initialize players with the raw calculation of board location
players = Players(raw_top_left[0], raw_top_right[0], row_config=(3, 3, 3, 3))
players = Players(raw_top_left[0], raw_top_right[0])

players.update(detection_map.get('player'))

@@ -138,6 +138,6 @@ def detect(self, frame):
self.ball.update(detection_map.get('ball'))

if self.players is None:
self.players = Players(0, self.raw_image.shape[1], row_config=(3, 3, 3, 3))
self.players = Players(0, self.raw_image.shape[1])

self.players.update(detection_map.get('player'))
@@ -3,16 +3,23 @@
ROTATION_MIN = 0.5
ROTATION_MAX = 2.0

REACH_WIDTH = 100
REACH_HEIGHT = 75

TEAM_HOME = 0
TEAM_AWAY = 1


class Players:
def __init__(self, field_x_start, field_x_end, row_config=(3, 3, 3, 3)):
def __init__(self, field_x_start, field_x_end,
row_config=((3, TEAM_HOME), (3, TEAM_AWAY), (3, TEAM_HOME), (3, TEAM_AWAY))):
self.rows = len(row_config)
self.field_x_start = field_x_start
self.field_x_end = field_x_end

self.row_size = (self.field_x_end - self.field_x_start) / self.rows

self.players = [Row(i, num_players) for i, num_players in enumerate(row_config)]
self.players = [Row(i, config[0], config[1]) for i, config in enumerate(row_config)]

def update(self, detections):
for r in self.players:
@@ -38,6 +45,23 @@ def get_row(self, row):
else:
return None

def calculate_possession(self, ball_position):
if ball_position is None:
return None

team_possession = [0, 0]
player_possession = []
for row in self.players:
row.calculate_possession(ball_position)

team = row.team
possession = row.possession
team_possession[team] += np.sum(possession)
player_possession.append(possession)

return team_possession / np.sum(team_possession), \
player_possession / np.sum(player_possession)


class Row:
"""
@@ -48,15 +72,18 @@ class Row:
When re-detecting the field, we recreate the row, but since the position of the
players can't change, we will get the same configuration again with updated positions.
"""
def __init__(self, row_number, num_players):
def __init__(self, row_number, num_players, team):
self.row_number = row_number
self.num_players = num_players
self.team = team

self.players = []
self.rotation = 0.0

self.x_coordinate = None

self.possession = [0 for _ in range(num_players)]

def reset(self):
self.players = []
self.rotation = 0.0
@@ -79,6 +106,20 @@ def add_player(self, player):
self.calculate_rotation()
self.x_coordinate = np.mean([p[0] for p in self.players])

def calculate_possession(self, ball_position):
for i, player in enumerate(self.players):
reach_x_start = player[0] - REACH_WIDTH / 2
reach_x_end = player[0] + REACH_WIDTH / 2
reach_y_start = player[1] - REACH_HEIGHT / 2
reach_y_end = player[1] + REACH_HEIGHT / 2

ball_x = ball_position[0]
ball_y = ball_position[1]

if reach_x_start < ball_x < reach_x_end and reach_y_start < ball_y < reach_y_end:
self.possession[i] += 1
break

def get_players(self):
return self.players

@@ -1,8 +1,8 @@
import cv2
from PIL import ImageFont, ImageDraw, Image
import numpy as np

from tablesoccer.Ball import Ball
import tablesoccer.Players as Players


class SoccerField:
@@ -13,13 +13,20 @@ def __init__(self):
self.ball = Ball()
self.players = None

self.possession = None

def update(self, detector):
self.center = detector.center
self.corners = detector.corners

self.ball = detector.ball
self.players = detector.players

if self.players is not None:
calc = self.players.calculate_possession(self.ball.get_position())
if calc is not None:
self.possession = calc # to avoid overwriting an existing value with None if ball is out of sight

def draw(self, canvas):
"""
Draw the current state of the environment on a canvas.
@@ -37,10 +44,15 @@ def draw(self, canvas):
font = ImageFont.truetype("Roboto-Regular.ttf", 12)

im = Image.fromarray(canvas)
draw = ImageDraw.Draw(im)
draw = ImageDraw.Draw(im, 'RGBA')

draw.text((0, 0), "Direction: %s" % self.ball.direction, font=font)

if self.possession is not None:
draw.text((canvas.shape[1]-100, 0),
"Pos.: %.0f%% - %.0f%%" % (self.possession[0][0]*100, self.possession[0][1]*100),
font=font)

center_bb = (self.center[0] - 2, self.center[1] - 2, self.center[0] + 2, self.center[1] + 2)
draw.ellipse(center_bb, (120, 255, 255, 255))

@@ -51,12 +63,26 @@ def draw(self, canvas):

players = self.players
if players is not None:
for i, row in enumerate(players.players):
for r, row in enumerate(players.players):
draw.text((int(row.x_coordinate - 20), 25), "%.2f%%" % (row.rotation * 100), font=font)

for player in row.get_players():
for p, player in enumerate(row.get_players()):
if len(player) > 0:
x, y = player[0], player[1]

# draw reach
opacity = 140
if self.possession is not None:
player_pos = self.possession[1]
if len(player_pos) > r and len(player_pos[r]) > p:
opacity = int(opacity * self.possession[1][r][p])
reach = (x - Players.REACH_WIDTH / 2,
y - Players.REACH_HEIGHT / 2,
x + Players.REACH_WIDTH / 2,
y + Players.REACH_HEIGHT / 2)
draw.rectangle(reach, fill=(255, 255, 255, opacity), outline=(255, 255, 255, 140), width=2)

# draw player location
bb = (x - 2, y - 2, x + 2, y + 2)
draw.ellipse(bb, (255, 255, 120, 255))

0 comments on commit 25335dc

Please sign in to comment.
You can’t perform that action at this time.