Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
28 changes: 27 additions & 1 deletion deep_sort/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
# vim: expandtab:ts=4:sw=4
from .detection import Detection
from .kalman_filter import KalmanFilter
from .linear_assignment import min_cost_matching, matching_cascade, gate_cost_matrix
from .iou_matching import iou, iou_cost
from .nn_matching import NearestNeighborDistanceMetric
from .track import Track, TrackState
from .tracker import Tracker
from .yolo_detector import YOLOv8Detector, ReIDExtractor, YOLOv8DeepSORT
from .track_interpolation import TrackInterpolator

__all__ = [
'Detection',
'KalmanFilter',
'min_cost_matching',
'matching_cascade',
'gate_cost_matrix',
'iou',
'iou_cost',
'NearestNeighborDistanceMetric',
'Track',
'TrackState',
'Tracker',
'YOLOv8Detector',
'ReIDExtractor',
'YOLOv8DeepSORT',
'TrackInterpolator'
]
42 changes: 9 additions & 33 deletions deep_sort/detection.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,25 @@
# vim: expandtab:ts=4:sw=4
import numpy as np


class Detection(object):
"""
This class represents a bounding box detection in a single image.

Parameters
----------
tlwh : array_like
Bounding box in format `(x, y, w, h)`.
confidence : float
Detector confidence score.
feature : array_like
A feature vector that describes the object contained in this image.

Attributes
----------
tlwh : ndarray
Bounding box in format `(top left x, top left y, width, height)`.
confidence : ndarray
Detector confidence score.
feature : ndarray | NoneType
A feature vector that describes the object contained in this image.

"""

def __init__(self, tlwh, confidence, feature):
class Detection:
def __init__(self, tlwh, confidence, feature, class_id=0, class_name=None):
self.tlwh = np.asarray(tlwh, dtype=np.float64)
self.confidence = float(confidence)
self.feature = np.asarray(feature, dtype=np.float32)
self.feature = np.asarray(feature, dtype=np.float32) if feature is not None else None
self.class_id = int(class_id)
self.class_name = class_name

def to_tlbr(self):
"""Convert bounding box to format `(min x, min y, max x, max y)`, i.e.,
`(top left, bottom right)`.
"""
ret = self.tlwh.copy()
ret[2:] += ret[:2]
return ret

def to_xyah(self):
"""Convert bounding box to format `(center x, center y, aspect ratio,
height)`, where the aspect ratio is `width / height`.
"""
ret = self.tlwh.copy()
ret[:2] += ret[2:] / 2
ret[2] /= ret[3]
return ret

def to_xyxy(self):
ret = self.to_tlbr()
return ret
137 changes: 11 additions & 126 deletions deep_sort/nn_matching.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,7 @@
# vim: expandtab:ts=4:sw=4
import numpy as np


def _pdist(a, b):
"""Compute pair-wise squared distance between points in `a` and `b`.

Parameters
----------
a : array_like
An NxM matrix of N samples of dimensionality M.
b : array_like
An LxM matrix of L samples of dimensionality M.

Returns
-------
ndarray
Returns a matrix of size len(a), len(b) such that eleement (i, j)
contains the squared distance between `a[i]` and `b[j]`.

"""
a, b = np.asarray(a), np.asarray(b)
if len(a) == 0 or len(b) == 0:
return np.zeros((len(a), len(b)))
Expand All @@ -29,100 +12,28 @@ def _pdist(a, b):


def _cosine_distance(a, b, data_is_normalized=False):
"""Compute pair-wise cosine distance between points in `a` and `b`.

Parameters
----------
a : array_like
An NxM matrix of N samples of dimensionality M.
b : array_like
An LxM matrix of L samples of dimensionality M.
data_is_normalized : Optional[bool]
If True, assumes rows in a and b are unit length vectors.
Otherwise, a and b are explicitly normalized to lenght 1.

Returns
-------
ndarray
Returns a matrix of size len(a), len(b) such that eleement (i, j)
contains the squared distance between `a[i]` and `b[j]`.

"""
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)
a = np.asarray(a)
b = np.asarray(b)
if len(a) > 0:
a = a / (np.linalg.norm(a, axis=1, keepdims=True) + 1e-8)
if len(b) > 0:
b = b / (np.linalg.norm(b, axis=1, keepdims=True) + 1e-8)
return 1. - np.dot(a, b.T)


def _nn_euclidean_distance(x, y):
""" Helper function for nearest neighbor distance metric (Euclidean).

Parameters
----------
x : ndarray
A matrix of N row-vectors (sample points).
y : ndarray
A matrix of M row-vectors (query points).

Returns
-------
ndarray
A vector of length M that contains for each entry in `y` the
smallest Euclidean distance to a sample in `x`.

"""
distances = _pdist(x, y)
return np.maximum(0.0, distances.min(axis=0))


def _nn_cosine_distance(x, y):
""" Helper function for nearest neighbor distance metric (cosine).

Parameters
----------
x : ndarray
A matrix of N row-vectors (sample points).
y : ndarray
A matrix of M row-vectors (query points).

Returns
-------
ndarray
A vector of length M that contains for each entry in `y` the
smallest cosine distance to a sample in `x`.

"""
distances = _cosine_distance(x, y)
return distances.min(axis=0)


class NearestNeighborDistanceMetric(object):
"""
A nearest neighbor distance metric that, for each target, returns
the closest distance to any sample that has been observed so far.

Parameters
----------
metric : str
Either "euclidean" or "cosine".
matching_threshold: float
The matching threshold. Samples with larger distance are considered an
invalid match.
budget : Optional[int]
If not None, fix samples per class to at most this number. Removes
the oldest samples when the budget is reached.

Attributes
----------
samples : Dict[int -> List[ndarray]]
A dictionary that maps from target identities to the list of samples
that have been observed so far.

"""

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


if metric == "euclidean":
self._metric = _nn_euclidean_distance
elif metric == "cosine":
Expand All @@ -135,43 +46,17 @@ def __init__(self, metric, matching_threshold, budget=None):
self.samples = {}

def partial_fit(self, features, targets, active_targets):
"""Update the distance metric with new data.

Parameters
----------
features : ndarray
An NxM matrix of N features of dimensionality M.
targets : ndarray
An integer array of associated target identities.
active_targets : List[int]
A list of targets that are currently present in the scene.

"""
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 = {k: self.samples[k] for k in active_targets}

def distance(self, features, targets):
"""Compute distance between features and targets.

Parameters
----------
features : ndarray
An NxM matrix of N features of dimensionality M.
targets : List[int]
A list of targets to match the given `features` against.

Returns
-------
ndarray
Returns a cost matrix of shape len(targets), len(features), where
element (i, j) contains the closest squared distance between
`targets[i]` and `features[j]`.

"""
cost_matrix = np.zeros((len(targets), len(features)))
for i, target in enumerate(targets):
cost_matrix[i, :] = self._metric(self.samples[target], features)
if target in self.samples and len(self.samples[target]) > 0:
cost_matrix[i, :] = self._metric(self.samples[target], features)
else:
cost_matrix[i, :] = self.matching_threshold + 1
return cost_matrix
Loading