Skip to content
Permalink
Browse files

Calculate direction of ball

  • Loading branch information...
andreasschmidtjensen committed Mar 14, 2019
1 parent 49949a7 commit 96770918a9bbcce417f2dec9de42b681688d349c
Showing with 80 additions and 84 deletions.
  1. +8 −2 start.py
  2. +46 −0 tablesoccer/Ball.py
  3. +2 −0 tablesoccer/Controller.py
  4. +2 −2 tablesoccer/Detector.py
  5. +19 −18 tablesoccer/Position.py
  6. +3 −62 tablesoccer/SoccerField.py
@@ -1,7 +1,9 @@
import cv2
import imutils

from tablesoccer import Controller


def setup_window(name, x, y):
cv2.namedWindow(name)
cv2.moveWindow(name, x, y)
@@ -13,8 +15,9 @@ def setup_window(name, x, y):

source_type = 'webcam'
path = 0
imwrite = True

d = Controller(source_type, path)
d = Controller(source_type, path, debug=True)
d.start()

while True:
@@ -25,12 +28,15 @@ def setup_window(name, x, y):

if raw is not None:
cv2.imshow('Raw image', raw)
if imwrite: cv2.imwrite('raw.jpg', raw)

if env is not None:
cv2.imshow('Environment', env)
if imwrite: cv2.imwrite('env.jpg', env)

if transformed is not None:
cv2.imshow('Transformed image', transformed)
cv2.imshow('Transformed image', imutils.resize(transformed, width=250))
if imwrite: cv2.imwrite('transformed.jpg', transformed)

cv2.waitKey(1)

@@ -1,9 +1,15 @@
import datetime

import numpy as np

from tablesoccer.Position import Position


class Ball:
def __init__(self):
self.position = Position(10)
self.history = []
self.direction = ""

def update(self, detection):
if detection is None:
@@ -12,7 +18,47 @@ def update(self, detection):
return

if len(detection) > 0:
# save position from detection
self.position.update_position(detection[0][2][0], detection[0][2][1])
# update history
self.history.append({"ts": datetime.datetime.now(), "position": self.get_position()})
self.history = self.history[-100:] # keep last 100 positions
# check if direction has changed
self.update_direction()

def get_position(self):
return self.position.get_position()

def update_direction(self):
if len(self.history) > 10:
# check direction between current and 10 frames ago
prev = self.history[-10]["position"]
this = self.history[-1]["position"]

dX = prev[0] - this[0]
dY = prev[1] - this[1]

(dirX, dirY) = ("", "")

# ensure there is significant movement in the x-direction
if np.abs(dX) > 10:
dirX = "Left" if np.sign(dX) == 1 else "Right"

# ensure there is significant movement in the y-direction
if np.abs(dY) > 10:
dirY = "Up" if np.sign(dY) == 1 else "Down"

# handle when both directions are non-empty
if dirX != "" and dirY != "":
self.direction = "{}-{}".format(dirY, dirX)

# otherwise, only one direction is non-empty
else:
self.direction = dirX if dirX != "" else dirY

if self.direction == "":
if "dir" in self.history[-2]:
# update direction to last known direction if nothing found
self.direction = self.history[-2]["dir"]

self.history[-1]["dir"] = self.direction
@@ -48,6 +48,7 @@ def run(self):
frame = get_image(self.source)
frame = imutils.resize(frame, width=400)

# 1) DETECT CORNERS
if self.detector.corners is None or self.recalculate:
print("** Recalculating field **")
self.detector.calculate_field(frame)
@@ -60,6 +61,7 @@ def run(self):
img = cv2.circle(img, (int(c[0]), int(c[1])), 2, (255, 255, 120), 2)
self.snapshots["RAW_DETECTIONS"] = img

# 2) TRANSFROM IMAGE AND DO DETECTIONS
if self.detector.corners is not None:
if self.debug:
self.snapshots["CORNER_CALC"] = self.detector.calc_image
@@ -33,8 +33,8 @@


