import abc
from gbvision.constants.types import Number, Frame, Shape
from gbvision.utils.shapes.base_shape import BaseShapeType
from gbvision.utils.tracker import Tracker
[docs]class ContinuesShape(abc.ABC):
"""
An abstract class who's target is to determine in which case two shapes are describing
the same object, and by that represent a shape in a continues matter.
in case that no shape matching the object is found then the tracker is deployed by using force_update.
:param shape: the shape to track with continuity
:param frame: the frame from which the shape was taken
:param tracker: optional, a tracker used to track the object if it wasn't found with continuity (default is an empty tracker)
:param max_area_ratio: the maximum ration between areas of the current shape and another shape \
so that they can be thought of as the same, default is 2.0
:param max_distance_ratio: the maximum ratio between the sum of areas of this shape and another and their distance, default is 0.1
"""
def __init__(self, shape, frame: Frame, tracker: Tracker = None, max_area_ratio=2.0,
max_distance_ratio=2.0): # initialization method of the abstract class
assert max_area_ratio > 1.0 # sets maximum area ratio as 1
self._shape = shape # shape describing the object
self._count = 0 # the count of how many frames the object cannot be found
self._tracker = Tracker() if tracker is None else tracker # Tracker setup in case object is not found
self._tracker.init(frame, self._base_shape().to_bounding_rect(shape))
self.max_area_ratio = max_area_ratio # setting the maximum bound to which the area difference between the
# two shapes can be withstanded until it declared as not the same object
self.max_distance_ratio = max_distance_ratio # same goes here except it refers to distance between shapes
@staticmethod
@abc.abstractmethod
def _base_shape() -> BaseShapeType:
"""
returns the base shape matching this continues shape
:return: the base shape (a class that inherits from BaseShape)
"""
def _shape_collision(self, shape: Shape) -> bool:
return self._base_shape().collision(self._shape, shape)
def _shape_square_distance(self, other_shape: Shape) -> Number:
"""
A method which finds the distance between the centers of the object shape and another one
:param other_shape: The other shape you want to check the distance to
:return: The distance between the shape squared in order to save computing of square root (pythagorean theorem)
"""
return self._base_shape().distance_squared(self._shape, other_shape)
def _is_legal(self, shape: Shape) -> bool:
"""
checks a variety of different relations between to shapes to determine whether it describes the same object or not
:param shape: the other shape comparing to the current one
:return: true or false, same or different
"""
if self._shape_collision(shape):
if 1.0 / self.max_area_ratio <= self._base_shape().area(self._shape) / self._base_shape().area(
shape) <= self.max_area_ratio:
if self._shape_square_distance(shape) <= (
self._base_shape().area(self._shape) + self._base_shape().area(shape)) * self.max_distance_ratio:
return True
return False
[docs] def get(self) -> Shape:
"""
retrieve the shape that this continues shape tracks
:return: this continues shape's inner shape
"""
return self._shape
[docs] def update(self, shape: Shape, frame: Frame) -> bool:
"""
an annual check updating the location and data of the object
:param shape: the shape suspect as the same object
:param frame: the frame on which the suspect shape is
:return: true or false, same shape, not the same shape
"""
if self._is_legal(shape):
self._shape = shape
self._count = 0
self._tracker.init(frame, self._base_shape().to_bounding_rect(shape))
return True
return False
[docs] def update_forced(self, frame: Frame):
"""
an update which happens when you lost the shape with continuity
:param frame: the frame on which opencv2 tracking is happening
"""
self._shape = self._base_shape().from_bounding_rect(self._tracker.update(frame))
self._count += 1
[docs] def is_lost(self, max_count: int) -> bool:
"""
check if it has been too long (more that max_count frames) since you last saw the object
:param max_count:the maximum amount of frames tolerable before the shape is declared lost, None is infinity
:return: lost or not
"""
return max_count is not None and self._count > max_count