From 74700c046b1fcc311baf5ea57ec442c5f58c0250 Mon Sep 17 00:00:00 2001 From: Facundo Lezama Date: Thu, 10 Nov 2022 19:44:15 +0000 Subject: [PATCH] Add requested changes for vectorized distances. --- README.md | 7 ----- demos/yolov4/Dockerfile | 2 +- demos/yolov4/requirements.txt | 1 + norfair/distances.py | 55 +++++++++++++++++++++-------------- norfair/tracker.py | 6 ++-- pyproject.toml | 1 + 6 files changed, 38 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index a2b6ef6b..4beb0b2e 100644 --- a/README.md +++ b/README.md @@ -100,13 +100,6 @@ Most tracking demos are showcased with vehicles and pedestrians, but the detecto Norfair works by estimating the future position of each point based on its past positions. It then tries to match these estimated positions with newly detected points provided by the detector. For this matching to occur, Norfair can rely on any distance function. There are some predefined distances already integrated in Norfair, and the users can also define their own custom distances. Therefore, each object tracker can be made as simple or as complex as needed. -The following is an example of a particularly simple distance function calculating the Euclidean distance between tracked objects and detections. This distance is already integrated in Norfair and can be used simply by setting the string `"euclidean"` when building the tracker. This is possibly the simplest distance function you could use in Norfair, as it uses just one single point per detection/object. - -```python - def euclidean_distance(detection, tracked_object): - return np.linalg.norm(detection.points - tracked_object.estimate) -``` - As an example we use [Detectron2](https://github.com/facebookresearch/detectron2) to get the single point detections to use with this distance function. We just use the centroids of the bounding boxes it produces around cars as our detections, and get the following results. ![Tracking cars with Norfair](https://raw.githubusercontent.com/tryolabs/norfair/master/docs/videos/traffic.gif) diff --git a/demos/yolov4/Dockerfile b/demos/yolov4/Dockerfile index 8e8c98d9..efa53ec9 100644 --- a/demos/yolov4/Dockerfile +++ b/demos/yolov4/Dockerfile @@ -19,6 +19,6 @@ WORKDIR /demo COPY requirements.txt requirements.txt RUN pip install -r requirements.txt -RUN pip install git+https://github.com/tryolabs/norfair.git@master#egg=norfair[metrics] +RUN pip install git+https://github.com/tryolabs/norfair.git@master WORKDIR /demo/src diff --git a/demos/yolov4/requirements.txt b/demos/yolov4/requirements.txt index 47ad69ae..acd1fecd 100644 --- a/demos/yolov4/requirements.txt +++ b/demos/yolov4/requirements.txt @@ -1 +1,2 @@ opencv-python==4.6.0.66 +importlib-metadata==4.8.3 \ No newline at end of file diff --git a/norfair/distances.py b/norfair/distances.py index 732b2389..da5bc92d 100644 --- a/norfair/distances.py +++ b/norfair/distances.py @@ -28,10 +28,10 @@ def get_distances( Parameters ---------- - objects: Sequence[TrackedObject] + objects : Sequence[TrackedObject] Sequence of [TrackedObject][norfair.tracker.TrackedObject] to be compared with potential [Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject] candidates. - candidates: Union[List[Detection], List[TrackedObject]], optional + candidates : Union[List[Detection], List[TrackedObject]], optional List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns @@ -48,10 +48,7 @@ class ScalarDistance(Distance): Parameters ---------- - distance_function : Union[ - Callable[["Detection", "TrackedObject"], float], - Callable[["TrackedObject", "TrackedObject"], float], - ], + distance_function : Union[Callable[["Detection", "TrackedObject"], float], Callable[["TrackedObject", "TrackedObject"], float]] Distance function used to determine the pointwise distance between new candidates and objects. This function should take 2 input arguments, the first being a `Union[Detection, TrackedObject]`, and the second [TrackedObject][norfair.tracker.TrackedObject]. It has to return a `float` with the distance it calculates. @@ -76,10 +73,10 @@ def get_distances( Parameters ---------- - objects: Sequence[TrackedObject] + objects : Sequence[TrackedObject] Sequence of [TrackedObject][norfair.tracker.TrackedObject] to be compared with potential [Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject] candidates. - candidates: Union[List[Detection], List[TrackedObject]], optional + candidates : Union[List[Detection], List[TrackedObject]], optional List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns @@ -107,12 +104,14 @@ def get_distances( class VectorizedDistance(Distance): """ - VectorizedDistance class represents a distance that is calculated pairwise. + VectorizedDistance class represents a distance that is calculated in a vectorized way. This means + that instead of going through every pair and explicitly calculating its distance, VectorizedDistance + uses the entire vectors to compare to each other in a single operation. Parameters ---------- distance_function : Callable[[np.ndarray, np.ndarray], np.ndarray] - Distance function used to determine the pairwise distances between new candidates and objects. + Distance function used to determine the distances between new candidates and objects. This function should take 2 input arguments, the first being a `np.ndarray` and the second `np.ndarray`. It has to return a `np.ndarray` with the distance matrix it calculates. """ @@ -133,10 +132,10 @@ def get_distances( Parameters ---------- - objects: Sequence[TrackedObject] + objects : Sequence[TrackedObject] Sequence of [TrackedObject][norfair.tracker.TrackedObject] to be compared with potential [Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject] candidates. - candidates: Union[List[Detection], List[TrackedObject]], optional + candidates : Union[List[Detection], List[TrackedObject]], optional List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns @@ -191,12 +190,13 @@ def _compute_distance( ) -> np.ndarray: """ Method that computes the pairwise distances between new candidates and objects. + It is intended to use the entire vectors to compare to each other in a single operation. Parameters ---------- - stacked_candidates: np.ndarray + stacked_candidates : np.ndarray np.ndarray containing a stack of candidates to be compared with the stacked_objects. - stacked_objects: np.ndarray + stacked_objects : np.ndarray np.ndarray containing a stack of objects to be compared with the stacked_objects. Returns @@ -209,7 +209,7 @@ def _compute_distance( class ScipyDistance(VectorizedDistance): """ - ScipyDistance class extends VectorizedDistance for the use of Scipy's pairwise distances. + ScipyDistance class extends VectorizedDistance for the use of Scipy's vectorized distances. This class uses `scipy.spatial.distance.cdist` to calculate distances between two `np.ndarray`. @@ -235,12 +235,13 @@ def _compute_distance( ) -> np.ndarray: """ Method that computes the pairwise distances between new candidates and objects. + It is intended to use the entire vectors to compare to each other in a single operation. Parameters ---------- - stacked_candidates: np.ndarray + stacked_candidates : np.ndarray np.ndarray containing a stack of candidates to be compared with the stacked_objects. - stacked_objects: np.ndarray + stacked_objects : np.ndarray np.ndarray containing a stack of objects to be compared with the stacked_objects. Returns @@ -472,21 +473,31 @@ def iou_opt(detection: "Detection", tracked_object: "TrackedObject") -> float: "sqeuclidean", "yule", ] +AVAILABLE_VECTORIZED_DISTANCES = ( + list(_VECTORIZED_DISTANCE_FUNCTIONS.keys()) + _SCIPY_DISTANCE_FUNCTIONS +) def get_distance_by_name(name: str) -> Distance: - f""" + """ Select a distance by name. - Valid names are: `{list(_SCALAR_DISTANCE_FUNCTIONS.keys()) + list(_VECTORIZED_DISTANCE_FUNCTIONS.keys()) + _SCIPY_DISTANCE_FUNCTIONS}`. + Parameters + ---------- + name : str + A string defining the metric to get. + + Returns + ------- + Distance + The distance object. """ if name in _SCALAR_DISTANCE_FUNCTIONS: warning( "You are using a scalar distance function. If you want to speed up the" " tracking process please consider using a vectorized distance function" - " such as" - f" {list(_VECTORIZED_DISTANCE_FUNCTIONS.keys()) + _SCIPY_DISTANCE_FUNCTIONS}." + f" such as {AVAILABLE_VECTORIZED_DISTANCES}." ) distance = _SCALAR_DISTANCE_FUNCTIONS[name] distance_function = ScalarDistance(distance) @@ -498,7 +509,7 @@ def get_distance_by_name(name: str) -> Distance: else: raise ValueError( f"Invalid distance '{name}', expecting one of" - f" {list(_SCALAR_DISTANCE_FUNCTIONS.keys()) + list(_VECTORIZED_DISTANCE_FUNCTIONS.keys()) + _SCIPY_DISTANCE_FUNCTIONS}" + f" {list(_SCALAR_DISTANCE_FUNCTIONS.keys()) + AVAILABLE_VECTORIZED_DISTANCES}" ) return distance_function diff --git a/norfair/tracker.py b/norfair/tracker.py index 5c8c400a..62420a44 100644 --- a/norfair/tracker.py +++ b/norfair/tracker.py @@ -7,8 +7,7 @@ from norfair.camera_motion import CoordinatesTransformation from .distances import ( - _SCIPY_DISTANCE_FUNCTIONS, - _VECTORIZED_DISTANCE_FUNCTIONS, + AVAILABLE_VECTORIZED_DISTANCES, ScalarDistance, get_distance_by_name, ) @@ -103,8 +102,7 @@ def __init__( warning( "You are using a scalar distance function. If you want to speed up the" " tracking process please consider using a vectorized distance" - " function such as" - f" {list(_VECTORIZED_DISTANCE_FUNCTIONS.keys()) + _SCIPY_DISTANCE_FUNCTIONS}." + f" function such as {AVAILABLE_VECTORIZED_DISTANCES}." ) distance_function = ScalarDistance(distance_function) else: diff --git a/pyproject.toml b/pyproject.toml index bee51a5a..2753b400 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ classifiers = [ python = "^3.6.1" filterpy = "^1.4.5" rich = ">=9.10.0, <13.0.0" +scipy = ">=1.5.4" opencv-python = { version = ">= 3.2.0, < 5.0.0", optional = true } motmetrics = { version = "1.2.5", optional = true } importlib-metadata = { version = "4.8.3", python = "<3.8" }