Skip to content

Commit

Permalink
Remove clock custom object, pass datetime object instead; Black refor…
Browse files Browse the repository at this point in the history
…matting
  • Loading branch information
levan92 committed Sep 23, 2021
1 parent b080e78 commit 2852fe8
Show file tree
Hide file tree
Showing 18 changed files with 523 additions and 375 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ When instantiating a `DeepSort` object (as in `deepsort_tracker.py`), `polygon`
- Due to special request, tensorflow embedder is available now too (very unwillingly included).
- Skip nms completely in preprocessing detections if `nms_max_overlap == 1.0` (which is the default), in the original repo, nms will still be done even if threshold is set to 1.0 (probably because it was not optimised for speed).
- Now able to override the `Track` class with a custom Track class (that inherits from `Track` class) for custom track logic
- Now takes in a "clock" object (see `utils/clock.py` for example), which provides date for track naming and facilities track id reset every day, preventing overflow and overly large track ids when system runs for a long time.
- Takes in today's date now, which provides date for track naming and facilities track id reset every day, preventing overflow and overly large track ids when system runs for a long time.

```python3
from datetime import datetime
today = datetime.now().date()
```

- Now supports polygon detections. We do not track polygon points per se, but merely convert the polygon to its bounding rectangle for tracking. That said, if embedding is enabled, the embedder works on the crop around the bounding rectangle, with area not covered by the polygon masked away. [Read more here](#polygon-support).
- The original `Track.to_*` methods for retrieving bounding box values returns only the Kalman predicted values. In some applications, it is better to return the bb values of the original detections the track was associated to at the current round. Added a `orig` argument which can be flagged `True` to get that. [Read more here](#getting-bounding-box-of-original-detection).
- Added `get_det_supplementary` method to `Track` class, in order to pass detection related info through the track. [Read more here](#storing-supplementary-info-of-original-detection).
Expand Down
2 changes: 1 addition & 1 deletion deep_sort_realtime/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.0"
__version__ = "1.1"
4 changes: 2 additions & 2 deletions deep_sort_realtime/deep_sort/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Detection(object):
feature : array_like
A feature vector that describes the object contained in this image.
class_name : Optional str
Detector predicted class name.
Detector predicted class name.
others : Optional any
Other supplementary fields associated with detection that wants to be stored as a "memory" to be retrieve through the track downstream.
Expand All @@ -31,7 +31,7 @@ class Detection(object):
"""

def __init__(self, ltwh, confidence, feature, class_name=None, others=None):
# def __init__(self, ltwh, feature):
# def __init__(self, ltwh, feature):
self.ltwh = np.asarray(ltwh, dtype=np.float)
self.confidence = float(confidence)
self.feature = np.asarray(feature, dtype=np.float32)
Expand Down
19 changes: 11 additions & 8 deletions deep_sort_realtime/deep_sort/iou_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,23 @@ def iou(bbox, candidates):
candidates_tl = candidates[:, :2]
candidates_br = candidates[:, :2] + candidates[:, 2:]

tl = np.c_[np.maximum(bbox_tl[0], candidates_tl[:, 0])[:, np.newaxis],
np.maximum(bbox_tl[1], candidates_tl[:, 1])[:, np.newaxis]]
br = np.c_[np.minimum(bbox_br[0], candidates_br[:, 0])[:, np.newaxis],
np.minimum(bbox_br[1], candidates_br[:, 1])[:, np.newaxis]]
wh = np.maximum(0., br - tl)
tl = np.c_[
np.maximum(bbox_tl[0], candidates_tl[:, 0])[:, np.newaxis],
np.maximum(bbox_tl[1], candidates_tl[:, 1])[:, np.newaxis],
]
br = np.c_[
np.minimum(bbox_br[0], candidates_br[:, 0])[:, np.newaxis],
np.minimum(bbox_br[1], candidates_br[:, 1])[:, np.newaxis],
]
wh = np.maximum(0.0, br - tl)

area_intersection = wh.prod(axis=1)
area_bbox = bbox[2:].prod()
area_candidates = candidates[:, 2:].prod(axis=1)
return area_intersection / (area_bbox + area_candidates - area_intersection)


def iou_cost(tracks, detections, track_indices=None,
detection_indices=None):
def iou_cost(tracks, detections, track_indices=None, detection_indices=None):
"""An intersection over union distance metric.
Parameters
Expand Down Expand Up @@ -77,5 +80,5 @@ def iou_cost(tracks, detections, track_indices=None,

bbox = tracks[track_idx].to_ltwh()
candidates = np.asarray([detections[i].ltwh for i in detection_indices])
cost_matrix[row, :] = 1. - iou(bbox, candidates)
cost_matrix[row, :] = 1.0 - iou(bbox, candidates)
return cost_matrix
53 changes: 32 additions & 21 deletions deep_sort_realtime/deep_sort/kalman_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
6: 12.592,
7: 14.067,
8: 15.507,
9: 16.919}
9: 16.919,
}


class KalmanFilter(object):
Expand All @@ -38,7 +39,7 @@ class KalmanFilter(object):
"""

def __init__(self):
ndim, dt = 4, 1.
ndim, dt = 4, 1.0

# Create Kalman filter model matrices.
self._motion_mat = np.eye(2 * ndim, 2 * ndim)
Expand All @@ -49,8 +50,8 @@ def __init__(self):
# Motion and observation uncertainty are chosen relative to the current
# state estimate. These weights control the amount of uncertainty in
# the model. This is a bit hacky.
self._std_weight_position = 1. / 20
self._std_weight_velocity = 1. / 160
self._std_weight_position = 1.0 / 20
self._std_weight_velocity = 1.0 / 160

def initiate(self, measurement):
"""Create track from unassociated measurement.
Expand Down Expand Up @@ -81,7 +82,8 @@ def initiate(self, measurement):
10 * self._std_weight_velocity * measurement[3],
10 * self._std_weight_velocity * measurement[3],
1e-5,
10 * self._std_weight_velocity * measurement[3]]
10 * self._std_weight_velocity * measurement[3],
]
covariance = np.diag(np.square(std))
return mean, covariance

