#!/usr/bin/env python
# -*- coding: utf-8 -*-
# (C) 2017 David Toro <davsamirtor@gmail.com>
# compatibility with python 2 and 3
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
from builtins import object
from past.utils import old_div
# import build-in modules
import os
from collections import deque
# import third party modules
#from RRtoolbox.lib.plotter import fastplt # DEBUG
#from RRtoolbox.lib.arrayops import overlay
from .core import Space, Group, Agent, cv_major_ver, xrange, TailItem, Point
from .array_utils import norm_range, draw_contour_groups, is_numpy
import numpy as np
import cv2
# special variables
# __all__ = []
__author__ = "David Toro"
# __copyright__ = "Copyright 2017, The <name> Project"
# __credits__ = [""]
__license__ = "GPL"
# __version__ = "1.0.0"
__maintainer__ = "David Toro"
__email__ = "davsamirtor@gmail.com"
# __status__ = "Pre-release"
SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) # get current file path
try:
DETECTOR_PATH = cv2.data.haarcascades # comes with "pip install opencv-contrib-python"
except AttributeError:
DETECTOR_PATH = os.path.join(SCRIPT_PATH, "./haarcascades/") # "/usr/local/share/OpenCV/haarcascades/"
face_cascade = cv2.CascadeClassifier(os.path.abspath(os.path.join(DETECTOR_PATH, 'haarcascade_frontalface_default.xml')))
eye_cascade = cv2.CascadeClassifier(os.path.abspath(os.path.join(DETECTOR_PATH, 'haarcascade_eye.xml')))
[docs]class Detector(Space):
"""
Here a Detector creates an Object or Entity from the real world
which will have its own behaviour or "personality". This Detector is
the one that classifies the objects and finds them in the real world
if they are "lost" or they are not in the scenes anymore until
they reappear again.
"""
available_detectors = {}
def __init__(self):
# private Detector color
self._BGR_color = None
self.objects = Group(_space_parent=self, name="objects")
[docs] def active_objects(self):
"""
:return: objects that are active regardless if they are tracking
"""
for o in self.objects:
if o.active:
yield o
[docs] def inactive_objects(self):
"""
:return: objects that are not active
"""
for o in self.objects:
if not o.active:
yield o
[docs] def tracked_objects(self):
"""
:return: objects that are active and are tracking
"""
for o in self.objects:
if o.active and o.is_tracking:
yield o
[docs] def untracked_objects(self):
"""
:return: objects that are not active or are not tracking
"""
for o in self.objects:
if not o.active or not o.is_tracking:
yield o
[docs] def track_objects(self, frame, mask=None):
"""
only update without creating new objects
:param frame:
:param mask:
:return:
"""
for o in self.objects:
if o.active:
o.update(frame, mask)
[docs] def detect_raw_objects(self, frame, mask=None):
"""To modify behaviour of detection"""
tail_objects = None
return tail_objects
[docs] def filter_bad_raw_objects(self, tail_objects, frame, mask=None):
return
[docs] def process_raw_objects(self, frame, tail_items, bad_items, mask=None):
"""
create new object or reuse object from a tail_item
:param frame:
:param tail_items:
:param mask:
:return:
"""
#TODO objects should not be created inside other objects
#FIXME if object dissappears from a period of time it is deleted but it should be restored (solve: do not touch o._stray_count but device a mechanism to move object to that new position)
#TODO needs to implement the features of obejcts. This process should be paralell
#TODO needs to implement paralelization of obejcts so that it does not take a lot of time if objects are increased
# create dictionary to classify
# format (inside, near)
active_objects_dic = {i:([],[]) for i in self.active_objects()}
# unclassified
unclassified = []
# classify all tail_items with objects
for ti in tail_items:
near_flag = False
for o, (inside, near) in active_objects_dic.items():
# find out if cnt overlaps with objects from this detector
# faster check
if o.tail[0].point_inside(ti):
# tail_item inside Object
# do not let object tracker grow too much
#o.update_tracker(frame, tail_item=ti)
inside.append(ti)
break
elif ti.point_inside(o.position):
# Object inside tail_item
# if object lost track and it was detected again
#o.update_tracker(frame, tail_item=ti)
inside.append(ti)
break
#elif o.tail[0].cnt_intersect(ti): # covered by cnt_near
# # Object and tail_item touch
# break
else:
flag, dist = o.tail[0].cnt_near(ti)
if flag:
# it is near an object do not create new
near_flag = True
# append to near raw_objects of object
#near.append(ti)
# continue finding objects that could contain the raw_obejct
continue
else:
if not near_flag and ti not in bad_items:
# create new object only if raw_object is not any of the
# objects that are being tracked
o = Object(frame=frame, parent_detector=self,
tail_item=ti, mask=mask)
self.objects.add_as_contained(o)
unclassified.append(o)
# create or reuse objects as needed
#for ti in unclassified:
# o = Object(frame=frame, parent_detector=self,
# tail_item=ti, mask=mask)
# self.objects.add_as_contained(o)
for o, (inside, near) in active_objects_dic.items():
if not inside:
if not o.is_tracking:
o._stray_count += 1
else:
# group tail items and update tracker
cont = np.vstack(inside)
hull = cv2.convexHull(cont)
o.update_tracker(frame, cnt=hull)
return unclassified
[docs] def delete_stray_objects(self):
"""
delete all objects that are missing the correct target
"""
objects = self.objects
for o in objects:
if (o.active and not o.live_forever and
o._stray_count > o._max_stray_count):
o._space_delete() # delete from all groups
def _compute_objects(self, frame, mask=None, track=True, _debug_good=None,
_debug_bad=None):
"""
complete steps to detect and process objects
:param frame:
:param mask:
:param track:
:param _debug_good:
:return:
"""
# example code
if track:
self.track_objects(frame, mask)
# get raw objects from frame
raw_objects = self.detect_raw_objects(frame, mask)
# if there are raw objects to process
if raw_objects is not None:
# convert raw_objects items to tail items if not already
raw_objects = [i if isinstance(i, TailItem) else TailItem(i)
for i in raw_objects]
# filter raw_objects if necessary
bad_objects = self.filter_bad_raw_objects(raw_objects, frame, mask)
if bad_objects is None:
bad_objects = set()
else:
bad_objects = set(bad_objects) # for fast membership
# debug good raw_objects
if _debug_good is not None:
self._debug_detector(_debug_good, [i for i in raw_objects
if i not in bad_objects])
# debug bad raw_objects
if _debug_bad is not None:
self._debug_detector(_debug_bad, bad_objects)
# process tail item into an object
objs = self.process_raw_objects(frame, raw_objects, bad_objects, mask)
# delete all objects that are missing the correct target
self.delete_stray_objects()
return objs
[docs] def get_BGR_color(self):
"""
get detector color from Parent detector or randomly generated
"""
if self._BGR_color is None:
try:
# inherit color from parent
self._BGR_color = norm_range(self._space_parent.get_BGR_color())
except AttributeError:
self._BGR_color = norm_range(np.random.rand(3)*255)
return self._BGR_color
[docs] @classmethod
def register_detector(cls, detector_class, name=None):
if issubclass(detector_class, Detector) and detector_class is not Detector:
if name is None:
name = detector_class.__name__.lower()
if name in cls.available_detectors:
raise ValueError("name '{}' is already registered".format(name))
cls.available_detectors[name] = detector_class
else:
raise TypeError("class must be a subclass of {}".format(Detector))
[docs] @classmethod
def get_detector(cls, name):
try:
return cls.available_detectors[name]
except KeyError:
try:
return cls.available_detectors[name.lower()]
except KeyError:
raise KeyError("name '{}' is not registered".format(name))
def __json_enco__(self):
pass
def _debug_detector(self, vis, mask):
# construct a mask if mask is cnts
if not is_numpy(mask):
cnts = [i.cnt if isinstance(i, TailItem) else i for i in mask]
mask = draw_contour_groups([cnts], shape=vis.shape, binary=True)
pallet = np.array([[0, 0, 0], self.get_BGR_color()])
a = pallet[(mask > 0).astype(np.int)]
# a = np.zeros_like(frame)
# a[mask>0] = color
vis[mask > 0] = (vis * 0.5 + a * 0.5)[mask > 0].astype(np.uint8)
[docs]def affine(phi, img):
"""
Increase robustness to descriptors by calculating other invariant perspectives to image.
:param phi: rotation of image (in degrees)
:param img: image to find Affine transforms
:param mask: mask to detect keypoints (it uses default, mask[:] = 255)
:return: skew_img, skew_mask, Ai (invert Affine Transform)
Ai - is an affine transform matrix from skew_img to img
"""
h, w = img.shape[:2] # get 2D shape
A = np.float32([[1, 0, 0], [0, 1, 0]]) # init Transformation matrix
if phi != 0.0: # simulate rotation
phi = np.deg2rad(phi) # convert degrees to radian
s, c = np.sin(phi), np.cos(phi) # get sine, cosine components
# build partial Transformation matrix
A = np.float32([[c, -s], [s, c]])
corners = [[0, 0], [w, 0], [w, h], [0, h]] # use corners
tcorners = np.int32(np.dot(corners, A.T)) # transform corners
x, y, w, h = cv2.boundingRect(
tcorners.reshape(1, -1, 2)) # get translations
A = np.hstack([A, [[-x], [-y]]]) # finish Transformation matrix build
img = cv2.warpAffine(
img, A, (w, h), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE)
h, w = img.shape[:2] # get new 2D shape
Ai = cv2.invertAffineTransform(A)
return img, Ai
[docs]class InvariantCascade(object):
def __init__(self, angles=None):
self.angles = angles
[docs] def reconstruct(self, Ai, angle, bbox):
#H = np.linalg.inv(np.append(M, ((0, 0, 1),), 0))
# TODO: virtual coordinates not with real coordinates
x, y, sz_x, sz_y = bbox
xn, yn = np.dot(Ai, (x, y, 1))
return Agent.get_rotated_box_from_bounding_box((xn, yn, sz_x, sz_y))[:-1]+(-angle,)
[docs]class EyeDetector(Detector):
[docs] def detect_raw_objects(self, frame, mask=None):
min_size = 0.1
max_size = 0.5
h, w = frame.shape[:2]
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# http://answers.opencv.org/question/116587/rejectlevels-and-levelweights-of-detectmultiscale/
eyes = eye_cascade.detectMultiScale(gray,
minSize=(int(w * min_size),
int(h * min_size)),
maxSize=(int(w * max_size),
int(h * max_size)),
flags=cv2.CASCADE_SCALE_IMAGE)
return [TailItem(bbox=bbox) for bbox in eyes]
Detector.register_detector(EyeDetector)
[docs]class FaceDetector(Detector):
[docs] def detect_raw_objects(self, frame, mask=None):
min_scale = 0.05 # 0.1
h, w = frame.shape[:2]
minSize = (int(w * min_scale), int(h * min_scale))
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# https://stackoverflow.com/a/20805153/5288758
# https://stackoverflow.com/a/22250382/5288758
#faces = face_cascade.detectMultiScale(gray, minSize=minSize,
# scaleFactor=1.2, minNeighbors=6,
# flags=cv2.CASCADE_SCALE_IMAGE)
#normal = [TailItem(bbox=bbox) for bbox in faces]
# TODO complete invariance
normal = []
to_rotate = InvariantCascade([0, 20, 40, -20, -40])
#to_rotate = InvariantCascade([10])
for M, i, f in to_rotate.transformations(gray):
faces = face_cascade.detectMultiScale(f, minSize=minSize,
scaleFactor=1.3,
minNeighbors=5,
flags=cv2.CASCADE_SCALE_IMAGE)
normal.extend([TailItem(rbox=to_rotate.reconstruct(M, i, bbox)) for bbox in faces])
return normal
Detector.register_detector(FaceDetector)
[docs]class PeopleDetector(Detector):
pass
Detector.register_detector(PeopleDetector)
[docs]class ObjectDetector(Detector):
pass
Detector.register_detector(ObjectDetector)
[docs]class MovementDetector(Detector):
pass
Detector.register_detector(MovementDetector)
[docs]class ColorDetector(Detector):
"""
Detect objects by color
"""
def __init__(self, color_lower, color_upper):
super(ColorDetector, self).__init__()
self.color_lower = norm_range(color_lower)
self.color_upper = norm_range(color_upper)
[docs] def get_HSV_color_range(self):
"""
return lower and upper HSV ranges
"""
return self.color_lower, self.color_upper
[docs] def set_HSV_color_range(self, color_lower=None, color_upper=None):
"""
set lower and upper HSV ranges
"""
if color_lower:
self.color_lower = norm_range(color_lower)
if color_upper:
self.color_upper = norm_range(color_upper)
[docs] def get_HSV_color(self):
"""
return media HSV color from lower and upper HSV ranges
"""
lower, upper = self.get_HSV_color_range()
lower, upper = np.array(lower), np.array(upper)
return (lower+upper)/2
[docs] def get_BGR_color(self):
"""
return media BGR color from lower and upper HSV ranges
"""
return cv2.cvtColor(np.array([[self.get_HSV_color()]], np.uint8),
cv2.COLOR_BGR2HSV)[0, 0]
[docs] def detect_raw_objects(self, frame, mask=None):
blurred = cv2.GaussianBlur(frame, (11, 11), 0)
hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)
# construct a mask for the color, then perform a series of dilation
# and erosion operations to remove any small blobs left in it
mk = cv2.inRange(hsv, self.color_lower, self.color_upper)
mk = cv2.erode(mk, None, iterations=2)
mk = cv2.dilate(mk, None, iterations=2)
# find contours in the mask and initialize the current
# (x, y) center of the ball
cnts = cv2.findContours(mk, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[-2]
return cnts
[docs] def filter_bad_raw_objects(self, tail_objects, frame, mask=None):
# only proceed if at least one contour was found
ignore = False
ignore_z = 0.001 # percent
#z_must_be = np.min(frame.shape[:2])/ignore_z # z as radius
z_must_be = frame.shape[0]*frame.shape[1]*ignore_z # z as area
bad_objects = []
for ti in tail_objects:
#((x, y), z) = cv2.minEnclosingCircle(cnt) # z as radius
M = cv2.moments(ti.cnt)
z = M["m00"] # z as area
x, y = (int(old_div(M["m10"], M["m00"])),
int(old_div(M["m01"], M["m00"])))
# complete more data in tail item
ti._pt = Point(*(x, y, z))
if mask is not None:
# find out if cnt overlaps with objects from other detectors
#ignore = mask[y, x]
pass
# only proceed if cnt is not covered by other trackers
# and z meets a minimum size
if ignore and z < z_must_be:
bad_objects.append(ti)
return bad_objects
@classmethod
def __json_deco__(cls, data):
return cls(data[0], data[1])
def __json_enco__(self):
return self.get_HSV_color_range()
Detector.register_detector(ColorDetector)
[docs]class Object(Agent):
"""
It is any entity in the World that has its own characteristics or
features and that can be tracked in the real world.
"""
def __init__(self, frame, parent_detector,
max_tail_len=30, tracker_type='MEDIANFLOW', key_pts=None,
descriptors=None, **kwargs):
super(Object, self).__init__()
self._space_parent = parent_detector
# private position deltas
(self._dX, self._dY, self._dZ) = (None, None, None)
# private object color
self._BGR_color = None
# private flag to recompute positions and tracked object
self._to_recompute = True
# unique features of object
self.key_pts = key_pts # key points for descriptors
self.descriptors = descriptors # descriptions for unique object
# create deques
self._direction_cover = 10 # last points to use to calculate direction
self.tail = None # deque containing points
self.max_tail_len = max_tail_len # maximum cached points in tail
# tracker mechanism
self.tracker_type = tracker_type
self.tracker = None
self._is_tracking = False
# to delete stray objects
self._stray_count = 0
self._max_stray_count = 10
self.live_forever = False # do not let stray count to delete object
# to detect zones where the object passes
self._in_zones = []
# first data
self.add_to_tail(**kwargs)
# update tracker
self.update_tracker(frame)
@property
def cnt(self):
return self.tail[0].cnt
@cnt.setter
def cnt(self, value):
return
@property
def rotated_box(self):
return self.tail[0].rbox
@rotated_box.setter
def rotated_box(self, value):
return
@property
def dX(self):
"""get X position"""
self.compute() # if necessary update before giving answer
return self._dX
@property
def dY(self):
"""get Y position"""
self.compute() # if necessary update before giving answer
return self._dY
@property
def dZ(self):
"""get Z position"""
self.compute() # if necessary update before giving answer
return self._dZ
@property
def tail_len(self):
return len(self.tail)
@property
def max_tail_len(self):
return self.tail.maxlen
@max_tail_len.setter
def max_tail_len(self, new_len):
if not self._enough_items(new_len):
raise ValueError("max_tail_len must be greater than {} and got "
"'{}'".format(self._enough_items(), new_len))
try:
old_len = self.tail.maxlen
except AttributeError:
# if tail is None force new tail operation
old_len = new_len + 1
if old_len != new_len:
c_len = min([old_len, new_len]) # where tails can be merged
cached_tail = self.tail # old tail
self.tail = deque(maxlen=new_len) # new tail
if cached_tail: # either if None or no tail items
for i, p in enumerate(cached_tail):
if i > c_len:
# break if either, old or new, lack
# the next tail items
break
# add from last tail items
self.tail.append(p)
@property
def is_tracking(self):
return self._is_tracking
@is_tracking.setter
def is_tracking(self, value):
if value and not self._is_tracking:
# tracking again then clean path
last = self.tail[0]
self.tail.clear()
self.tail.append(last)
self._is_tracking = value
@property
def in_zones(self):
return self._in_zones
@in_zones.setter
def in_zones(self, zones):
# leave the new ones in zones and
# find the ones that must be eliminated
to_eliminate = []
_in_zones = self._in_zones
for oz in _in_zones:
if oz in zones:
zones.remove(oz)
else:
to_eliminate.append(oz)
# eliminate zones that object is no more in
for i in to_eliminate:
_in_zones.remove(i)
i._space_remove_child(self)
# add new zones
for nz in zones:
_in_zones.append(nz)
nz._space_add_child(self)
@in_zones.deleter
def in_zones(self):
self.in_zones = []
def _enough_items(self, no_items=None):
"""
test whether number of tail items are enough to be covered
and calculated by the direction attribute
:param no_items: number of items in tail
:return: True if no_items passes can be processed. If no_items is None
it returns the no_items what would be considered True.
"""
to_cover = self._direction_cover * 2
if no_items is None:
return to_cover
if no_items >= to_cover:
return True
@property
def position(self):
"""
:return: last point or position
"""
return self.tail[0].pt
[docs] def get_BGR_color(self):
"""
get object color assigned from Detector or randomly generated
"""
if self._BGR_color is None:
try:
# inherit color from parent
self._BGR_color = norm_range(self._space_parent.get_BGR_color())
except AttributeError:
self._BGR_color = norm_range(np.random.rand(3)*255)
return self._BGR_color
[docs] def add_to_tail(self, *args, **kwargs):
"""
add a tail_item itself or from a contour (cnt), bounding box (bbox)
or rotated box (rbox) to the tail. The point (pt) can be specified
on creation but not if tail_item is given
:param args: {0}
:param kwargs: {0}
:return: tail_item
"""
frame = kwargs.pop("frame", None)
mask = kwargs.pop("mask", None)
try:
tf = kwargs["tail_item"]
except KeyError:
tf = TailItem(*args, **kwargs)
self.tail.appendleft(tf)
self._to_recompute = True
# update key_points and descriptors
if frame is not None:
self._update_description(frame, tf)
if mask is not None:
self._fill_mask(mask, tf)
return tf
add_to_tail.__doc__ = add_to_tail.__doc__.format(TailItem._fields)
def _update_description(self, frame, tail_item):
return
def _fill_mask(self, mask, tail_item):
cv2.drawContours(mask, [tail_item.cnt], -1, 1, -1)
[docs] def point_inside(self, point):
"""
test whether point is inside object in last position
:param point: point or x-coordinate, y-coordinate
:return: True if inside or in contour, else False
"""
return self.tail[0].point_inside(point)
[docs] def point_near(self, point):
"""
test whether point is near object in last position
:param point: point or x-coordinate, y-coordinate
:return: True if inside or in contour, else False
"""
return self.tail[0].point_near(point)
[docs] def cnt_intersect(self, cnt):
"""
test whether last internal cnt from tail is intersected
with external cnt
:param cnt: external contour
:return: True if contours intersect, else False
"""
return self.tail[0].cnt_intersect(cnt)
[docs] def cnt_near(self, cnt):
"""
test whether last internal cnt from tail is near
with external cnt
:param cnt: external contour
:return: True if contours intersect, else False
"""
return self.tail[0].cnt_near(cnt)
[docs] def direction(self, x_axis=("left", "right"), y_axis=("up", "down"),
z_axis=("far", "near")):
"""
get tracked object direction in a readable form
:param x_axis: names of the extremes in the x axis. ("left", "right")
:param y_axis: names of the extremes in the y axis. ("up", "down")
:param z_axis: names of the extremes in the z axis. ("far", "near")
:return: x_axis, y_axis, z_axis directions
"""
(dirX, dirY, dirZ) = ("", "", "")
if not self.compute(): # if necessary update before giving answer
# if there was no update but it needed to update
return "-".join((dirX, dirY, dirZ))
dX, dY, dZ = self._dX, self._dY, self._dZ
# ensure there is significant movement in the
# x-direction,
if np.abs(dX) > 0:
dirX = x_axis[1] if np.sign(dX) == 1 else x_axis[0]
# y-direction,
if np.abs(dY) > 0:
dirY = y_axis[1] if np.sign(dY) == 1 else y_axis[0]
# and z-direction
if np.abs(dZ) > 0:
dirZ = z_axis[1] if np.sign(dY) == 1 else z_axis[0]
return "-".join((dirX, dirY, dirZ))
def _compute(self):
# check there are enough points
tail = self.tail
if not self._enough_items(len(tail)):
return # still to compute, nothing done
cover = self._direction_cover
# how many points to cover
#average = np.average([np.array(tail[i]) for i in np.arange(cover)], 0)
#rolled = np.average([np.array(tail[-i-1]) for i in np.arange(cover)], 0)
average = (np.sum(np.array([np.array(tail[i].pt)*(cover-i)
for i in xrange(cover)]), 0)
/ np.sum(np.arange(cover) + 1))
rolled = (np.sum(np.array([np.array(tail[-i-1].pt)*(cover-i)
for i in xrange(cover)]), 0)
/ np.sum(np.arange(cover) + 1))
# compute the difference between the x and y coordinates
self._dX, self._dY, self._dZ = [int(i) for i in (average - rolled)]
self._to_recompute = False # computed
[docs] def update_tracker(self, frame, mask=None, tracker_type=None, **kwargs):
if tracker_type is None:
tracker_type = self.tracker_type
tk_name = tracker_type.upper()
if int(cv_major_ver) < 3:
tracker = cv2.Tracker_create(tk_name)
elif tk_name == 'BOOSTING':
tracker = cv2.TrackerBoosting_create()
elif tk_name == 'MIL':
tracker = cv2.TrackerMIL_create()
elif tk_name == 'KCF':
tracker = cv2.TrackerKCF_create()
elif tk_name == 'TLD':
tracker = cv2.TrackerTLD_create()
elif tk_name == 'MEDIANFLOW':
tracker = cv2.TrackerMedianFlow_create()
elif tk_name == 'GOTURN':
tracker = cv2.TrackerGOTURN_create()
else:
raise ValueError("tracker_type not supported")
# add tail if tracker was created
if kwargs:
# renewing tracker and correct it
tf = self.add_to_tail(mask=mask, frame=frame, **kwargs)
else:
# renewing tracker from last item in tail
tf = self.tail[0]
# register new tracker if tail was added successfully
self.tracker_type = tracker_type
self.tracker = tracker
self.is_tracking = tracker.init(frame, tf.bbox)
[docs] def update(self, frame, mask=None):
# Update tracker
self.is_tracking, bbox = self.tracker.update(frame)
if self.is_tracking:
self.add_to_tail(mask=mask, frame=frame, bbox=bbox)
[docs] def draw_circle(self, frame, color=None):
# draw the circle and centroid on the frame,
# then update the list of tracked points
if color is None:
color = self.get_BGR_color()
else:
color = norm_range(color)
try:
x, y, _ = self.position
(_, radius) = cv2.minEnclosingCircle(self.tail[0].cnt)
center = (int(x), int(y))
cv2.circle(frame, center, int(radius), color, 2)
cv2.circle(frame, center, 5, norm_range([255-i for i in color]), -1)
except IndexError:
pass
[docs] def draw_tail(self, frame, color=None, iterate=None):
"""
Draw object tail on frame
:param frame: frame to draw on
:param color: color of tail (1x3 array)
:param iterate: iterate over positions
:return:
"""
if color is None:
color = self.get_BGR_color()
else:
color = norm_range(color)
tail = self.tail
if iterate is None:
iterate = xrange(1, len(tail))
# loop over the set of tracked points
for i in iterate:
# if either of the tracked data are None, ignore them
if tail[i - 1] is None or tail[i] is None:
continue
# otherwise, compute the thickness of the line and
# draw the connecting lines
thickness = int(np.sqrt(old_div(self.tail_len, float(i + 1))) * 2.5)
cv2.line(frame, tail[i - 1].pt[:2], tail[i].pt[:2], color, thickness)
[docs] def draw_stats(self, frame, position=None, fontFace=None, fontScale=None,
color=None, thickness=None, tag=None):
"""
:param frame:
:param position:
:param fontFace:
:param fontScale:
:param color:
:param thickness:
:param tag:
:return:
"""
if position is None:
position = (10, frame.shape[0] - 10)
if fontFace is None:
fontFace = cv2.FONT_HERSHEY_SIMPLEX
if fontScale is None:
fontScale = 0.35
if color is None:
color = self.get_BGR_color()
else:
color = norm_range(color)
if thickness is None:
thickness = 1
if tag is None:
tag = "dx: {dx}, dy: {dy}, dz: {dz} \n{direction}"
# show the movement deltas and the direction of movement on
# the frame
text = tag.format(dx=self.dX, dy=self.dY, dz=self.dZ,
direction=self.direction())
cv2.putText(frame, text, position, fontFace=fontFace,
fontScale=fontScale, color=color, thickness=thickness)
def __json_enco__(self):
return self.max_tail_len
def _parent_changed_event(self, old_parent):
return