ACTUAL_RADIUS = 2
ACTUAL_WIDTH = 31
ACTUAL_HEIGHT = 21
ACTUAL_WIDTH = 32
ACTUAL_HEIGHT = 22


class Detector:
@@ -1,26 +1,27 @@
import numpy as np


class Position:
def __init__(self, length=10, precision=2):
self.length = length
self.precision = precision
def __init__(self, length=10, precision=2):
self.length = length
self.precision = precision

self.x = []
self.y = []
self.x = []
self.y = []

def update_position(self, x, y):
self.x = [round(x, self.precision)] + self.x[0:self.length-1]
self.y = [round(y, self.precision)] + self.y[0:self.length-1]
def update_position(self, x, y):
self.x = [round(x, self.precision)] + self.x[0:self.length - 1]
self.y = [round(y, self.precision)] + self.y[0:self.length - 1]

def get_position(self):
average = self.__moving_average()
return np.array(average) if average is not None else None
def get_position(self):
average = self.__moving_average()
return np.array(average) if average is not None else None

def remove_last(self):
self.x = self.x[:-1]
self.y = self.y[:-1]
def remove_last(self):
self.x = self.x[:-1]
self.y = self.y[:-1]

def __moving_average(self):
if len(self.x) == 0:
return None
return sum(self.x) / len(self.x), sum(self.y) / len(self.y)
def __moving_average(self):
if len(self.x) == 0:
return None
return sum(self.x) / len(self.x), sum(self.y) / len(self.y)
@@ -1,11 +1,6 @@
import cv2

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

ACTUAL_RADIUS = 2
ACTUAL_WIDTH = 33
ACTUAL_HEIGHT = 23


class SoccerField:
@@ -16,63 +11,6 @@ def __init__(self):
self.ball = Ball()
self.players = None

def calc_field(self, detections):
"""
Calculate the location of the field within the image based on the 'field_center' class
R = actual radius of center
W = actual length of field
H = actual height of field
(x,y) = detected center coordinates
r_w = detected width of center
r_h = detected height of center
w = calculated length of field
h = calculated height of field
radius ratio = r / R
w = r_w / R * W
h = r_h / R * H
(0,0) = (x - w / 2, y - h / 2)
:param detections: The map of all detections in the current frame.
:return: None - stores field information in class variables
"""

field_center = None
for d in detections:
if d[0] == 'field_center':
field_center = d
break

if field_center is not None:
# detection of center
field_x = field_center[2][0]
field_y = field_center[2][1]
field_w = field_center[2][2]
field_h = field_center[2][3]

self.ratio_width = field_w / ACTUAL_RADIUS
self.ratio_height = field_h / ACTUAL_RADIUS

self.board_width = field_w / ACTUAL_RADIUS * ACTUAL_WIDTH
self.board_height = field_h / ACTUAL_RADIUS * ACTUAL_HEIGHT

self.top_left = (field_x - self.board_width / 2, field_y - self.board_height / 2)
self.top_right = (field_x + self.board_width / 2, field_y - self.board_height / 2)
self.bottom_right = (field_x + self.board_width / 2, field_y + self.board_height / 2)
self.bottom_left = (field_x - self.board_width / 2, field_y + self.board_height / 2)

self.center = (field_x, field_y)

if self.players is None:
self.players = Players(self.top_left[0], self.top_right[0], rows=4)

self.ball.update(detections)
if self.players is not None:
self.players.update(detections)

def update(self, detector):
self.center = detector.center
self.corners = detector.corners
@@ -94,6 +32,9 @@ def draw(self, canvas):
:return:
"""
if self.center is not None:
cv2.putText(canvas, "DIR: %s" % self.ball.direction, (0, 80),
cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (120, 255, 50), 1)

canvas = cv2.circle(canvas, tuple(self.center.astype(int)), 2, (120, 255, 255), 2)

ball_pos = self.ball.get_position()

0 comments on commit 9677091

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