Expand All @@ -108,17 +110,21 @@ def predict(self, mean, covariance):
self._std_weight_position * mean[3],
self._std_weight_position * mean[3],
1e-2,
self._std_weight_position * mean[3]]
self._std_weight_position * mean[3],
]
std_vel = [
self._std_weight_velocity * mean[3],
self._std_weight_velocity * mean[3],
1e-5,
self._std_weight_velocity * mean[3]]
self._std_weight_velocity * mean[3],
]
motion_cov = np.diag(np.square(np.r_[std_pos, std_vel]))

mean = np.dot(self._motion_mat, mean)
covariance = np.linalg.multi_dot((
self._motion_mat, covariance, self._motion_mat.T)) + motion_cov
covariance = (
np.linalg.multi_dot((self._motion_mat, covariance, self._motion_mat.T))
+ motion_cov
)

return mean, covariance

Expand All @@ -143,12 +149,14 @@ def project(self, mean, covariance):
self._std_weight_position * mean[3],
self._std_weight_position * mean[3],
1e-1,
self._std_weight_position * mean[3]]
self._std_weight_position * mean[3],
]
innovation_cov = np.diag(np.square(std))

mean = np.dot(self._update_mat, mean)
covariance = np.linalg.multi_dot((
self._update_mat, covariance, self._update_mat.T))
covariance = np.linalg.multi_dot(
(self._update_mat, covariance, self._update_mat.T)
)
return mean, covariance + innovation_cov

def update(self, mean, covariance, measurement):
Expand All @@ -174,19 +182,22 @@ def update(self, mean, covariance, measurement):
projected_mean, projected_cov = self.project(mean, covariance)

chol_factor, lower = scipy.linalg.cho_factor(
projected_cov, lower=True, check_finite=False)
projected_cov, lower=True, check_finite=False
)
kalman_gain = scipy.linalg.cho_solve(
(chol_factor, lower), np.dot(covariance, self._update_mat.T).T,
check_finite=False).T
(chol_factor, lower),
np.dot(covariance, self._update_mat.T).T,
check_finite=False,
).T
innovation = measurement - projected_mean

new_mean = mean + np.dot(innovation, kalman_gain.T)
new_covariance = covariance - np.linalg.multi_dot((
kalman_gain, projected_cov, kalman_gain.T))
new_covariance = covariance - np.linalg.multi_dot(
(kalman_gain, projected_cov, kalman_gain.T)
)
return new_mean, new_covariance

def gating_distance(self, mean, covariance, measurements,
only_position=False):
def gating_distance(self, mean, covariance, measurements, only_position=False):
"""Compute gating distance between state distribution and measurements.
A suitable distance threshold can be obtained from `chi2inv95`. If
Expand Down Expand Up @@ -223,7 +234,7 @@ def gating_distance(self, mean, covariance, measurements,
cholesky_factor = np.linalg.cholesky(covariance)
d = measurements - mean
z = scipy.linalg.solve_triangular(
cholesky_factor, d.T, lower=True, check_finite=False,
overwrite_b=True)
cholesky_factor, d.T, lower=True, check_finite=False, overwrite_b=True
)
squared_maha = np.sum(z * z, axis=0)
return squared_maha
57 changes: 39 additions & 18 deletions deep_sort_realtime/deep_sort/linear_assignment.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
# vim: expandtab:ts=4:sw=4
from __future__ import absolute_import
import numpy as np

# from sklearn.utils.linear_assignment_ import linear_assignment
from scipy.optimize import linear_sum_assignment
from . import kalman_filter


INFTY_COST = 1e+5
INFTY_COST = 1e5


def min_cost_matching(
distance_metric, max_distance, tracks, detections, track_indices=None,
detection_indices=None):
distance_metric,
max_distance,
tracks,
detections,
track_indices=None,
detection_indices=None,
):
"""Solve linear assignment problem.
Parameters
Expand Down Expand Up @@ -53,8 +59,7 @@ def min_cost_matching(
if len(detection_indices) == 0 or len(track_indices) == 0:
return [], track_indices, detection_indices # Nothing to match.

cost_matrix = distance_metric(
tracks, detections, track_indices, detection_indices)
cost_matrix = distance_metric(tracks, detections, track_indices, detection_indices)
cost_matrix[cost_matrix > max_distance] = max_distance + 1e-5
# indices = linear_assignment(cost_matrix)
indices = np.vstack(linear_sum_assignment(cost_matrix)).T
Expand All @@ -78,8 +83,14 @@ def min_cost_matching(


def matching_cascade(
distance_metric, max_distance, cascade_depth, tracks, detections,
track_indices=None, detection_indices=None):
distance_metric,
max_distance,
cascade_depth,
tracks,
detections,
track_indices=None,
detection_indices=None,
):
"""Run matching cascade.
Parameters
Expand Down Expand Up @@ -128,24 +139,34 @@ def matching_cascade(
break

track_indices_l = [
k for k in track_indices
if tracks[k].time_since_update == 1 + level
k for k in track_indices if tracks[k].time_since_update == 1 + level
]
if len(track_indices_l) == 0: # Nothing to match at this level
continue

matches_l, _, unmatched_detections = \
min_cost_matching(
distance_metric, max_distance, tracks, detections,
track_indices_l, unmatched_detections)
matches_l, _, unmatched_detections = min_cost_matching(
distance_metric,
max_distance,
tracks,
detections,
track_indices_l,
unmatched_detections,
)
matches += matches_l
unmatched_tracks = list(set(track_indices) - set(k for k, _ in matches))
return matches, unmatched_tracks, unmatched_detections


def gate_cost_matrix(
kf, cost_matrix, tracks, detections, track_indices, detection_indices,
gated_cost=INFTY_COST, only_position=False):
kf,
cost_matrix,
tracks,
detections,
track_indices,
detection_indices,
gated_cost=INFTY_COST,
only_position=False,
):
"""Invalidate infeasible entries in cost matrix based on the state
distributions obtained by Kalman filtering.
Expand Down Expand Up @@ -182,11 +203,11 @@ def gate_cost_matrix(
"""
gating_dim = 2 if only_position else 4
gating_threshold = kalman_filter.chi2inv95[gating_dim]
measurements = np.asarray(
[detections[i].to_xyah() for i in detection_indices])
measurements = np.asarray([detections[i].to_xyah() for i in detection_indices])
for row, track_idx in enumerate(track_indices):
track = tracks[track_idx]
gating_distance = kf.gating_distance(
track.mean, track.covariance, measurements, only_position)
track.mean, track.covariance, measurements, only_position
)
cost_matrix[row, gating_distance > gating_threshold] = gated_cost
return cost_matrix
16 changes: 7 additions & 9 deletions deep_sort_realtime/deep_sort/nn_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def _pdist(a, b):
if len(a) == 0 or len(b) == 0:
return np.zeros((len(a), len(b)))
a2, b2 = np.square(a).sum(axis=1), np.square(b).sum(axis=1)
r2 = -2. * np.dot(a, b.T) + a2[:, None] + b2[None, :]
r2 = np.clip(r2, 0., float(np.inf))
r2 = -2.0 * np.dot(a, b.T) + a2[:, None] + b2[None, :]
r2 = np.clip(r2, 0.0, float(np.inf))
return r2


Expand All @@ -51,11 +51,11 @@ def _cosine_distance(a, b, data_is_normalized=False):
if not data_is_normalized:
a = np.asarray(a) / np.linalg.norm(a, axis=1, keepdims=True)
b = np.asarray(b) / np.linalg.norm(b, axis=1, keepdims=True)
return 1. - np.dot(a, b.T)
return 1.0 - np.dot(a, b.T)


def _nn_euclidean_distance(x, y):
""" Helper function for nearest neighbor distance metric (Euclidean).
"""Helper function for nearest neighbor distance metric (Euclidean).
Parameters
----------
Expand All @@ -76,7 +76,7 @@ def _nn_euclidean_distance(x, y):


def _nn_cosine_distance(x, y):
""" Helper function for nearest neighbor distance metric (cosine).
"""Helper function for nearest neighbor distance metric (cosine).
Parameters
----------
Expand Down Expand Up @@ -122,14 +122,12 @@ class NearestNeighborDistanceMetric(object):

def __init__(self, metric, matching_threshold, budget=None):


if metric == "euclidean":
self._metric = _nn_euclidean_distance
elif metric == "cosine":
self._metric = _nn_cosine_distance
else:
raise ValueError(
"Invalid metric; must be either 'euclidean' or 'cosine'")
raise ValueError("Invalid metric; must be either 'euclidean' or 'cosine'")
self.matching_threshold = matching_threshold
self.budget = budget
self.samples = {}
Expand All @@ -150,7 +148,7 @@ def partial_fit(self, features, targets, active_targets):
for feature, target in zip(features, targets):
self.samples.setdefault(target, []).append(feature)
if self.budget is not None:
self.samples[target] = self.samples[target][-self.budget:]
self.samples[target] = self.samples[target][-self.budget :]
self.samples = {k: self.samples[k] for k in active_targets}

def distance(self, features, targets):
Expand Down
Loading

0 comments on commit 2852fe8

Please sign in to comment.