If set to True, the id is added to a title that is drawn on top of the box.
If an object doesn't have an id this parameter is ignored.
- False
+ True
@@ -2320,7 +2320,7 @@
color_by_label:bool=None,# Deprecateddraw_labels:bool=False,text_size:Optional[float]=None,
- draw_ids:bool=False,
+ draw_ids:bool=True,text_color:Optional[ColorLike]=None,text_thickness:Optional[int]=None,draw_box:bool=True,
diff --git a/dev/reference/filter/index.html b/dev/reference/filter/index.html
index 0bc3da89..f64c185e 100644
--- a/dev/reference/filter/index.html
+++ b/dev/reference/filter/index.html
@@ -9,7 +9,7 @@
-
+
diff --git a/dev/reference/index.html b/dev/reference/index.html
index 0ec17376..d657c322 100644
--- a/dev/reference/index.html
+++ b/dev/reference/index.html
@@ -9,7 +9,7 @@
-
+
diff --git a/dev/reference/metrics/index.html b/dev/reference/metrics/index.html
index 2326d1ad..601019b4 100644
--- a/dev/reference/metrics/index.html
+++ b/dev/reference/metrics/index.html
@@ -9,7 +9,7 @@
-
+
diff --git a/dev/reference/tracker/index.html b/dev/reference/tracker/index.html
index 95eb1e42..a548bc8c 100644
--- a/dev/reference/tracker/index.html
+++ b/dev/reference/tracker/index.html
@@ -9,7 +9,7 @@
-
+
diff --git a/dev/reference/utils/index.html b/dev/reference/utils/index.html
index bd02e286..62ef1d14 100644
--- a/dev/reference/utils/index.html
+++ b/dev/reference/utils/index.html
@@ -9,7 +9,7 @@
-
+
diff --git a/dev/reference/video/index.html b/dev/reference/video/index.html
index cba25955..6367ed52 100644
--- a/dev/reference/video/index.html
+++ b/dev/reference/video/index.html
@@ -9,7 +9,7 @@
-
+
diff --git a/dev/search/search_index.json b/dev/search/search_index.json
index 9ad446ca..b89f0d86 100644
--- a/dev/search/search_index.json
+++ b/dev/search/search_index.json
@@ -1 +1 @@
-{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Norfair is a customizable lightweight Python library for real-time multi-object tracking. Using Norfair, you can add tracking capabilities to any detector with just a few lines of code. Tracking players with moving camera Tracking 3D objects Features # Any detector expressing its detections as a series of (x, y) coordinates can be used with Norfair. This includes detectors performing tasks such as object or keypoint detection (see examples ). Modular. It can easily be inserted into complex video processing pipelines to add tracking to existing projects. At the same time, it is possible to build a video inference loop from scratch using just Norfair and a detector. Supports moving camera, re-identification with appearance embeddings, and n-dimensional object tracking (see Advanced features ). Norfair provides several predefined distance functions to compare tracked objects and detections. The distance functions can also be defined by the user, enabling the implementation of different tracking strategies. Fast. The only thing bounding inference speed will be the detection network feeding detections to Norfair. Norfair is built, used and maintained by Tryolabs . Installation # Norfair currently supports Python 3.7+. For the minimal version, install as: 1 pip install norfair To make Norfair install the dependencies to support more features, install as: 1 2 3 pip install norfair [ video ] # Adds several video helper features running on OpenCV pip install norfair [ metrics ] # Supports running MOT metrics evaluation pip install norfair [ metrics,video ] # Everything included If the needed dependencies are already present in the system, installing the minimal version of Norfair is enough for enabling the extra features. This is particularly useful for embedded devices, where installing compiled dependencies can be difficult, but they can sometimes come preinstalled with the system. Documentation # Getting started guide . Official reference . Examples & demos # We provide several examples of how Norfair can be used to add tracking capabilities to different detectors, and also showcase more advanced features. Note: for ease of reproducibility, we provide Dockerfiles for all the demos. Even though Norfair does not need a GPU, the default configuration of most demos requires a GPU to be able to run the detectors. For this, make sure you install NVIDIA Container Toolkit so that your GPU can be shared with Docker. It is possible to run several demos with a CPU, but you will have to modify the scripts or tinker with the installation of their dependencies. Adding tracking to different detectors # Most tracking demos are showcased with vehicles and pedestrians, but the detectors are generally trained with many more classes from the COCO dataset . YOLOv7 : tracking object centroids or bounding boxes. YOLOv5 : tracking object centroids or bounding boxes. YOLOv4 : tracking object centroids. Detectron2 : tracking object centroids. AlphaPose : tracking human keypoints (pose estimation) and inserting Norfair into a complex existing pipeline using. OpenPose : tracking human keypoints. Tracking objects with YOLOPv2 , a model for traffic object detection, drivable road area segmentation, and lane line detection. Advanced features # Speed up pose estimation by extrapolating detections using OpenPose . Track both bounding boxes and human keypoints (multi-class), unifying the detections from a YOLO model and OpenPose. Re-identification (ReID) of tracked objects using appearance embeddings. This is a good starting point for scenarios with a lot of occlusion, in which the Kalman filter alone would struggle. Accurately track objects even if the camera is moving , by estimating camera motion potentially accounting for pan, tilt, rotation, movement in any direction, and zoom. Track points in 3D , using MediaPipe Objectron . Tracking of small objects , using SAHI: Slicing Aided Hyper Inference . ROS integration # To make it even easier to use Norfair in robotics projects, we now offer a version that integrates with the Robotic Operating System (ROS). We present a ROS package and a fully functional environment running on Docker to do the first steps with this package and start your first application easier. Benchmarking and profiling # Kalman filter and distance function profiling using TRT pose estimator . Computation of MOT17 scores using motmetrics4norfair . How it works # 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. As an example we use 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. On the left you can see the points we get from Detectron2, and on the right how Norfair tracks them assigning a unique identifier through time. Even a straightforward distance function like this one can work when the tracking needed is simple. Norfair also provides several useful tools for creating a video inference loop. Here is what the full code for creating the previous example looks like, including the code needed to set up Detectron2: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import cv2 import numpy as np from detectron2.config import get_cfg from detectron2.engine import DefaultPredictor from norfair import Detection , Tracker , Video , draw_tracked_objects # Set up Detectron2 object detector cfg = get_cfg () cfg . merge_from_file ( \"demos/faster_rcnn_R_50_FPN_3x.yaml\" ) cfg . MODEL . ROI_HEADS . SCORE_THRESH_TEST = 0.5 cfg . MODEL . WEIGHTS = \"detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl\" detector = DefaultPredictor ( cfg ) # Norfair video = Video ( input_path = \"video.mp4\" ) tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 20 ) for frame in video : detections = detector ( cv2 . cvtColor ( frame , cv2 . COLOR_BGR2RGB )) detections = [ Detection ( p ) for p in detections [ 'instances' ] . pred_boxes . get_centers () . cpu () . numpy ()] tracked_objects = tracker . update ( detections = detections ) draw_tracked_objects ( frame , tracked_objects ) video . write ( frame ) The video and drawing tools use OpenCV frames, so they are compatible with most Python video code available online. The point tracking is based on SORT generalized to detections consisting of a dynamically changing number of points per detection. Motivation # Trying out the latest state-of-the-art detectors normally requires running repositories that weren't intended to be easy to use. These tend to be repositories associated with a research paper describing a novel new way of doing detection, and they are therefore intended to be run as a one-off evaluation script to get some result metric to publish on a particular research paper. This explains why they tend to not be easy to run as inference scripts, or why extracting the core model to use in another standalone script isn't always trivial. Norfair was born out of the need to quickly add a simple layer of tracking over a wide range of newly released SOTA detectors. It was designed to seamlessly be plugged into a complex, highly coupled code base, with minimum effort. Norfair provides a series of modular but compatible tools, which you can pick and choose to use in your project. Comparison to other trackers # Norfair's contribution to Python's object tracker library repertoire is its ability to work with any object detector by being able to work with a variable number of points per detection, and the ability for the user to heavily customize the tracker by creating their own distance function. If you are looking for a tracker, here are some other projects worth noting: OpenCV includes several tracking solutions like KCF Tracker and MedianFlow Tracker which are run by making the user select a part of the frame to track, and then letting the tracker follow that area. They tend not to be run on top of a detector and are not very robust. dlib includes a correlation single object tracker. You have to create your own multiple object tracker on top of it yourself if you want to track multiple objects with it. AlphaPose just released a new version of their human pose tracker. This tracker is tightly integrated into their code base, and to the task of tracking human poses. SORT and Deep SORT are similar to this repo in that they use Kalman filters (and a deep embedding for Deep SORT), but they are hardcoded to a fixed distance function and to tracking boxes. Norfair also adds some filtering when matching tracked objects with detections, and changes the Hungarian Algorithm for its own distance minimizer. Both these repos are also released under the GPL license, which might be an issue for some individuals or companies because the source code of derivative works needs to be published. Benchmarks # MOT17 and MOT20 results obtained using motmetrics4norfair demo script on the train split. We used detections obtained with ByteTrack's YOLOX object detection model. MOT17 Train IDF1 IDP IDR Rcll Prcn MOTA MOTP MOT17-02 61.3% 63.6% 59.0% 86.8% 93.5% 79.9% 14.8% MOT17-04 93.3% 93.6% 93.0% 98.6% 99.3% 97.9% 07.9% MOT17-05 77.8% 77.7% 77.8% 85.9% 85.8% 71.2% 14.7% MOT17-09 65.0% 67.4% 62.9% 90.3% 96.8% 86.8% 12.2% MOT17-10 70.2% 72.5% 68.1% 87.3% 93.0% 80.1% 18.7% MOT17-11 80.2% 80.5% 80.0% 93.0% 93.6% 86.4% 11.3% MOT17-13 79.0% 79.6% 78.4% 90.6% 92.0% 82.4% 16.6% OVERALL 80.6% 81.8% 79.6% 92.9% 95.5% 88.1% 11.9% MOT20 Train IDF1 IDP IDR Rcll Prcn MOTA MOTP MOT20-01 85.9% 88.1% 83.8% 93.4% 98.2% 91.5% 12.6% MOT20-02 72.8% 74.6% 71.0% 93.2% 97.9% 91.0% 12.7% MOT20-03 93.0% 94.1% 92.0% 96.1% 98.3% 94.4% 13.7% MOT20-05 87.9% 88.9% 87.0% 96.0% 98.1% 94.1% 13.0% OVERALL 87.3% 88.4% 86.2% 95.6% 98.1% 93.7% 13.2% Commercial support # Tryolabs can provide commercial support, implement new features in Norfair or build video analytics tools for solving your challenging problems. Norfair powers several video analytics applications, such as the face mask detection tool. If you are interested, please contact us . Citing Norfair # For citations in academic publications, please export your desired citation format (BibTeX or other) from Zenodo . License # Copyright \u00a9 2022, Tryolabs . Released under the BSD 3-Clause .","title":"Home"},{"location":"#features","text":"Any detector expressing its detections as a series of (x, y) coordinates can be used with Norfair. This includes detectors performing tasks such as object or keypoint detection (see examples ). Modular. It can easily be inserted into complex video processing pipelines to add tracking to existing projects. At the same time, it is possible to build a video inference loop from scratch using just Norfair and a detector. Supports moving camera, re-identification with appearance embeddings, and n-dimensional object tracking (see Advanced features ). Norfair provides several predefined distance functions to compare tracked objects and detections. The distance functions can also be defined by the user, enabling the implementation of different tracking strategies. Fast. The only thing bounding inference speed will be the detection network feeding detections to Norfair. Norfair is built, used and maintained by Tryolabs .","title":"Features"},{"location":"#installation","text":"Norfair currently supports Python 3.7+. For the minimal version, install as: 1 pip install norfair To make Norfair install the dependencies to support more features, install as: 1 2 3 pip install norfair [ video ] # Adds several video helper features running on OpenCV pip install norfair [ metrics ] # Supports running MOT metrics evaluation pip install norfair [ metrics,video ] # Everything included If the needed dependencies are already present in the system, installing the minimal version of Norfair is enough for enabling the extra features. This is particularly useful for embedded devices, where installing compiled dependencies can be difficult, but they can sometimes come preinstalled with the system.","title":"Installation"},{"location":"#documentation","text":"Getting started guide . Official reference .","title":"Documentation"},{"location":"#examples-demos","text":"We provide several examples of how Norfair can be used to add tracking capabilities to different detectors, and also showcase more advanced features. Note: for ease of reproducibility, we provide Dockerfiles for all the demos. Even though Norfair does not need a GPU, the default configuration of most demos requires a GPU to be able to run the detectors. For this, make sure you install NVIDIA Container Toolkit so that your GPU can be shared with Docker. It is possible to run several demos with a CPU, but you will have to modify the scripts or tinker with the installation of their dependencies.","title":"Examples & demos"},{"location":"#adding-tracking-to-different-detectors","text":"Most tracking demos are showcased with vehicles and pedestrians, but the detectors are generally trained with many more classes from the COCO dataset . YOLOv7 : tracking object centroids or bounding boxes. YOLOv5 : tracking object centroids or bounding boxes. YOLOv4 : tracking object centroids. Detectron2 : tracking object centroids. AlphaPose : tracking human keypoints (pose estimation) and inserting Norfair into a complex existing pipeline using. OpenPose : tracking human keypoints. Tracking objects with YOLOPv2 , a model for traffic object detection, drivable road area segmentation, and lane line detection.","title":"Adding tracking to different detectors"},{"location":"#advanced-features","text":"Speed up pose estimation by extrapolating detections using OpenPose . Track both bounding boxes and human keypoints (multi-class), unifying the detections from a YOLO model and OpenPose. Re-identification (ReID) of tracked objects using appearance embeddings. This is a good starting point for scenarios with a lot of occlusion, in which the Kalman filter alone would struggle. Accurately track objects even if the camera is moving , by estimating camera motion potentially accounting for pan, tilt, rotation, movement in any direction, and zoom. Track points in 3D , using MediaPipe Objectron . Tracking of small objects , using SAHI: Slicing Aided Hyper Inference .","title":"Advanced features"},{"location":"#ros-integration","text":"To make it even easier to use Norfair in robotics projects, we now offer a version that integrates with the Robotic Operating System (ROS). We present a ROS package and a fully functional environment running on Docker to do the first steps with this package and start your first application easier.","title":"ROS integration"},{"location":"#benchmarking-and-profiling","text":"Kalman filter and distance function profiling using TRT pose estimator . Computation of MOT17 scores using motmetrics4norfair .","title":"Benchmarking and profiling"},{"location":"#how-it-works","text":"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. As an example we use 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. On the left you can see the points we get from Detectron2, and on the right how Norfair tracks them assigning a unique identifier through time. Even a straightforward distance function like this one can work when the tracking needed is simple. Norfair also provides several useful tools for creating a video inference loop. Here is what the full code for creating the previous example looks like, including the code needed to set up Detectron2: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import cv2 import numpy as np from detectron2.config import get_cfg from detectron2.engine import DefaultPredictor from norfair import Detection , Tracker , Video , draw_tracked_objects # Set up Detectron2 object detector cfg = get_cfg () cfg . merge_from_file ( \"demos/faster_rcnn_R_50_FPN_3x.yaml\" ) cfg . MODEL . ROI_HEADS . SCORE_THRESH_TEST = 0.5 cfg . MODEL . WEIGHTS = \"detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl\" detector = DefaultPredictor ( cfg ) # Norfair video = Video ( input_path = \"video.mp4\" ) tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 20 ) for frame in video : detections = detector ( cv2 . cvtColor ( frame , cv2 . COLOR_BGR2RGB )) detections = [ Detection ( p ) for p in detections [ 'instances' ] . pred_boxes . get_centers () . cpu () . numpy ()] tracked_objects = tracker . update ( detections = detections ) draw_tracked_objects ( frame , tracked_objects ) video . write ( frame ) The video and drawing tools use OpenCV frames, so they are compatible with most Python video code available online. The point tracking is based on SORT generalized to detections consisting of a dynamically changing number of points per detection.","title":"How it works"},{"location":"#motivation","text":"Trying out the latest state-of-the-art detectors normally requires running repositories that weren't intended to be easy to use. These tend to be repositories associated with a research paper describing a novel new way of doing detection, and they are therefore intended to be run as a one-off evaluation script to get some result metric to publish on a particular research paper. This explains why they tend to not be easy to run as inference scripts, or why extracting the core model to use in another standalone script isn't always trivial. Norfair was born out of the need to quickly add a simple layer of tracking over a wide range of newly released SOTA detectors. It was designed to seamlessly be plugged into a complex, highly coupled code base, with minimum effort. Norfair provides a series of modular but compatible tools, which you can pick and choose to use in your project.","title":"Motivation"},{"location":"#comparison-to-other-trackers","text":"Norfair's contribution to Python's object tracker library repertoire is its ability to work with any object detector by being able to work with a variable number of points per detection, and the ability for the user to heavily customize the tracker by creating their own distance function. If you are looking for a tracker, here are some other projects worth noting: OpenCV includes several tracking solutions like KCF Tracker and MedianFlow Tracker which are run by making the user select a part of the frame to track, and then letting the tracker follow that area. They tend not to be run on top of a detector and are not very robust. dlib includes a correlation single object tracker. You have to create your own multiple object tracker on top of it yourself if you want to track multiple objects with it. AlphaPose just released a new version of their human pose tracker. This tracker is tightly integrated into their code base, and to the task of tracking human poses. SORT and Deep SORT are similar to this repo in that they use Kalman filters (and a deep embedding for Deep SORT), but they are hardcoded to a fixed distance function and to tracking boxes. Norfair also adds some filtering when matching tracked objects with detections, and changes the Hungarian Algorithm for its own distance minimizer. Both these repos are also released under the GPL license, which might be an issue for some individuals or companies because the source code of derivative works needs to be published.","title":"Comparison to other trackers"},{"location":"#benchmarks","text":"MOT17 and MOT20 results obtained using motmetrics4norfair demo script on the train split. We used detections obtained with ByteTrack's YOLOX object detection model. MOT17 Train IDF1 IDP IDR Rcll Prcn MOTA MOTP MOT17-02 61.3% 63.6% 59.0% 86.8% 93.5% 79.9% 14.8% MOT17-04 93.3% 93.6% 93.0% 98.6% 99.3% 97.9% 07.9% MOT17-05 77.8% 77.7% 77.8% 85.9% 85.8% 71.2% 14.7% MOT17-09 65.0% 67.4% 62.9% 90.3% 96.8% 86.8% 12.2% MOT17-10 70.2% 72.5% 68.1% 87.3% 93.0% 80.1% 18.7% MOT17-11 80.2% 80.5% 80.0% 93.0% 93.6% 86.4% 11.3% MOT17-13 79.0% 79.6% 78.4% 90.6% 92.0% 82.4% 16.6% OVERALL 80.6% 81.8% 79.6% 92.9% 95.5% 88.1% 11.9% MOT20 Train IDF1 IDP IDR Rcll Prcn MOTA MOTP MOT20-01 85.9% 88.1% 83.8% 93.4% 98.2% 91.5% 12.6% MOT20-02 72.8% 74.6% 71.0% 93.2% 97.9% 91.0% 12.7% MOT20-03 93.0% 94.1% 92.0% 96.1% 98.3% 94.4% 13.7% MOT20-05 87.9% 88.9% 87.0% 96.0% 98.1% 94.1% 13.0% OVERALL 87.3% 88.4% 86.2% 95.6% 98.1% 93.7% 13.2%","title":"Benchmarks"},{"location":"#commercial-support","text":"Tryolabs can provide commercial support, implement new features in Norfair or build video analytics tools for solving your challenging problems. Norfair powers several video analytics applications, such as the face mask detection tool. If you are interested, please contact us .","title":"Commercial support"},{"location":"#citing-norfair","text":"For citations in academic publications, please export your desired citation format (BibTeX or other) from Zenodo .","title":"Citing Norfair"},{"location":"#license","text":"Copyright \u00a9 2022, Tryolabs . Released under the BSD 3-Clause .","title":"License"},{"location":"getting_started/","text":"Getting Started # Norfair's goal is to easily track multiple objects in videos based on the frame-by-frame detections of a user-defined model. Model or Detector # We recommend first deciding and setting up the model and then adding Norfair on top of it. Models trained for any form of object detection or keypoint detection (including pose estimation ) are all supported. You can check some of the integrations we have as examples: Yolov7 , Yolov5 and Yolov4 Detectron2 Alphapose Openpose MMDetection Any other model trained on one of the supported tasks is also supported and should be easy to integrate with Norfair, regardless of whether it uses Pytorch, TensorFlow, or other. If you are unsure of which model to use, Yolov7 is a good starting point since it's easy to set up and offers models of different sizes pre-trained on object detection and pose estimation. Note Norfair is a Detection-Based-Tracker (DBT) and as such, its performance is highly dependent on the performance of the model of choice. The detections from the model will need to be wrapped in an instance of Detection before passing them to Norfair. Install # Installing Norfair is extremely easy, simply run pip install norfair to install the latest version from PyPI . You can also install the latest version from the master branch using pip install git+https://github.com/tryolabs/norfair.git@master#egg=norfair Video # Norfair offers optional functionality to process videos (mp4 and mov formats are supported) or capture a live feed from a camera. To use this functionality you need to install Norfair with the video extra using this command: pip install norfair[video] . Check the Video class for more info on how to use it. Tracking # Let's dive right into a simple example in the following snippet: 1 2 3 4 5 6 7 8 9 10 11 12 from norfair import Detection , Tracker , Video , draw_tracked_objects detector = MyDetector () # Set up a detector video = Video ( input_path = \"video.mp4\" ) tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 100 ) for frame in video : detections = detector ( frame ) norfair_detections = [ Detection ( points ) for points in detections ] tracked_objects = tracker . update ( detections = norfair_detections ) draw_tracked_objects ( frame , tracked_objects ) video . write ( frame ) The tracker is created and then the detections are fed to it one frame at a time in order. This method is called online tracking and allows Norfair to be used in live feeds and real-time scenarios where future frames are not available. Norfair includes functionality for creating an output video with drawings which is useful for evaluating and debugging. We usually start with this simple setup and move from there. Next Steps # The next steps depend a lot on your goal and the result of evaluating the output videos, nevertheless here are some pointers that might help you solve common problems Detection Issues # Most common problem is that the tracking has errors or is not precise enough. In this case, the first thing to check is whether this is a detection error or a tracking error. As mentioned above if the detector fails the tracking will suffer. To debug this use draw_points or draw_boxes to inspect the detections and analyze if they are precise enough. If you are filtering the detections based on scores, this is a good time to tweak the threshold. If you decide that the detections are not good enough you can try a different architecture, a bigger version of the model, or consider fine-tuning the model on your domain. Tracking Issues # After inspecting the detections you might find issues with the tracking, several things can go wrong with tracking but here is a list of common errors and things to try: Objects take too long to start , this can have multiple causes: initialization_delay is too big on the Tracker. Makes the TrackedObject stay on initializing for too long, 3 is usually a good value to start with. distance_threshold is too small on the Tracker. Prevents the Detections to be matched with the correct TrackedObject. The best value depends on the distance used. Incorrect distance_function on the Tracker. Some distances might not be valid in some cases, for instance, if using IoU but the objects in your video move so quickly that there is never an overlap between the detections of consecutive frames. Try different distances, euclidean or create_normalized_mean_euclidean_distance are good starting points. Objects take too long to disappear . Lower hit_counter_max on the Tracker. Points or bounding boxes jitter too much . Increase R (measurement error) or lower Q (estimate or process error) on the OptimizedKalmanFilterFactory or FilterPyKalmanFilterFactory . This makes the Kalman Filter put less weight on the measurements and trust more on the estimate, stabilizing the result. Camera motion confuses the Tracker. If the camera moves, the apparent movement of objects can become too erratic for the Tracker. Use MotionEstimator . Incorrect matches between Detections and TrackedObjects, a couple of scenarios can cause this: distance_threshold is too big so the Tracker matches Detections to TrackedObjects that are simply too far. Lower the threshold until you fix the error, the correct value will depend on the distance function that you're using. Mismatches when objects overlap. In this case, tracking becomes more challenging, usually, the quality of the detection degrades causing one of the objects to be missed or creating a single big detection that includes both objects. On top of the detection issues, the tracker needs to decide which detection should be matched to which TrackedObject which can be error-prone if only considering spatial information. The solution is not easy but incorporating the notion of the appearance similarity based on some kind of embedding to your distance_function can help. Can't recover an object after occlusions . Use ReID distance, see this demo for an example but for real-world use you will need a good ReID model that can provide good embeddings.","title":"Getting Started"},{"location":"getting_started/#getting-started","text":"Norfair's goal is to easily track multiple objects in videos based on the frame-by-frame detections of a user-defined model.","title":"Getting Started"},{"location":"getting_started/#model-or-detector","text":"We recommend first deciding and setting up the model and then adding Norfair on top of it. Models trained for any form of object detection or keypoint detection (including pose estimation ) are all supported. You can check some of the integrations we have as examples: Yolov7 , Yolov5 and Yolov4 Detectron2 Alphapose Openpose MMDetection Any other model trained on one of the supported tasks is also supported and should be easy to integrate with Norfair, regardless of whether it uses Pytorch, TensorFlow, or other. If you are unsure of which model to use, Yolov7 is a good starting point since it's easy to set up and offers models of different sizes pre-trained on object detection and pose estimation. Note Norfair is a Detection-Based-Tracker (DBT) and as such, its performance is highly dependent on the performance of the model of choice. The detections from the model will need to be wrapped in an instance of Detection before passing them to Norfair.","title":"Model or Detector"},{"location":"getting_started/#install","text":"Installing Norfair is extremely easy, simply run pip install norfair to install the latest version from PyPI . You can also install the latest version from the master branch using pip install git+https://github.com/tryolabs/norfair.git@master#egg=norfair","title":"Install"},{"location":"getting_started/#video","text":"Norfair offers optional functionality to process videos (mp4 and mov formats are supported) or capture a live feed from a camera. To use this functionality you need to install Norfair with the video extra using this command: pip install norfair[video] . Check the Video class for more info on how to use it.","title":"Video"},{"location":"getting_started/#tracking","text":"Let's dive right into a simple example in the following snippet: 1 2 3 4 5 6 7 8 9 10 11 12 from norfair import Detection , Tracker , Video , draw_tracked_objects detector = MyDetector () # Set up a detector video = Video ( input_path = \"video.mp4\" ) tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 100 ) for frame in video : detections = detector ( frame ) norfair_detections = [ Detection ( points ) for points in detections ] tracked_objects = tracker . update ( detections = norfair_detections ) draw_tracked_objects ( frame , tracked_objects ) video . write ( frame ) The tracker is created and then the detections are fed to it one frame at a time in order. This method is called online tracking and allows Norfair to be used in live feeds and real-time scenarios where future frames are not available. Norfair includes functionality for creating an output video with drawings which is useful for evaluating and debugging. We usually start with this simple setup and move from there.","title":"Tracking"},{"location":"getting_started/#next-steps","text":"The next steps depend a lot on your goal and the result of evaluating the output videos, nevertheless here are some pointers that might help you solve common problems","title":"Next Steps"},{"location":"getting_started/#detection-issues","text":"Most common problem is that the tracking has errors or is not precise enough. In this case, the first thing to check is whether this is a detection error or a tracking error. As mentioned above if the detector fails the tracking will suffer. To debug this use draw_points or draw_boxes to inspect the detections and analyze if they are precise enough. If you are filtering the detections based on scores, this is a good time to tweak the threshold. If you decide that the detections are not good enough you can try a different architecture, a bigger version of the model, or consider fine-tuning the model on your domain.","title":"Detection Issues"},{"location":"getting_started/#tracking-issues","text":"After inspecting the detections you might find issues with the tracking, several things can go wrong with tracking but here is a list of common errors and things to try: Objects take too long to start , this can have multiple causes: initialization_delay is too big on the Tracker. Makes the TrackedObject stay on initializing for too long, 3 is usually a good value to start with. distance_threshold is too small on the Tracker. Prevents the Detections to be matched with the correct TrackedObject. The best value depends on the distance used. Incorrect distance_function on the Tracker. Some distances might not be valid in some cases, for instance, if using IoU but the objects in your video move so quickly that there is never an overlap between the detections of consecutive frames. Try different distances, euclidean or create_normalized_mean_euclidean_distance are good starting points. Objects take too long to disappear . Lower hit_counter_max on the Tracker. Points or bounding boxes jitter too much . Increase R (measurement error) or lower Q (estimate or process error) on the OptimizedKalmanFilterFactory or FilterPyKalmanFilterFactory . This makes the Kalman Filter put less weight on the measurements and trust more on the estimate, stabilizing the result. Camera motion confuses the Tracker. If the camera moves, the apparent movement of objects can become too erratic for the Tracker. Use MotionEstimator . Incorrect matches between Detections and TrackedObjects, a couple of scenarios can cause this: distance_threshold is too big so the Tracker matches Detections to TrackedObjects that are simply too far. Lower the threshold until you fix the error, the correct value will depend on the distance function that you're using. Mismatches when objects overlap. In this case, tracking becomes more challenging, usually, the quality of the detection degrades causing one of the objects to be missed or creating a single big detection that includes both objects. On top of the detection issues, the tracker needs to decide which detection should be matched to which TrackedObject which can be error-prone if only considering spatial information. The solution is not easy but incorporating the notion of the appearance similarity based on some kind of embedding to your distance_function can help. Can't recover an object after occlusions . Use ReID distance, see this demo for an example but for real-world use you will need a good ReID model that can provide good embeddings.","title":"Tracking Issues"},{"location":"reference/","text":"Reference # A customizable lightweight Python library for real-time multi-object tracking. Examples: >>> from norfair import Detection , Tracker , Video , draw_tracked_objects >>> detector = MyDetector () # Set up a detector >>> video = Video ( input_path = \"video.mp4\" ) >>> tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 50 ) >>> for frame in video : >>> detections = detector ( frame ) >>> norfair_detections = [ Detection ( points ) for points in detections ] >>> tracked_objects = tracker . update ( detections = norfair_detections ) >>> draw_tracked_objects ( frame , tracked_objects ) >>> video . write ( frame )","title":"Reference"},{"location":"reference/#reference","text":"A customizable lightweight Python library for real-time multi-object tracking. Examples: >>> from norfair import Detection , Tracker , Video , draw_tracked_objects >>> detector = MyDetector () # Set up a detector >>> video = Video ( input_path = \"video.mp4\" ) >>> tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 50 ) >>> for frame in video : >>> detections = detector ( frame ) >>> norfair_detections = [ Detection ( points ) for points in detections ] >>> tracked_objects = tracker . update ( detections = norfair_detections ) >>> draw_tracked_objects ( frame , tracked_objects ) >>> video . write ( frame )","title":"Reference"},{"location":"reference/camera_motion/","text":"Camera Motion # Camera motion stimation module. CoordinatesTransformation # Bases: ABC Abstract class representing a coordinate transformation. Detections' and tracked objects' coordinates can be interpreted in 2 reference: Relative : their position on the current frame, (0, 0) is top left Absolute : their position on an fixed space, (0, 0) is the top left of the first frame of the video. Therefore, coordinate transformation in this context is a class that can transform coordinates in one reference to another. Source code in norfair/camera_motion.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class CoordinatesTransformation ( ABC ): \"\"\" Abstract class representing a coordinate transformation. Detections' and tracked objects' coordinates can be interpreted in 2 reference: - _Relative_: their position on the current frame, (0, 0) is top left - _Absolute_: their position on an fixed space, (0, 0) is the top left of the first frame of the video. Therefore, coordinate transformation in this context is a class that can transform coordinates in one reference to another. \"\"\" @abstractmethod def abs_to_rel ( self , points : np . ndarray ) -> np . ndarray : pass @abstractmethod def rel_to_abs ( self , points : np . ndarray ) -> np . ndarray : pass TransformationGetter # Bases: ABC Abstract class representing a method for finding CoordinatesTransformation between 2 sets of points Source code in norfair/camera_motion.py 41 42 43 44 45 46 47 48 49 50 class TransformationGetter ( ABC ): \"\"\" Abstract class representing a method for finding CoordinatesTransformation between 2 sets of points \"\"\" @abstractmethod def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , CoordinatesTransformation ]: pass TranslationTransformation # Bases: CoordinatesTransformation Coordinate transformation between points using a simple translation Parameters: Name Type Description Default movement_vector np . ndarray The vector representing the translation. required Source code in norfair/camera_motion.py 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class TranslationTransformation ( CoordinatesTransformation ): \"\"\" Coordinate transformation between points using a simple translation Parameters ---------- movement_vector : np.ndarray The vector representing the translation. \"\"\" def __init__ ( self , movement_vector ): self . movement_vector = movement_vector def abs_to_rel ( self , points : np . ndarray ): return points + self . movement_vector def rel_to_abs ( self , points : np . ndarray ): return points - self . movement_vector TranslationTransformationGetter # Bases: TransformationGetter Calculates TranslationTransformation between points. The camera movement is calculated as the mode of optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the translation, for this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters: Name Type Description Default bin_size float Before calculatin the mode, optiocal flow is bucketized into bins of this size. 0.2 proportion_points_used_threshold float Proportion of points that must be matched, otherwise the reference frame must be updated. 0.9 Source code in norfair/camera_motion.py 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 class TranslationTransformationGetter ( TransformationGetter ): \"\"\" Calculates TranslationTransformation between points. The camera movement is calculated as the mode of optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the translation, for this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters ---------- bin_size : float Before calculatin the mode, optiocal flow is bucketized into bins of this size. proportion_points_used_threshold: float Proportion of points that must be matched, otherwise the reference frame must be updated. \"\"\" def __init__ ( self , bin_size : float = 0.2 , proportion_points_used_threshold : float = 0.9 ) -> None : self . bin_size = bin_size self . proportion_points_used_threshold = proportion_points_used_threshold self . data = None def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , TranslationTransformation ]: # get flow flow = curr_pts - prev_pts # get mode flow = np . around ( flow / self . bin_size ) * self . bin_size unique_flows , counts = np . unique ( flow , axis = 0 , return_counts = True ) max_index = counts . argmax () proportion_points_used = counts [ max_index ] / len ( prev_pts ) update_prvs = proportion_points_used < self . proportion_points_used_threshold flow_mode = unique_flows [ max_index ] try : flow_mode += self . data except TypeError : pass if update_prvs : self . data = flow_mode return update_prvs , TranslationTransformation ( flow_mode ) HomographyTransformation # Bases: CoordinatesTransformation Coordinate transformation beweent points using an homography Parameters: Name Type Description Default homography_matrix np . ndarray The matrix representing the homography required Source code in norfair/camera_motion.py 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 class HomographyTransformation ( CoordinatesTransformation ): \"\"\" Coordinate transformation beweent points using an homography Parameters ---------- homography_matrix : np.ndarray The matrix representing the homography \"\"\" def __init__ ( self , homography_matrix : np . ndarray ): self . homography_matrix = homography_matrix self . inverse_homography_matrix = np . linalg . inv ( homography_matrix ) def abs_to_rel ( self , points : np . ndarray ): ones = np . ones (( len ( points ), 1 )) points_with_ones = np . hstack (( points , ones )) points_transformed = points_with_ones @ self . homography_matrix . T points_transformed = points_transformed / points_transformed [:, - 1 ] . reshape ( - 1 , 1 ) return points_transformed [:, : 2 ] def rel_to_abs ( self , points : np . ndarray ): ones = np . ones (( len ( points ), 1 )) points_with_ones = np . hstack (( points , ones )) points_transformed = points_with_ones @ self . inverse_homography_matrix . T points_transformed = points_transformed / points_transformed [:, - 1 ] . reshape ( - 1 , 1 ) return points_transformed [:, : 2 ] HomographyTransformationGetter # Bases: TransformationGetter Calculates HomographyTransformation between points. The camera movement is represented as an homography that matches the optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the homography, often resulting in the identity. For this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters: Name Type Description Default method Optional [ int ], optional One of openCV's method for finding homographies. Valid options are: [0, cv.RANSAC, cv.LMEDS, cv.RHO] , by default cv.RANSAC None ransac_reproj_threshold int , optional Maximum allowed reprojection error to treat a point pair as an inlier. More info in links below. 3 max_iters int , optional The maximum number of RANSAC iterations. More info in links below. 2000 confidence float , optional Confidence level, must be between 0 and 1. More info in links below. 0.995 proportion_points_used_threshold float , optional Proportion of points that must be matched, otherwise the reference frame must be updated. 0.9 See Also # opencv.findHomography Source code in norfair/camera_motion.py 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 class HomographyTransformationGetter ( TransformationGetter ): \"\"\" Calculates HomographyTransformation between points. The camera movement is represented as an homography that matches the optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the homography, often resulting in the identity. For this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters ---------- method : Optional[int], optional One of openCV's method for finding homographies. Valid options are: `[0, cv.RANSAC, cv.LMEDS, cv.RHO]`, by default `cv.RANSAC` ransac_reproj_threshold : int, optional Maximum allowed reprojection error to treat a point pair as an inlier. More info in links below. max_iters : int, optional The maximum number of RANSAC iterations. More info in links below. confidence : float, optional Confidence level, must be between 0 and 1. More info in links below. proportion_points_used_threshold : float, optional Proportion of points that must be matched, otherwise the reference frame must be updated. See Also -------- [opencv.findHomography](https://docs.opencv.org/3.4/d9/d0c/group__calib3d.html#ga4abc2ece9fab9398f2e560d53c8c9780) \"\"\" def __init__ ( self , method : Optional [ int ] = None , ransac_reproj_threshold : int = 3 , max_iters : int = 2000 , confidence : float = 0.995 , proportion_points_used_threshold : float = 0.9 , ) -> None : self . data = None if method is None : method = cv2 . RANSAC self . method = method self . ransac_reproj_threshold = ransac_reproj_threshold self . max_iters = max_iters self . confidence = confidence self . proportion_points_used_threshold = proportion_points_used_threshold def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , HomographyTransformation ]: homography_matrix , points_used = cv2 . findHomography ( prev_pts , curr_pts , method = self . method , ransacReprojThreshold = self . ransac_reproj_threshold , maxIters = self . max_iters , confidence = self . confidence , ) proportion_points_used = np . sum ( points_used ) / len ( points_used ) update_prvs = proportion_points_used < self . proportion_points_used_threshold try : homography_matrix = homography_matrix @ self . data except ( TypeError , ValueError ): pass if update_prvs : self . data = homography_matrix return update_prvs , HomographyTransformation ( homography_matrix ) MotionEstimator # Estimator of the motion of the camera. Uses optical flow to estimate the motion of the camera from frame to frame. The optical flow is calculated on a sample of strong points (corners). Parameters: Name Type Description Default max_points int , optional Maximum amount of points sampled. More points make the estimation process slower but more precise 200 min_distance int , optional Minimum distance between the sample points. 15 block_size int , optional Size of an average block when finding the corners. More info in links below. 3 transformations_getter TransformationGetter , optional An instance of TransformationGetter. By default HomographyTransformationGetter None draw_flow bool , optional Draws the optical flow on the frame for debugging. False flow_color Optional [ Tuple [ int , int , int ]], optional Color of the drawing, by default blue. None quality_level float , optional Parameter characterizing the minimal accepted quality of image corners. 0.01 Examples: >>> from norfair import Tracker , Video >>> from norfair.camera_motion MotionEstimator >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> motion_estimator = MotionEstimator () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> coord_transformation = motion_estimator . update ( frame ) >>> tracked_objects = tracker . update ( detections , coord_transformations = coord_transformation ) See Also # For more infor on how the points are sampled: OpenCV.goodFeaturesToTrack Source code in norfair/camera_motion.py 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 class MotionEstimator : \"\"\" Estimator of the motion of the camera. Uses optical flow to estimate the motion of the camera from frame to frame. The optical flow is calculated on a sample of strong points (corners). Parameters ---------- max_points : int, optional Maximum amount of points sampled. More points make the estimation process slower but more precise min_distance : int, optional Minimum distance between the sample points. block_size : int, optional Size of an average block when finding the corners. More info in links below. transformations_getter : TransformationGetter, optional An instance of TransformationGetter. By default [`HomographyTransformationGetter`][norfair.camera_motion.HomographyTransformationGetter] draw_flow : bool, optional Draws the optical flow on the frame for debugging. flow_color : Optional[Tuple[int, int, int]], optional Color of the drawing, by default blue. quality_level : float, optional Parameter characterizing the minimal accepted quality of image corners. Examples -------- >>> from norfair import Tracker, Video >>> from norfair.camera_motion MotionEstimator >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> motion_estimator = MotionEstimator() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> coord_transformation = motion_estimator.update(frame) >>> tracked_objects = tracker.update(detections, coord_transformations=coord_transformation) See Also -------- For more infor on how the points are sampled: [OpenCV.goodFeaturesToTrack](https://docs.opencv.org/3.4/dd/d1a/group__imgproc__feature.html#ga1d6bb77486c8f92d79c8793ad995d541) \"\"\" def __init__ ( self , max_points : int = 200 , min_distance : int = 15 , block_size : int = 3 , transformations_getter : TransformationGetter = None , draw_flow : bool = False , flow_color : Optional [ Tuple [ int , int , int ]] = None , quality_level : float = 0.01 , ): self . max_points = max_points self . min_distance = min_distance self . block_size = block_size self . draw_flow = draw_flow if self . draw_flow and flow_color is None : flow_color = [ 0 , 0 , 100 ] self . flow_color = flow_color self . gray_prvs = None self . prev_pts = None if transformations_getter is None : transformations_getter = HomographyTransformationGetter () self . transformations_getter = transformations_getter self . prev_mask = None self . gray_next = None self . quality_level = quality_level def update ( self , frame : np . ndarray , mask : np . ndarray = None ) -> CoordinatesTransformation : \"\"\" Estimate camera motion for each frame Parameters ---------- frame : np.ndarray The frame. mask : np.ndarray, optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape `(frame.shape[0], frame.shape[1])`, dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. Returns ------- CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. \"\"\" self . gray_next = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2GRAY ) if self . gray_prvs is None : self . gray_prvs = self . gray_next self . prev_mask = mask curr_pts , self . prev_pts = _get_sparse_flow ( self . gray_next , self . gray_prvs , self . prev_pts , self . max_points , self . min_distance , self . block_size , self . prev_mask , quality_level = self . quality_level , ) if self . draw_flow : for ( curr , prev ) in zip ( curr_pts , self . prev_pts ): c = tuple ( curr . astype ( int ) . ravel ()) p = tuple ( prev . astype ( int ) . ravel ()) cv2 . line ( frame , c , p , self . flow_color , 2 ) cv2 . circle ( frame , c , 3 , self . flow_color , - 1 ) update_prvs , coord_transformations = self . transformations_getter ( curr_pts , self . prev_pts , ) if update_prvs : self . gray_prvs = self . gray_next self . prev_pts = None self . prev_mask = mask return coord_transformations update ( frame , mask = None ) # Estimate camera motion for each frame Parameters: Name Type Description Default frame np . ndarray The frame. required mask np . ndarray , optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape (frame.shape[0], frame.shape[1]) , dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. None Returns: Type Description CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. Source code in norfair/camera_motion.py 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 def update ( self , frame : np . ndarray , mask : np . ndarray = None ) -> CoordinatesTransformation : \"\"\" Estimate camera motion for each frame Parameters ---------- frame : np.ndarray The frame. mask : np.ndarray, optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape `(frame.shape[0], frame.shape[1])`, dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. Returns ------- CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. \"\"\" self . gray_next = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2GRAY ) if self . gray_prvs is None : self . gray_prvs = self . gray_next self . prev_mask = mask curr_pts , self . prev_pts = _get_sparse_flow ( self . gray_next , self . gray_prvs , self . prev_pts , self . max_points , self . min_distance , self . block_size , self . prev_mask , quality_level = self . quality_level , ) if self . draw_flow : for ( curr , prev ) in zip ( curr_pts , self . prev_pts ): c = tuple ( curr . astype ( int ) . ravel ()) p = tuple ( prev . astype ( int ) . ravel ()) cv2 . line ( frame , c , p , self . flow_color , 2 ) cv2 . circle ( frame , c , 3 , self . flow_color , - 1 ) update_prvs , coord_transformations = self . transformations_getter ( curr_pts , self . prev_pts , ) if update_prvs : self . gray_prvs = self . gray_next self . prev_pts = None self . prev_mask = mask return coord_transformations","title":"Camera Motion"},{"location":"reference/camera_motion/#camera-motion","text":"Camera motion stimation module.","title":"Camera Motion"},{"location":"reference/camera_motion/#norfair.camera_motion.CoordinatesTransformation","text":"Bases: ABC Abstract class representing a coordinate transformation. Detections' and tracked objects' coordinates can be interpreted in 2 reference: Relative : their position on the current frame, (0, 0) is top left Absolute : their position on an fixed space, (0, 0) is the top left of the first frame of the video. Therefore, coordinate transformation in this context is a class that can transform coordinates in one reference to another. Source code in norfair/camera_motion.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class CoordinatesTransformation ( ABC ): \"\"\" Abstract class representing a coordinate transformation. Detections' and tracked objects' coordinates can be interpreted in 2 reference: - _Relative_: their position on the current frame, (0, 0) is top left - _Absolute_: their position on an fixed space, (0, 0) is the top left of the first frame of the video. Therefore, coordinate transformation in this context is a class that can transform coordinates in one reference to another. \"\"\" @abstractmethod def abs_to_rel ( self , points : np . ndarray ) -> np . ndarray : pass @abstractmethod def rel_to_abs ( self , points : np . ndarray ) -> np . ndarray : pass","title":"CoordinatesTransformation"},{"location":"reference/camera_motion/#norfair.camera_motion.TransformationGetter","text":"Bases: ABC Abstract class representing a method for finding CoordinatesTransformation between 2 sets of points Source code in norfair/camera_motion.py 41 42 43 44 45 46 47 48 49 50 class TransformationGetter ( ABC ): \"\"\" Abstract class representing a method for finding CoordinatesTransformation between 2 sets of points \"\"\" @abstractmethod def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , CoordinatesTransformation ]: pass","title":"TransformationGetter"},{"location":"reference/camera_motion/#norfair.camera_motion.TranslationTransformation","text":"Bases: CoordinatesTransformation Coordinate transformation between points using a simple translation Parameters: Name Type Description Default movement_vector np . ndarray The vector representing the translation. required Source code in norfair/camera_motion.py 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class TranslationTransformation ( CoordinatesTransformation ): \"\"\" Coordinate transformation between points using a simple translation Parameters ---------- movement_vector : np.ndarray The vector representing the translation. \"\"\" def __init__ ( self , movement_vector ): self . movement_vector = movement_vector def abs_to_rel ( self , points : np . ndarray ): return points + self . movement_vector def rel_to_abs ( self , points : np . ndarray ): return points - self . movement_vector","title":"TranslationTransformation"},{"location":"reference/camera_motion/#norfair.camera_motion.TranslationTransformationGetter","text":"Bases: TransformationGetter Calculates TranslationTransformation between points. The camera movement is calculated as the mode of optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the translation, for this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters: Name Type Description Default bin_size float Before calculatin the mode, optiocal flow is bucketized into bins of this size. 0.2 proportion_points_used_threshold float Proportion of points that must be matched, otherwise the reference frame must be updated. 0.9 Source code in norfair/camera_motion.py 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 class TranslationTransformationGetter ( TransformationGetter ): \"\"\" Calculates TranslationTransformation between points. The camera movement is calculated as the mode of optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the translation, for this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters ---------- bin_size : float Before calculatin the mode, optiocal flow is bucketized into bins of this size. proportion_points_used_threshold: float Proportion of points that must be matched, otherwise the reference frame must be updated. \"\"\" def __init__ ( self , bin_size : float = 0.2 , proportion_points_used_threshold : float = 0.9 ) -> None : self . bin_size = bin_size self . proportion_points_used_threshold = proportion_points_used_threshold self . data = None def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , TranslationTransformation ]: # get flow flow = curr_pts - prev_pts # get mode flow = np . around ( flow / self . bin_size ) * self . bin_size unique_flows , counts = np . unique ( flow , axis = 0 , return_counts = True ) max_index = counts . argmax () proportion_points_used = counts [ max_index ] / len ( prev_pts ) update_prvs = proportion_points_used < self . proportion_points_used_threshold flow_mode = unique_flows [ max_index ] try : flow_mode += self . data except TypeError : pass if update_prvs : self . data = flow_mode return update_prvs , TranslationTransformation ( flow_mode )","title":"TranslationTransformationGetter"},{"location":"reference/camera_motion/#norfair.camera_motion.HomographyTransformation","text":"Bases: CoordinatesTransformation Coordinate transformation beweent points using an homography Parameters: Name Type Description Default homography_matrix np . ndarray The matrix representing the homography required Source code in norfair/camera_motion.py 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 class HomographyTransformation ( CoordinatesTransformation ): \"\"\" Coordinate transformation beweent points using an homography Parameters ---------- homography_matrix : np.ndarray The matrix representing the homography \"\"\" def __init__ ( self , homography_matrix : np . ndarray ): self . homography_matrix = homography_matrix self . inverse_homography_matrix = np . linalg . inv ( homography_matrix ) def abs_to_rel ( self , points : np . ndarray ): ones = np . ones (( len ( points ), 1 )) points_with_ones = np . hstack (( points , ones )) points_transformed = points_with_ones @ self . homography_matrix . T points_transformed = points_transformed / points_transformed [:, - 1 ] . reshape ( - 1 , 1 ) return points_transformed [:, : 2 ] def rel_to_abs ( self , points : np . ndarray ): ones = np . ones (( len ( points ), 1 )) points_with_ones = np . hstack (( points , ones )) points_transformed = points_with_ones @ self . inverse_homography_matrix . T points_transformed = points_transformed / points_transformed [:, - 1 ] . reshape ( - 1 , 1 ) return points_transformed [:, : 2 ]","title":"HomographyTransformation"},{"location":"reference/camera_motion/#norfair.camera_motion.HomographyTransformationGetter","text":"Bases: TransformationGetter Calculates HomographyTransformation between points. The camera movement is represented as an homography that matches the optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the homography, often resulting in the identity. For this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters: Name Type Description Default method Optional [ int ], optional One of openCV's method for finding homographies. Valid options are: [0, cv.RANSAC, cv.LMEDS, cv.RHO] , by default cv.RANSAC None ransac_reproj_threshold int , optional Maximum allowed reprojection error to treat a point pair as an inlier. More info in links below. 3 max_iters int , optional The maximum number of RANSAC iterations. More info in links below. 2000 confidence float , optional Confidence level, must be between 0 and 1. More info in links below. 0.995 proportion_points_used_threshold float , optional Proportion of points that must be matched, otherwise the reference frame must be updated. 0.9","title":"HomographyTransformationGetter"},{"location":"reference/camera_motion/#norfair.camera_motion.HomographyTransformationGetter--see-also","text":"opencv.findHomography Source code in norfair/camera_motion.py 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 class HomographyTransformationGetter ( TransformationGetter ): \"\"\" Calculates HomographyTransformation between points. The camera movement is represented as an homography that matches the optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the homography, often resulting in the identity. For this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters ---------- method : Optional[int], optional One of openCV's method for finding homographies. Valid options are: `[0, cv.RANSAC, cv.LMEDS, cv.RHO]`, by default `cv.RANSAC` ransac_reproj_threshold : int, optional Maximum allowed reprojection error to treat a point pair as an inlier. More info in links below. max_iters : int, optional The maximum number of RANSAC iterations. More info in links below. confidence : float, optional Confidence level, must be between 0 and 1. More info in links below. proportion_points_used_threshold : float, optional Proportion of points that must be matched, otherwise the reference frame must be updated. See Also -------- [opencv.findHomography](https://docs.opencv.org/3.4/d9/d0c/group__calib3d.html#ga4abc2ece9fab9398f2e560d53c8c9780) \"\"\" def __init__ ( self , method : Optional [ int ] = None , ransac_reproj_threshold : int = 3 , max_iters : int = 2000 , confidence : float = 0.995 , proportion_points_used_threshold : float = 0.9 , ) -> None : self . data = None if method is None : method = cv2 . RANSAC self . method = method self . ransac_reproj_threshold = ransac_reproj_threshold self . max_iters = max_iters self . confidence = confidence self . proportion_points_used_threshold = proportion_points_used_threshold def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , HomographyTransformation ]: homography_matrix , points_used = cv2 . findHomography ( prev_pts , curr_pts , method = self . method , ransacReprojThreshold = self . ransac_reproj_threshold , maxIters = self . max_iters , confidence = self . confidence , ) proportion_points_used = np . sum ( points_used ) / len ( points_used ) update_prvs = proportion_points_used < self . proportion_points_used_threshold try : homography_matrix = homography_matrix @ self . data except ( TypeError , ValueError ): pass if update_prvs : self . data = homography_matrix return update_prvs , HomographyTransformation ( homography_matrix )","title":"See Also"},{"location":"reference/camera_motion/#norfair.camera_motion.MotionEstimator","text":"Estimator of the motion of the camera. Uses optical flow to estimate the motion of the camera from frame to frame. The optical flow is calculated on a sample of strong points (corners). Parameters: Name Type Description Default max_points int , optional Maximum amount of points sampled. More points make the estimation process slower but more precise 200 min_distance int , optional Minimum distance between the sample points. 15 block_size int , optional Size of an average block when finding the corners. More info in links below. 3 transformations_getter TransformationGetter , optional An instance of TransformationGetter. By default HomographyTransformationGetter None draw_flow bool , optional Draws the optical flow on the frame for debugging. False flow_color Optional [ Tuple [ int , int , int ]], optional Color of the drawing, by default blue. None quality_level float , optional Parameter characterizing the minimal accepted quality of image corners. 0.01 Examples: >>> from norfair import Tracker , Video >>> from norfair.camera_motion MotionEstimator >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> motion_estimator = MotionEstimator () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> coord_transformation = motion_estimator . update ( frame ) >>> tracked_objects = tracker . update ( detections , coord_transformations = coord_transformation )","title":"MotionEstimator"},{"location":"reference/camera_motion/#norfair.camera_motion.MotionEstimator--see-also","text":"For more infor on how the points are sampled: OpenCV.goodFeaturesToTrack Source code in norfair/camera_motion.py 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 class MotionEstimator : \"\"\" Estimator of the motion of the camera. Uses optical flow to estimate the motion of the camera from frame to frame. The optical flow is calculated on a sample of strong points (corners). Parameters ---------- max_points : int, optional Maximum amount of points sampled. More points make the estimation process slower but more precise min_distance : int, optional Minimum distance between the sample points. block_size : int, optional Size of an average block when finding the corners. More info in links below. transformations_getter : TransformationGetter, optional An instance of TransformationGetter. By default [`HomographyTransformationGetter`][norfair.camera_motion.HomographyTransformationGetter] draw_flow : bool, optional Draws the optical flow on the frame for debugging. flow_color : Optional[Tuple[int, int, int]], optional Color of the drawing, by default blue. quality_level : float, optional Parameter characterizing the minimal accepted quality of image corners. Examples -------- >>> from norfair import Tracker, Video >>> from norfair.camera_motion MotionEstimator >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> motion_estimator = MotionEstimator() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> coord_transformation = motion_estimator.update(frame) >>> tracked_objects = tracker.update(detections, coord_transformations=coord_transformation) See Also -------- For more infor on how the points are sampled: [OpenCV.goodFeaturesToTrack](https://docs.opencv.org/3.4/dd/d1a/group__imgproc__feature.html#ga1d6bb77486c8f92d79c8793ad995d541) \"\"\" def __init__ ( self , max_points : int = 200 , min_distance : int = 15 , block_size : int = 3 , transformations_getter : TransformationGetter = None , draw_flow : bool = False , flow_color : Optional [ Tuple [ int , int , int ]] = None , quality_level : float = 0.01 , ): self . max_points = max_points self . min_distance = min_distance self . block_size = block_size self . draw_flow = draw_flow if self . draw_flow and flow_color is None : flow_color = [ 0 , 0 , 100 ] self . flow_color = flow_color self . gray_prvs = None self . prev_pts = None if transformations_getter is None : transformations_getter = HomographyTransformationGetter () self . transformations_getter = transformations_getter self . prev_mask = None self . gray_next = None self . quality_level = quality_level def update ( self , frame : np . ndarray , mask : np . ndarray = None ) -> CoordinatesTransformation : \"\"\" Estimate camera motion for each frame Parameters ---------- frame : np.ndarray The frame. mask : np.ndarray, optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape `(frame.shape[0], frame.shape[1])`, dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. Returns ------- CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. \"\"\" self . gray_next = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2GRAY ) if self . gray_prvs is None : self . gray_prvs = self . gray_next self . prev_mask = mask curr_pts , self . prev_pts = _get_sparse_flow ( self . gray_next , self . gray_prvs , self . prev_pts , self . max_points , self . min_distance , self . block_size , self . prev_mask , quality_level = self . quality_level , ) if self . draw_flow : for ( curr , prev ) in zip ( curr_pts , self . prev_pts ): c = tuple ( curr . astype ( int ) . ravel ()) p = tuple ( prev . astype ( int ) . ravel ()) cv2 . line ( frame , c , p , self . flow_color , 2 ) cv2 . circle ( frame , c , 3 , self . flow_color , - 1 ) update_prvs , coord_transformations = self . transformations_getter ( curr_pts , self . prev_pts , ) if update_prvs : self . gray_prvs = self . gray_next self . prev_pts = None self . prev_mask = mask return coord_transformations","title":"See Also"},{"location":"reference/camera_motion/#norfair.camera_motion.MotionEstimator.update","text":"Estimate camera motion for each frame Parameters: Name Type Description Default frame np . ndarray The frame. required mask np . ndarray , optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape (frame.shape[0], frame.shape[1]) , dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. None Returns: Type Description CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. Source code in norfair/camera_motion.py 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 def update ( self , frame : np . ndarray , mask : np . ndarray = None ) -> CoordinatesTransformation : \"\"\" Estimate camera motion for each frame Parameters ---------- frame : np.ndarray The frame. mask : np.ndarray, optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape `(frame.shape[0], frame.shape[1])`, dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. Returns ------- CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. \"\"\" self . gray_next = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2GRAY ) if self . gray_prvs is None : self . gray_prvs = self . gray_next self . prev_mask = mask curr_pts , self . prev_pts = _get_sparse_flow ( self . gray_next , self . gray_prvs , self . prev_pts , self . max_points , self . min_distance , self . block_size , self . prev_mask , quality_level = self . quality_level , ) if self . draw_flow : for ( curr , prev ) in zip ( curr_pts , self . prev_pts ): c = tuple ( curr . astype ( int ) . ravel ()) p = tuple ( prev . astype ( int ) . ravel ()) cv2 . line ( frame , c , p , self . flow_color , 2 ) cv2 . circle ( frame , c , 3 , self . flow_color , - 1 ) update_prvs , coord_transformations = self . transformations_getter ( curr_pts , self . prev_pts , ) if update_prvs : self . gray_prvs = self . gray_next self . prev_pts = None self . prev_mask = mask return coord_transformations","title":"update()"},{"location":"reference/distances/","text":"Distances # Predefined distances Distance # Bases: ABC Abstract class representing a distance. Subclasses must implement the method get_distances Source code in norfair/distances.py 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Distance ( ABC ): \"\"\" Abstract class representing a distance. Subclasses must implement the method `get_distances` \"\"\" @abstractmethod def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" get_distances ( objects , candidates ) abstractmethod # Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @abstractmethod def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" ScalarDistance # Bases: Distance ScalarDistance class represents a distance that is calculated pointwise. Parameters: Name Type Description Default 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 . It has to return a float with the distance it calculates. required Source code in norfair/distances.py 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 class ScalarDistance ( Distance ): \"\"\" ScalarDistance class represents a distance that is calculated pointwise. Parameters ---------- 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. \"\"\" def __init__ ( self , distance_function : Union [ Callable [[ \"Detection\" , \"TrackedObject\" ], float ], Callable [[ \"TrackedObject\" , \"TrackedObject\" ], float ], ], ): self . distance_function = distance_function def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix for c , candidate in enumerate ( candidates ): for o , obj in enumerate ( objects ): if candidate . label != obj . label : if ( candidate . label is None ) or ( obj . label is None ): print ( \" \\n There are detections with and without label!\" ) continue distance = self . distance_function ( candidate , obj ) distance_matrix [ c , o ] = distance return distance_matrix get_distances ( objects , candidates ) # Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix for c , candidate in enumerate ( candidates ): for o , obj in enumerate ( objects ): if candidate . label != obj . label : if ( candidate . label is None ) or ( obj . label is None ): print ( \" \\n There are detections with and without label!\" ) continue distance = self . distance_function ( candidate , obj ) distance_matrix [ c , o ] = distance return distance_matrix VectorizedDistance # Bases: Distance 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: Name Type Description Default distance_function Callable [[ np . ndarray , np . ndarray ], np . ndarray ] 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. required Source code in norfair/distances.py 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 class VectorizedDistance ( Distance ): \"\"\" 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 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. \"\"\" def __init__ ( self , distance_function : Callable [[ np . ndarray , np . ndarray ], np . ndarray ], ): self . distance_function = distance_function def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix object_labels = np . array ([ o . label for o in objects ]) . astype ( str ) candidate_labels = np . array ([ c . label for c in candidates ]) . astype ( str ) # iterate over labels that are present both in objects and detections for label in np . intersect1d ( np . unique ( object_labels ), np . unique ( candidate_labels ) ): # generate masks of the subset of object and detections for this label obj_mask = object_labels == label cand_mask = candidate_labels == label stacked_objects = [] for o in objects : if str ( o . label ) == label : stacked_objects . append ( o . estimate . ravel ()) stacked_objects = np . stack ( stacked_objects ) stacked_candidates = [] for c in candidates : if str ( c . label ) == label : if \"Detection\" in str ( type ( c )): stacked_candidates . append ( c . points . ravel ()) else : stacked_candidates . append ( c . estimate . ravel ()) stacked_candidates = np . stack ( stacked_candidates ) # calculate the pairwise distances between objects and candidates with this label # and assign the result to the correct positions inside distance_matrix distance_matrix [ np . ix_ ( cand_mask , obj_mask )] = self . _compute_distance ( stacked_candidates , stacked_objects ) return distance_matrix def _compute_distance ( self , stacked_candidates : np . ndarray , stacked_objects : np . ndarray ) -> 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 np.ndarray containing a stack of candidates to be compared with the stacked_objects. stacked_objects : np.ndarray np.ndarray containing a stack of objects to be compared with the stacked_objects. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" return self . distance_function ( stacked_candidates , stacked_objects ) get_distances ( objects , candidates ) # Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix object_labels = np . array ([ o . label for o in objects ]) . astype ( str ) candidate_labels = np . array ([ c . label for c in candidates ]) . astype ( str ) # iterate over labels that are present both in objects and detections for label in np . intersect1d ( np . unique ( object_labels ), np . unique ( candidate_labels ) ): # generate masks of the subset of object and detections for this label obj_mask = object_labels == label cand_mask = candidate_labels == label stacked_objects = [] for o in objects : if str ( o . label ) == label : stacked_objects . append ( o . estimate . ravel ()) stacked_objects = np . stack ( stacked_objects ) stacked_candidates = [] for c in candidates : if str ( c . label ) == label : if \"Detection\" in str ( type ( c )): stacked_candidates . append ( c . points . ravel ()) else : stacked_candidates . append ( c . estimate . ravel ()) stacked_candidates = np . stack ( stacked_candidates ) # calculate the pairwise distances between objects and candidates with this label # and assign the result to the correct positions inside distance_matrix distance_matrix [ np . ix_ ( cand_mask , obj_mask )] = self . _compute_distance ( stacked_candidates , stacked_objects ) return distance_matrix ScipyDistance # Bases: VectorizedDistance 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 . Parameters: Name Type Description Default metric str , optional Defines the specific Scipy metric to use to calculate the pairwise distances between new candidates and objects. 'euclidean' Other kwargs are passed through to cdist See Also # scipy.spatial.distance.cdist Source code in norfair/distances.py 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 class ScipyDistance ( VectorizedDistance ): \"\"\" 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`. Parameters ---------- metric : str, optional Defines the specific Scipy metric to use to calculate the pairwise distances between new candidates and objects. Other kwargs are passed through to cdist See Also -------- [`scipy.spatial.distance.cdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html) \"\"\" def __init__ ( self , metric : str = \"euclidean\" , ** kwargs ): self . metric = metric super () . __init__ ( distance_function = partial ( cdist , metric = self . metric , ** kwargs )) frobenius ( detection , tracked_object ) # Frobernius norm on the difference of the points in detection and the estimates in tracked_object. The Frobenius distance and norm are given by: \\[ d_f(a, b) = ||a - b||_F \\] \\[ ||A||_F = [\\sum_{i,j} abs(a_{i,j})^2]^{1/2} \\] Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject A tracked object. required Returns: Type Description float The distance. See Also # np.linalg.norm Source code in norfair/distances.py 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 def frobenius ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Frobernius norm on the difference of the points in detection and the estimates in tracked_object. The Frobenius distance and norm are given by: $$ d_f(a, b) = ||a - b||_F $$ $$ ||A||_F = [\\\\sum_{i,j} abs(a_{i,j})^2]^{1/2} $$ Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject A tracked object. Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate ) mean_euclidean ( detection , tracked_object ) # Average euclidean distance between the points in detection and estimates in tracked_object. \\[ d(a, b) = \\frac{\\sum_{i=0}^N ||a_i - b_i||_2}{N} \\] Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject A tracked object required Returns: Type Description float The distance. See Also # np.linalg.norm Source code in norfair/distances.py 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 def mean_euclidean ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Average euclidean distance between the points in detection and estimates in tracked_object. $$ d(a, b) = \\\\frac{\\\\sum_{i=0}^N ||a_i - b_i||_2}{N} $$ Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject A tracked object Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate , axis = 1 ) . mean () mean_manhattan ( detection , tracked_object ) # Average manhattan distance between the points in detection and the estimates in tracked_object Given by: \\[ d(a, b) = \\frac{\\sum_{i=0}^N ||a_i - b_i||_1}{N} \\] Where \\(||a||_1\\) is the manhattan norm. Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject a tracked object. required Returns: Type Description float The distance. See Also # np.linalg.norm Source code in norfair/distances.py 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 def mean_manhattan ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Average manhattan distance between the points in detection and the estimates in tracked_object Given by: $$ d(a, b) = \\\\frac{\\\\sum_{i=0}^N ||a_i - b_i||_1}{N} $$ Where $||a||_1$ is the manhattan norm. Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject a tracked object. Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate , ord = 1 , axis = 1 ) . mean () iou ( candidates , objects ) # Calculate IoU between two sets of bounding boxes. Both sets of boxes are expected to be in [x_min, y_min, x_max, y_max] format. Normal IoU is 1 when the boxes are the same and 0 when they don't overlap, to transform that into a distance that makes sense we return 1 - iou . Parameters: Name Type Description Default candidates numpy . ndarray (N, 4) numpy.ndarray containing candidates bounding boxes. required objects numpy . ndarray (K, 4) numpy.ndarray containing objects bounding boxes. required Returns: Type Description numpy . ndarray (N, K) numpy.ndarray of 1 - iou between candidates and objects. Source code in norfair/distances.py 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 def iou ( candidates : np . ndarray , objects : np . ndarray ) -> np . ndarray : \"\"\" Calculate IoU between two sets of bounding boxes. Both sets of boxes are expected to be in `[x_min, y_min, x_max, y_max]` format. Normal IoU is 1 when the boxes are the same and 0 when they don't overlap, to transform that into a distance that makes sense we return `1 - iou`. Parameters ---------- candidates : numpy.ndarray (N, 4) numpy.ndarray containing candidates bounding boxes. objects : numpy.ndarray (K, 4) numpy.ndarray containing objects bounding boxes. Returns ------- numpy.ndarray (N, K) numpy.ndarray of `1 - iou` between candidates and objects. \"\"\" _validate_bboxes ( candidates ) area_candidates = _boxes_area ( candidates . T ) area_objects = _boxes_area ( objects . T ) top_left = np . maximum ( candidates [:, None , : 2 ], objects [:, : 2 ]) bottom_right = np . minimum ( candidates [:, None , 2 :], objects [:, 2 :]) area_intersection = np . prod ( np . clip ( bottom_right - top_left , a_min = 0 , a_max = None ), 2 ) return 1 - area_intersection / ( area_candidates [:, None ] + area_objects - area_intersection ) get_distance_by_name ( name ) # Select a distance by name. Parameters: Name Type Description Default name str A string defining the metric to get. required Returns: Type Description Distance The distance object. Source code in norfair/distances.py 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 def get_distance_by_name ( name : str ) -> Distance : \"\"\" Select a distance by name. 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\" f \" such as { AVAILABLE_VECTORIZED_DISTANCES } .\" ) distance = _SCALAR_DISTANCE_FUNCTIONS [ name ] distance_function = ScalarDistance ( distance ) elif name in _SCIPY_DISTANCE_FUNCTIONS : distance_function = ScipyDistance ( name ) elif name in _VECTORIZED_DISTANCE_FUNCTIONS : if name == \"iou_opt\" : warning ( \"iou_opt is deprecated, use iou instead\" ) distance = _VECTORIZED_DISTANCE_FUNCTIONS [ name ] distance_function = VectorizedDistance ( distance ) else : raise ValueError ( f \"Invalid distance ' { name } ', expecting one of\" f \" { list ( _SCALAR_DISTANCE_FUNCTIONS . keys ()) + AVAILABLE_VECTORIZED_DISTANCES } \" ) return distance_function create_keypoints_voting_distance ( keypoint_distance_threshold , detection_threshold ) # Construct a keypoint voting distance function configured with the thresholds. Count how many points in a detection match the with a tracked_object. A match is considered when distance between the points is < keypoint_distance_threshold and the score of the last_detection of the tracked_object is > detection_threshold . Notice the if multiple points are tracked, the ith point in detection can only match the ith point in the tracked object. Distance is 1 if no point matches and approximates 0 as more points are matched. Parameters: Name Type Description Default keypoint_distance_threshold float Points closer than this threshold are considered a match. required detection_threshold float Detections and objects with score lower than this threshold are ignored. required Returns: Type Description Callable The distance funtion that must be passed to the Tracker. Source code in norfair/distances.py 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 def create_keypoints_voting_distance ( keypoint_distance_threshold : float , detection_threshold : float ) -> Callable [[ \"Detection\" , \"TrackedObject\" ], float ]: \"\"\" Construct a keypoint voting distance function configured with the thresholds. Count how many points in a detection match the with a tracked_object. A match is considered when distance between the points is < `keypoint_distance_threshold` and the score of the last_detection of the tracked_object is > `detection_threshold`. Notice the if multiple points are tracked, the ith point in detection can only match the ith point in the tracked object. Distance is 1 if no point matches and approximates 0 as more points are matched. Parameters ---------- keypoint_distance_threshold: float Points closer than this threshold are considered a match. detection_threshold: float Detections and objects with score lower than this threshold are ignored. Returns ------- Callable The distance funtion that must be passed to the Tracker. \"\"\" def keypoints_voting_distance ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : distances = np . linalg . norm ( detection . points - tracked_object . estimate , axis = 1 ) match_num = np . count_nonzero ( ( distances < keypoint_distance_threshold ) * ( detection . scores > detection_threshold ) * ( tracked_object . last_detection . scores > detection_threshold ) ) return 1 / ( 1 + match_num ) return keypoints_voting_distance create_normalized_mean_euclidean_distance ( height , width ) # Construct a normalized mean euclidean distance function configured with the max height and width. The result distance is bound to [0, 1] where 1 indicates oposite corners of the image. Parameters: Name Type Description Default height int Height of the image. required width int Width of the image. required Returns: Type Description Callable The distance funtion that must be passed to the Tracker. Source code in norfair/distances.py 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 def create_normalized_mean_euclidean_distance ( height : int , width : int ) -> Callable [[ \"Detection\" , \"TrackedObject\" ], float ]: \"\"\" Construct a normalized mean euclidean distance function configured with the max height and width. The result distance is bound to [0, 1] where 1 indicates oposite corners of the image. Parameters ---------- height: int Height of the image. width: int Width of the image. Returns ------- Callable The distance funtion that must be passed to the Tracker. \"\"\" def normalized__mean_euclidean_distance ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\"Normalized mean euclidean distance\"\"\" # calculate distances and normalized it by width and height difference = ( detection . points - tracked_object . estimate ) . astype ( float ) difference [:, 0 ] /= width difference [:, 1 ] /= height # calculate eucledean distance and average return np . linalg . norm ( difference , axis = 1 ) . mean () return normalized__mean_euclidean_distance","title":"Distances"},{"location":"reference/distances/#distances","text":"Predefined distances","title":"Distances"},{"location":"reference/distances/#norfair.distances.Distance","text":"Bases: ABC Abstract class representing a distance. Subclasses must implement the method get_distances Source code in norfair/distances.py 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Distance ( ABC ): \"\"\" Abstract class representing a distance. Subclasses must implement the method `get_distances` \"\"\" @abstractmethod def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\"","title":"Distance"},{"location":"reference/distances/#norfair.distances.Distance.get_distances","text":"Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @abstractmethod def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\"","title":"get_distances()"},{"location":"reference/distances/#norfair.distances.ScalarDistance","text":"Bases: Distance ScalarDistance class represents a distance that is calculated pointwise. Parameters: Name Type Description Default 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 . It has to return a float with the distance it calculates. required Source code in norfair/distances.py 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 class ScalarDistance ( Distance ): \"\"\" ScalarDistance class represents a distance that is calculated pointwise. Parameters ---------- 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. \"\"\" def __init__ ( self , distance_function : Union [ Callable [[ \"Detection\" , \"TrackedObject\" ], float ], Callable [[ \"TrackedObject\" , \"TrackedObject\" ], float ], ], ): self . distance_function = distance_function def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix for c , candidate in enumerate ( candidates ): for o , obj in enumerate ( objects ): if candidate . label != obj . label : if ( candidate . label is None ) or ( obj . label is None ): print ( \" \\n There are detections with and without label!\" ) continue distance = self . distance_function ( candidate , obj ) distance_matrix [ c , o ] = distance return distance_matrix","title":"ScalarDistance"},{"location":"reference/distances/#norfair.distances.ScalarDistance.get_distances","text":"Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix for c , candidate in enumerate ( candidates ): for o , obj in enumerate ( objects ): if candidate . label != obj . label : if ( candidate . label is None ) or ( obj . label is None ): print ( \" \\n There are detections with and without label!\" ) continue distance = self . distance_function ( candidate , obj ) distance_matrix [ c , o ] = distance return distance_matrix","title":"get_distances()"},{"location":"reference/distances/#norfair.distances.VectorizedDistance","text":"Bases: Distance 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: Name Type Description Default distance_function Callable [[ np . ndarray , np . ndarray ], np . ndarray ] 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. required Source code in norfair/distances.py 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 class VectorizedDistance ( Distance ): \"\"\" 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 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. \"\"\" def __init__ ( self , distance_function : Callable [[ np . ndarray , np . ndarray ], np . ndarray ], ): self . distance_function = distance_function def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix object_labels = np . array ([ o . label for o in objects ]) . astype ( str ) candidate_labels = np . array ([ c . label for c in candidates ]) . astype ( str ) # iterate over labels that are present both in objects and detections for label in np . intersect1d ( np . unique ( object_labels ), np . unique ( candidate_labels ) ): # generate masks of the subset of object and detections for this label obj_mask = object_labels == label cand_mask = candidate_labels == label stacked_objects = [] for o in objects : if str ( o . label ) == label : stacked_objects . append ( o . estimate . ravel ()) stacked_objects = np . stack ( stacked_objects ) stacked_candidates = [] for c in candidates : if str ( c . label ) == label : if \"Detection\" in str ( type ( c )): stacked_candidates . append ( c . points . ravel ()) else : stacked_candidates . append ( c . estimate . ravel ()) stacked_candidates = np . stack ( stacked_candidates ) # calculate the pairwise distances between objects and candidates with this label # and assign the result to the correct positions inside distance_matrix distance_matrix [ np . ix_ ( cand_mask , obj_mask )] = self . _compute_distance ( stacked_candidates , stacked_objects ) return distance_matrix def _compute_distance ( self , stacked_candidates : np . ndarray , stacked_objects : np . ndarray ) -> 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 np.ndarray containing a stack of candidates to be compared with the stacked_objects. stacked_objects : np.ndarray np.ndarray containing a stack of objects to be compared with the stacked_objects. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" return self . distance_function ( stacked_candidates , stacked_objects )","title":"VectorizedDistance"},{"location":"reference/distances/#norfair.distances.VectorizedDistance.get_distances","text":"Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix object_labels = np . array ([ o . label for o in objects ]) . astype ( str ) candidate_labels = np . array ([ c . label for c in candidates ]) . astype ( str ) # iterate over labels that are present both in objects and detections for label in np . intersect1d ( np . unique ( object_labels ), np . unique ( candidate_labels ) ): # generate masks of the subset of object and detections for this label obj_mask = object_labels == label cand_mask = candidate_labels == label stacked_objects = [] for o in objects : if str ( o . label ) == label : stacked_objects . append ( o . estimate . ravel ()) stacked_objects = np . stack ( stacked_objects ) stacked_candidates = [] for c in candidates : if str ( c . label ) == label : if \"Detection\" in str ( type ( c )): stacked_candidates . append ( c . points . ravel ()) else : stacked_candidates . append ( c . estimate . ravel ()) stacked_candidates = np . stack ( stacked_candidates ) # calculate the pairwise distances between objects and candidates with this label # and assign the result to the correct positions inside distance_matrix distance_matrix [ np . ix_ ( cand_mask , obj_mask )] = self . _compute_distance ( stacked_candidates , stacked_objects ) return distance_matrix","title":"get_distances()"},{"location":"reference/distances/#norfair.distances.ScipyDistance","text":"Bases: VectorizedDistance 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 . Parameters: Name Type Description Default metric str , optional Defines the specific Scipy metric to use to calculate the pairwise distances between new candidates and objects. 'euclidean' Other kwargs are passed through to cdist","title":"ScipyDistance"},{"location":"reference/distances/#norfair.distances.ScipyDistance--see-also","text":"scipy.spatial.distance.cdist Source code in norfair/distances.py 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 class ScipyDistance ( VectorizedDistance ): \"\"\" 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`. Parameters ---------- metric : str, optional Defines the specific Scipy metric to use to calculate the pairwise distances between new candidates and objects. Other kwargs are passed through to cdist See Also -------- [`scipy.spatial.distance.cdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html) \"\"\" def __init__ ( self , metric : str = \"euclidean\" , ** kwargs ): self . metric = metric super () . __init__ ( distance_function = partial ( cdist , metric = self . metric , ** kwargs ))","title":"See Also"},{"location":"reference/distances/#norfair.distances.frobenius","text":"Frobernius norm on the difference of the points in detection and the estimates in tracked_object. The Frobenius distance and norm are given by: \\[ d_f(a, b) = ||a - b||_F \\] \\[ ||A||_F = [\\sum_{i,j} abs(a_{i,j})^2]^{1/2} \\] Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject A tracked object. required Returns: Type Description float The distance.","title":"frobenius()"},{"location":"reference/distances/#norfair.distances.frobenius--see-also","text":"np.linalg.norm Source code in norfair/distances.py 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 def frobenius ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Frobernius norm on the difference of the points in detection and the estimates in tracked_object. The Frobenius distance and norm are given by: $$ d_f(a, b) = ||a - b||_F $$ $$ ||A||_F = [\\\\sum_{i,j} abs(a_{i,j})^2]^{1/2} $$ Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject A tracked object. Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate )","title":"See Also"},{"location":"reference/distances/#norfair.distances.mean_euclidean","text":"Average euclidean distance between the points in detection and estimates in tracked_object. \\[ d(a, b) = \\frac{\\sum_{i=0}^N ||a_i - b_i||_2}{N} \\] Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject A tracked object required Returns: Type Description float The distance.","title":"mean_euclidean()"},{"location":"reference/distances/#norfair.distances.mean_euclidean--see-also","text":"np.linalg.norm Source code in norfair/distances.py 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 def mean_euclidean ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Average euclidean distance between the points in detection and estimates in tracked_object. $$ d(a, b) = \\\\frac{\\\\sum_{i=0}^N ||a_i - b_i||_2}{N} $$ Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject A tracked object Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate , axis = 1 ) . mean ()","title":"See Also"},{"location":"reference/distances/#norfair.distances.mean_manhattan","text":"Average manhattan distance between the points in detection and the estimates in tracked_object Given by: \\[ d(a, b) = \\frac{\\sum_{i=0}^N ||a_i - b_i||_1}{N} \\] Where \\(||a||_1\\) is the manhattan norm. Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject a tracked object. required Returns: Type Description float The distance.","title":"mean_manhattan()"},{"location":"reference/distances/#norfair.distances.mean_manhattan--see-also","text":"np.linalg.norm Source code in norfair/distances.py 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 def mean_manhattan ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Average manhattan distance between the points in detection and the estimates in tracked_object Given by: $$ d(a, b) = \\\\frac{\\\\sum_{i=0}^N ||a_i - b_i||_1}{N} $$ Where $||a||_1$ is the manhattan norm. Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject a tracked object. Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate , ord = 1 , axis = 1 ) . mean ()","title":"See Also"},{"location":"reference/distances/#norfair.distances.iou","text":"Calculate IoU between two sets of bounding boxes. Both sets of boxes are expected to be in [x_min, y_min, x_max, y_max] format. Normal IoU is 1 when the boxes are the same and 0 when they don't overlap, to transform that into a distance that makes sense we return 1 - iou . Parameters: Name Type Description Default candidates numpy . ndarray (N, 4) numpy.ndarray containing candidates bounding boxes. required objects numpy . ndarray (K, 4) numpy.ndarray containing objects bounding boxes. required Returns: Type Description numpy . ndarray (N, K) numpy.ndarray of 1 - iou between candidates and objects. Source code in norfair/distances.py 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 def iou ( candidates : np . ndarray , objects : np . ndarray ) -> np . ndarray : \"\"\" Calculate IoU between two sets of bounding boxes. Both sets of boxes are expected to be in `[x_min, y_min, x_max, y_max]` format. Normal IoU is 1 when the boxes are the same and 0 when they don't overlap, to transform that into a distance that makes sense we return `1 - iou`. Parameters ---------- candidates : numpy.ndarray (N, 4) numpy.ndarray containing candidates bounding boxes. objects : numpy.ndarray (K, 4) numpy.ndarray containing objects bounding boxes. Returns ------- numpy.ndarray (N, K) numpy.ndarray of `1 - iou` between candidates and objects. \"\"\" _validate_bboxes ( candidates ) area_candidates = _boxes_area ( candidates . T ) area_objects = _boxes_area ( objects . T ) top_left = np . maximum ( candidates [:, None , : 2 ], objects [:, : 2 ]) bottom_right = np . minimum ( candidates [:, None , 2 :], objects [:, 2 :]) area_intersection = np . prod ( np . clip ( bottom_right - top_left , a_min = 0 , a_max = None ), 2 ) return 1 - area_intersection / ( area_candidates [:, None ] + area_objects - area_intersection )","title":"iou()"},{"location":"reference/distances/#norfair.distances.get_distance_by_name","text":"Select a distance by name. Parameters: Name Type Description Default name str A string defining the metric to get. required Returns: Type Description Distance The distance object. Source code in norfair/distances.py 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 def get_distance_by_name ( name : str ) -> Distance : \"\"\" Select a distance by name. 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\" f \" such as { AVAILABLE_VECTORIZED_DISTANCES } .\" ) distance = _SCALAR_DISTANCE_FUNCTIONS [ name ] distance_function = ScalarDistance ( distance ) elif name in _SCIPY_DISTANCE_FUNCTIONS : distance_function = ScipyDistance ( name ) elif name in _VECTORIZED_DISTANCE_FUNCTIONS : if name == \"iou_opt\" : warning ( \"iou_opt is deprecated, use iou instead\" ) distance = _VECTORIZED_DISTANCE_FUNCTIONS [ name ] distance_function = VectorizedDistance ( distance ) else : raise ValueError ( f \"Invalid distance ' { name } ', expecting one of\" f \" { list ( _SCALAR_DISTANCE_FUNCTIONS . keys ()) + AVAILABLE_VECTORIZED_DISTANCES } \" ) return distance_function","title":"get_distance_by_name()"},{"location":"reference/distances/#norfair.distances.create_keypoints_voting_distance","text":"Construct a keypoint voting distance function configured with the thresholds. Count how many points in a detection match the with a tracked_object. A match is considered when distance between the points is < keypoint_distance_threshold and the score of the last_detection of the tracked_object is > detection_threshold . Notice the if multiple points are tracked, the ith point in detection can only match the ith point in the tracked object. Distance is 1 if no point matches and approximates 0 as more points are matched. Parameters: Name Type Description Default keypoint_distance_threshold float Points closer than this threshold are considered a match. required detection_threshold float Detections and objects with score lower than this threshold are ignored. required Returns: Type Description Callable The distance funtion that must be passed to the Tracker. Source code in norfair/distances.py 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 def create_keypoints_voting_distance ( keypoint_distance_threshold : float , detection_threshold : float ) -> Callable [[ \"Detection\" , \"TrackedObject\" ], float ]: \"\"\" Construct a keypoint voting distance function configured with the thresholds. Count how many points in a detection match the with a tracked_object. A match is considered when distance between the points is < `keypoint_distance_threshold` and the score of the last_detection of the tracked_object is > `detection_threshold`. Notice the if multiple points are tracked, the ith point in detection can only match the ith point in the tracked object. Distance is 1 if no point matches and approximates 0 as more points are matched. Parameters ---------- keypoint_distance_threshold: float Points closer than this threshold are considered a match. detection_threshold: float Detections and objects with score lower than this threshold are ignored. Returns ------- Callable The distance funtion that must be passed to the Tracker. \"\"\" def keypoints_voting_distance ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : distances = np . linalg . norm ( detection . points - tracked_object . estimate , axis = 1 ) match_num = np . count_nonzero ( ( distances < keypoint_distance_threshold ) * ( detection . scores > detection_threshold ) * ( tracked_object . last_detection . scores > detection_threshold ) ) return 1 / ( 1 + match_num ) return keypoints_voting_distance","title":"create_keypoints_voting_distance()"},{"location":"reference/distances/#norfair.distances.create_normalized_mean_euclidean_distance","text":"Construct a normalized mean euclidean distance function configured with the max height and width. The result distance is bound to [0, 1] where 1 indicates oposite corners of the image. Parameters: Name Type Description Default height int Height of the image. required width int Width of the image. required Returns: Type Description Callable The distance funtion that must be passed to the Tracker. Source code in norfair/distances.py 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 def create_normalized_mean_euclidean_distance ( height : int , width : int ) -> Callable [[ \"Detection\" , \"TrackedObject\" ], float ]: \"\"\" Construct a normalized mean euclidean distance function configured with the max height and width. The result distance is bound to [0, 1] where 1 indicates oposite corners of the image. Parameters ---------- height: int Height of the image. width: int Width of the image. Returns ------- Callable The distance funtion that must be passed to the Tracker. \"\"\" def normalized__mean_euclidean_distance ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\"Normalized mean euclidean distance\"\"\" # calculate distances and normalized it by width and height difference = ( detection . points - tracked_object . estimate ) . astype ( float ) difference [:, 0 ] /= width difference [:, 1 ] /= height # calculate eucledean distance and average return np . linalg . norm ( difference , axis = 1 ) . mean () return normalized__mean_euclidean_distance","title":"create_normalized_mean_euclidean_distance()"},{"location":"reference/drawing/","text":"Drawing # Collection of drawing functions draw_points # draw_points ( frame , drawables = None , radius = None , thickness = None , color = 'by_id' , color_by_label = None , draw_labels = True , text_size = None , draw_ids = True , draw_points = True , text_thickness = None , text_color = None , hide_dead_points = True , detections = None , label_size = None , draw_scores = False ) # Draw the points included in a list of Detections or TrackedObjects. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. Modified in place. required drawables Union [ Sequence [ Detection ], Sequence [ TrackedObject ]], optional List of objects to draw, Detections and TrackedObjects are accepted. None radius Optional [ int ], optional Radius of the circles representing each point. By default a sensible value is picked considering the frame size. None thickness Optional [ int ], optional Thickness or width of the line. None color ColorLike , optional This parameter can take: A color as a tuple of ints describing the BGR (0, 0, 255) A 6-digit hex string \"#FF0000\" One of the defined color names \"red\" A string defining the strategy to choose colors from the Palette: based on the id of the objects \"by_id\" based on the label of the objects \"by_label\" random choice \"random\" If using by_id or by_label strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). 'by_id' color_by_label bool , optional Deprecated . set color=\"by_label\" . None draw_labels bool , optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. True draw_scores bool , optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False text_size Optional [ int ], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. None draw_ids bool , optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. True draw_points bool , optional Set to False to hide the points and just draw the text. True text_thickness Optional [ int ], optional Thickness of the font. By default it's scaled with the text_size . None text_color Optional [ ColorLike ], optional Color of the text. By default the same color as the box is used. None hide_dead_points bool , optional Set this param to False to always draw all points, even the ones considered \"dead\". A point is \"dead\" when the corresponding value of TrackedObject.live_points is set to False. If all objects are dead the object is not drawn. All points of a detection are considered to be alive. True detections Sequence [ Detection ], optional Deprecated . use drawables. None label_size Optional [ int ], optional Deprecated . text_size. None Returns: Type Description np . ndarray The resulting frame. Source code in norfair/drawing/draw_points.py 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 def draw_points ( frame : np . ndarray , drawables : Union [ Sequence [ Detection ], Sequence [ TrackedObject ]] = None , radius : Optional [ int ] = None , thickness : Optional [ int ] = None , color : ColorLike = \"by_id\" , color_by_label : bool = None , # deprecated draw_labels : bool = True , text_size : Optional [ int ] = None , draw_ids : bool = True , draw_points : bool = True , # pylint: disable=redefined-outer-name text_thickness : Optional [ int ] = None , text_color : Optional [ ColorLike ] = None , hide_dead_points : bool = True , detections : Sequence [ \"Detection\" ] = None , # deprecated label_size : Optional [ int ] = None , # deprecated draw_scores : bool = False , ) -> np . ndarray : \"\"\" Draw the points included in a list of Detections or TrackedObjects. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. Modified in place. drawables : Union[Sequence[Detection], Sequence[TrackedObject]], optional List of objects to draw, Detections and TrackedObjects are accepted. radius : Optional[int], optional Radius of the circles representing each point. By default a sensible value is picked considering the frame size. thickness : Optional[int], optional Thickness or width of the line. color : ColorLike, optional This parameter can take: 1. A color as a tuple of ints describing the BGR `(0, 0, 255)` 2. A 6-digit hex string `\"#FF0000\"` 3. One of the defined color names `\"red\"` 4. A string defining the strategy to choose colors from the Palette: 1. based on the id of the objects `\"by_id\"` 2. based on the label of the objects `\"by_label\"` 3. random choice `\"random\"` If using `by_id` or `by_label` strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). color_by_label : bool, optional **Deprecated**. set `color=\"by_label\"`. draw_labels : bool, optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. draw_scores : bool, optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. text_size : Optional[int], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. draw_ids : bool, optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. draw_points : bool, optional Set to False to hide the points and just draw the text. text_thickness : Optional[int], optional Thickness of the font. By default it's scaled with the `text_size`. text_color : Optional[ColorLike], optional Color of the text. By default the same color as the box is used. hide_dead_points : bool, optional Set this param to False to always draw all points, even the ones considered \"dead\". A point is \"dead\" when the corresponding value of `TrackedObject.live_points` is set to False. If all objects are dead the object is not drawn. All points of a detection are considered to be alive. detections : Sequence[Detection], optional **Deprecated**. use drawables. label_size : Optional[int], optional **Deprecated**. text_size. Returns ------- np.ndarray The resulting frame. \"\"\" # # handle deprecated parameters # if color_by_label is not None : warn_once ( 'Parameter \"color_by_label\" on function draw_points is deprecated, set `color=\"by_label\"` instead' ) color = \"by_label\" if detections is not None : warn_once ( \"Parameter 'detections' on function draw_points is deprecated, use 'drawables' instead\" ) drawables = detections if label_size is not None : warn_once ( \"Parameter 'label_size' on function draw_points is deprecated, use 'text_size' instead\" ) text_size = label_size # end if drawables is None : return if text_color is not None : text_color = parse_color ( text_color ) if color is None : color = \"by_id\" if thickness is None : thickness = - 1 if radius is None : radius = int ( round ( max ( max ( frame . shape ) * 0.002 , 1 ))) for o in drawables : if not isinstance ( o , Drawable ): d = Drawable ( o ) else : d = o if hide_dead_points and not d . live_points . any (): continue if color == \"by_id\" : obj_color = Palette . choose_color ( d . id ) elif color == \"by_label\" : obj_color = Palette . choose_color ( d . label ) elif color == \"random\" : obj_color = Palette . choose_color ( np . random . rand ()) else : obj_color = parse_color ( color ) if text_color is None : obj_text_color = obj_color else : obj_text_color = text_color if draw_points : for point , live in zip ( d . points , d . live_points ): if live or not hide_dead_points : Drawer . circle ( frame , tuple ( point . astype ( int )), radius = radius , color = obj_color , thickness = thickness , ) if draw_labels or draw_ids or draw_scores : position = d . points [ d . live_points ] . mean ( axis = 0 ) position -= radius text = _build_text ( d , draw_labels = draw_labels , draw_ids = draw_ids , draw_scores = draw_scores ) Drawer . text ( frame , text , tuple ( position . astype ( int )), size = text_size , color = obj_text_color , thickness = text_thickness , ) return frame draw_tracked_objects ( frame , objects , radius = None , color = None , id_size = None , id_thickness = None , draw_points = True , color_by_label = False , draw_labels = False , label_size = None ) # Deprecated use draw_points Source code in norfair/drawing/draw_points.py 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 def draw_tracked_objects ( frame : np . ndarray , objects : Sequence [ \"TrackedObject\" ], radius : Optional [ int ] = None , color : Optional [ ColorLike ] = None , id_size : Optional [ float ] = None , id_thickness : Optional [ int ] = None , draw_points : bool = True , # pylint: disable=redefined-outer-name color_by_label : bool = False , draw_labels : bool = False , label_size : Optional [ int ] = None , ): \"\"\" **Deprecated** use [`draw_points`][norfair.drawing.draw_points.draw_points] \"\"\" warn_once ( \"draw_tracked_objects is deprecated, use draw_points instead\" ) frame_scale = frame . shape [ 0 ] / 100 if radius is None : radius = int ( frame_scale * 0.5 ) if id_size is None : id_size = frame_scale / 10 if id_thickness is None : id_thickness = int ( frame_scale / 5 ) if label_size is None : label_size = int ( max ( frame_scale / 100 , 1 )) _draw_points_alias ( frame = frame , drawables = objects , color = \"by_label\" if color_by_label else color , radius = radius , thickness = None , draw_labels = draw_labels , draw_ids = id_size is not None and id_size > 0 , draw_points = draw_points , text_size = label_size or id_size , text_thickness = id_thickness , text_color = None , hide_dead_points = True , ) # TODO: We used to have this function to debug # migrate it to use Drawer and clean it up # if possible maybe merge this functionality to the function above # def draw_debug_metrics( # frame: np.ndarray, # objects: Sequence[\"TrackedObject\"], # text_size: Optional[float] = None, # text_thickness: Optional[int] = None, # color: Optional[Tuple[int, int, int]] = None, # only_ids=None, # only_initializing_ids=None, # draw_score_threshold: float = 0, # color_by_label: bool = False, # draw_labels: bool = False, # ): # \"\"\"Draw objects with their debug information # It is recommended to set the input variable `objects` to `your_tracker_object.objects` # so you can also debug objects wich haven't finished initializing, and you get a more # complete view of what your tracker is doing on each step. # \"\"\" # frame_scale = frame.shape[0] / 100 # if text_size is None: # text_size = frame_scale / 10 # if text_thickness is None: # text_thickness = int(frame_scale / 5) # radius = int(frame_scale * 0.5) # for obj in objects: # if ( # not (obj.last_detection.scores is None) # and not (obj.last_detection.scores > draw_score_threshold).any() # ): # continue # if only_ids is not None: # if obj.id not in only_ids: # continue # if only_initializing_ids is not None: # if obj.initializing_id not in only_initializing_ids: # continue # if color_by_label: # text_color = Color.random(abs(hash(obj.label))) # elif color is None: # text_color = Color.random(obj.initializing_id) # else: # text_color = color # draw_position = _centroid( # obj.estimate[obj.last_detection.scores > draw_score_threshold] # if obj.last_detection.scores is not None # else obj.estimate # ) # for point in obj.estimate: # cv2.circle( # frame, # tuple(point.astype(int)), # radius=radius, # color=text_color, # thickness=-1, # ) # # Distance to last matched detection # if obj.last_distance is None: # last_dist = \"-\" # elif obj.last_distance > 999: # last_dist = \">\" # else: # last_dist = \"{:.2f}\".format(obj.last_distance) # # Distance to currently closest detection # if obj.current_min_distance is None: # current_min_dist = \"-\" # else: # current_min_dist = \"{:.2f}\".format(obj.current_min_distance) # # No support for multiline text in opencv :facepalm: # lines_to_draw = [ # \"{}|{}\".format(obj.id, obj.initializing_id), # \"a:{}\".format(obj.age), # \"h:{}\".format(obj.hit_counter), # \"ld:{}\".format(last_dist), # \"cd:{}\".format(current_min_dist), # ] # if draw_labels: # lines_to_draw.append(\"l:{}\".format(obj.label)) # for i, line in enumerate(lines_to_draw): # draw_position = ( # int(draw_position[0]), # int(draw_position[1] + i * text_size * 7 + 15), # ) # cv2.putText( # frame, # line, # draw_position, # cv2.FONT_HERSHEY_SIMPLEX, # text_size, # text_color, # text_thickness, # cv2.LINE_AA, # ) draw_boxes # draw_boxes ( frame , drawables = None , color = 'by_id' , thickness = None , random_color = None , color_by_label = None , draw_labels = False , text_size = None , draw_ids = False , text_color = None , text_thickness = None , draw_box = True , detections = None , line_color = None , line_width = None , label_size = None , draw_scores = False ) # Draw bounding boxes corresponding to Detections or TrackedObjects. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. Modified in place. required drawables Union [ Sequence [ Detection ], Sequence [ TrackedObject ]], optional List of objects to draw, Detections and TrackedObjects are accepted. This objects are assumed to contain 2 bi-dimensional points defining the bounding box as [[x0, y0], [x1, y1]] . None color ColorLike , optional This parameter can take: A color as a tuple of ints describing the BGR (0, 0, 255) A 6-digit hex string \"#FF0000\" One of the defined color names \"red\" A string defining the strategy to choose colors from the Palette: based on the id of the objects \"by_id\" based on the label of the objects \"by_label\" random choice \"random\" If using by_id or by_label strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). 'by_id' thickness Optional [ int ], optional Thickness or width of the line. None random_color bool , optional Deprecated . Set color=\"random\". None color_by_label bool , optional Deprecated . Set color=\"by_label\". None draw_labels bool , optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False draw_scores bool , optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False text_size Optional [ float ], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. None draw_ids bool , optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. False text_color Optional [ ColorLike ], optional Color of the text. By default the same color as the box is used. None text_thickness Optional [ int ], optional Thickness of the font. By default it's scaled with the text_size . None draw_box bool , optional Set to False to hide the box and just draw the text. True detections Sequence [ Detection ], optional Deprecated . Use drawables. None line_color Optional [ ColorLike ] Deprecated . Use color. None line_width Optional [ int ] Deprecated . Use thickness. None label_size Optional [ int ] Deprecated . Use text_size. None Returns: Type Description np . ndarray The resulting frame. Source code in norfair/drawing/draw_boxes.py 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 def draw_boxes ( frame : np . ndarray , drawables : Union [ Sequence [ Detection ], Sequence [ TrackedObject ]] = None , color : ColorLike = \"by_id\" , thickness : Optional [ int ] = None , random_color : bool = None , # Deprecated color_by_label : bool = None , # Deprecated draw_labels : bool = False , text_size : Optional [ float ] = None , draw_ids : bool = False , text_color : Optional [ ColorLike ] = None , text_thickness : Optional [ int ] = None , draw_box : bool = True , detections : Sequence [ \"Detection\" ] = None , # Deprecated line_color : Optional [ ColorLike ] = None , # Deprecated line_width : Optional [ int ] = None , # Deprecated label_size : Optional [ int ] = None , # Deprecated\u00b4 draw_scores : bool = False , ) -> np . ndarray : \"\"\" Draw bounding boxes corresponding to Detections or TrackedObjects. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. Modified in place. drawables : Union[Sequence[Detection], Sequence[TrackedObject]], optional List of objects to draw, Detections and TrackedObjects are accepted. This objects are assumed to contain 2 bi-dimensional points defining the bounding box as `[[x0, y0], [x1, y1]]`. color : ColorLike, optional This parameter can take: 1. A color as a tuple of ints describing the BGR `(0, 0, 255)` 2. A 6-digit hex string `\"#FF0000\"` 3. One of the defined color names `\"red\"` 4. A string defining the strategy to choose colors from the Palette: 1. based on the id of the objects `\"by_id\"` 2. based on the label of the objects `\"by_label\"` 3. random choice `\"random\"` If using `by_id` or `by_label` strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). thickness : Optional[int], optional Thickness or width of the line. random_color : bool, optional **Deprecated**. Set color=\"random\". color_by_label : bool, optional **Deprecated**. Set color=\"by_label\". draw_labels : bool, optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. draw_scores : bool, optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. text_size : Optional[float], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. draw_ids : bool, optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. text_color : Optional[ColorLike], optional Color of the text. By default the same color as the box is used. text_thickness : Optional[int], optional Thickness of the font. By default it's scaled with the `text_size`. draw_box : bool, optional Set to False to hide the box and just draw the text. detections : Sequence[Detection], optional **Deprecated**. Use drawables. line_color: Optional[ColorLike], optional **Deprecated**. Use color. line_width: Optional[int], optional **Deprecated**. Use thickness. label_size: Optional[int], optional **Deprecated**. Use text_size. Returns ------- np.ndarray The resulting frame. \"\"\" # # handle deprecated parameters # if random_color is not None : warn_once ( 'Parameter \"random_color\" is deprecated, set `color=\"random\"` instead' ) color = \"random\" if color_by_label is not None : warn_once ( 'Parameter \"color_by_label\" is deprecated, set `color=\"by_label\"` instead' ) color = \"by_label\" if detections is not None : warn_once ( 'Parameter \"detections\" is deprecated, use \"drawables\" instead' ) drawables = detections if line_color is not None : warn_once ( 'Parameter \"line_color\" is deprecated, use \"color\" instead' ) color = line_color if line_width is not None : warn_once ( 'Parameter \"line_width\" is deprecated, use \"thickness\" instead' ) thickness = line_width if label_size is not None : warn_once ( 'Parameter \"label_size\" is deprecated, use \"text_size\" instead' ) text_size = label_size # end if color is None : color = \"by_id\" if thickness is None : thickness = int ( max ( frame . shape ) / 500 ) if drawables is None : return frame if text_color is not None : text_color = parse_color ( text_color ) for obj in drawables : if not isinstance ( obj , Drawable ): d = Drawable ( obj ) else : d = obj if color == \"by_id\" : obj_color = Palette . choose_color ( d . id ) elif color == \"by_label\" : obj_color = Palette . choose_color ( d . label ) elif color == \"random\" : obj_color = Palette . choose_color ( np . random . rand ()) else : obj_color = parse_color ( color ) points = d . points . astype ( int ) if draw_box : Drawer . rectangle ( frame , tuple ( points ), color = obj_color , thickness = thickness , ) text = _build_text ( d , draw_labels = draw_labels , draw_ids = draw_ids , draw_scores = draw_scores ) if text : if text_color is None : obj_text_color = obj_color else : obj_text_color = text_color # the anchor will become the bottom-left of the text, # we select-top left of the bbox compensating for the thickness of the box text_anchor = ( points [ 0 , 0 ] - thickness // 2 , points [ 0 , 1 ] - thickness // 2 - 1 , ) frame = Drawer . text ( frame , text , position = text_anchor , size = text_size , color = obj_text_color , thickness = text_thickness , ) return frame draw_tracked_boxes ( frame , objects , border_colors = None , border_width = None , id_size = None , id_thickness = None , draw_box = True , color_by_label = False , draw_labels = False , label_size = None , label_width = None ) # Deprecated . Use draw_box Source code in norfair/drawing/draw_boxes.py 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 def draw_tracked_boxes ( frame : np . ndarray , objects : Sequence [ \"TrackedObject\" ], border_colors : Optional [ Tuple [ int , int , int ]] = None , border_width : Optional [ int ] = None , id_size : Optional [ int ] = None , id_thickness : Optional [ int ] = None , draw_box : bool = True , color_by_label : bool = False , draw_labels : bool = False , label_size : Optional [ int ] = None , label_width : Optional [ int ] = None , ) -> np . array : \"**Deprecated**. Use [`draw_box`][norfair.drawing.draw_boxes.draw_boxes]\" warn_once ( \"draw_tracked_boxes is deprecated, use draw_box instead\" ) return draw_boxes ( frame = frame , drawables = objects , color = \"by_label\" if color_by_label else border_colors , thickness = border_width , text_size = label_size or id_size , text_thickness = id_thickness or label_width , draw_labels = draw_labels , draw_ids = id_size is not None and id_size > 0 , draw_box = draw_box , ) color # Color # Contains predefined colors. Colors are defined as a Tuple of integers between 0 and 255 expressing the values in BGR This is the format opencv uses. Source code in norfair/drawing/color.py 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 class Color : \"\"\" Contains predefined colors. Colors are defined as a Tuple of integers between 0 and 255 expressing the values in BGR This is the format opencv uses. \"\"\" # from PIL.ImageColors.colormap aliceblue = hex_to_bgr ( \"#f0f8ff\" ) antiquewhite = hex_to_bgr ( \"#faebd7\" ) aqua = hex_to_bgr ( \"#00ffff\" ) aquamarine = hex_to_bgr ( \"#7fffd4\" ) azure = hex_to_bgr ( \"#f0ffff\" ) beige = hex_to_bgr ( \"#f5f5dc\" ) bisque = hex_to_bgr ( \"#ffe4c4\" ) black = hex_to_bgr ( \"#000000\" ) blanchedalmond = hex_to_bgr ( \"#ffebcd\" ) blue = hex_to_bgr ( \"#0000ff\" ) blueviolet = hex_to_bgr ( \"#8a2be2\" ) brown = hex_to_bgr ( \"#a52a2a\" ) burlywood = hex_to_bgr ( \"#deb887\" ) cadetblue = hex_to_bgr ( \"#5f9ea0\" ) chartreuse = hex_to_bgr ( \"#7fff00\" ) chocolate = hex_to_bgr ( \"#d2691e\" ) coral = hex_to_bgr ( \"#ff7f50\" ) cornflowerblue = hex_to_bgr ( \"#6495ed\" ) cornsilk = hex_to_bgr ( \"#fff8dc\" ) crimson = hex_to_bgr ( \"#dc143c\" ) cyan = hex_to_bgr ( \"#00ffff\" ) darkblue = hex_to_bgr ( \"#00008b\" ) darkcyan = hex_to_bgr ( \"#008b8b\" ) darkgoldenrod = hex_to_bgr ( \"#b8860b\" ) darkgray = hex_to_bgr ( \"#a9a9a9\" ) darkgrey = hex_to_bgr ( \"#a9a9a9\" ) darkgreen = hex_to_bgr ( \"#006400\" ) darkkhaki = hex_to_bgr ( \"#bdb76b\" ) darkmagenta = hex_to_bgr ( \"#8b008b\" ) darkolivegreen = hex_to_bgr ( \"#556b2f\" ) darkorange = hex_to_bgr ( \"#ff8c00\" ) darkorchid = hex_to_bgr ( \"#9932cc\" ) darkred = hex_to_bgr ( \"#8b0000\" ) darksalmon = hex_to_bgr ( \"#e9967a\" ) darkseagreen = hex_to_bgr ( \"#8fbc8f\" ) darkslateblue = hex_to_bgr ( \"#483d8b\" ) darkslategray = hex_to_bgr ( \"#2f4f4f\" ) darkslategrey = hex_to_bgr ( \"#2f4f4f\" ) darkturquoise = hex_to_bgr ( \"#00ced1\" ) darkviolet = hex_to_bgr ( \"#9400d3\" ) deeppink = hex_to_bgr ( \"#ff1493\" ) deepskyblue = hex_to_bgr ( \"#00bfff\" ) dimgray = hex_to_bgr ( \"#696969\" ) dimgrey = hex_to_bgr ( \"#696969\" ) dodgerblue = hex_to_bgr ( \"#1e90ff\" ) firebrick = hex_to_bgr ( \"#b22222\" ) floralwhite = hex_to_bgr ( \"#fffaf0\" ) forestgreen = hex_to_bgr ( \"#228b22\" ) fuchsia = hex_to_bgr ( \"#ff00ff\" ) gainsboro = hex_to_bgr ( \"#dcdcdc\" ) ghostwhite = hex_to_bgr ( \"#f8f8ff\" ) gold = hex_to_bgr ( \"#ffd700\" ) goldenrod = hex_to_bgr ( \"#daa520\" ) gray = hex_to_bgr ( \"#808080\" ) grey = hex_to_bgr ( \"#808080\" ) green = ( 0 , 128 , 0 ) greenyellow = hex_to_bgr ( \"#adff2f\" ) honeydew = hex_to_bgr ( \"#f0fff0\" ) hotpink = hex_to_bgr ( \"#ff69b4\" ) indianred = hex_to_bgr ( \"#cd5c5c\" ) indigo = hex_to_bgr ( \"#4b0082\" ) ivory = hex_to_bgr ( \"#fffff0\" ) khaki = hex_to_bgr ( \"#f0e68c\" ) lavender = hex_to_bgr ( \"#e6e6fa\" ) lavenderblush = hex_to_bgr ( \"#fff0f5\" ) lawngreen = hex_to_bgr ( \"#7cfc00\" ) lemonchiffon = hex_to_bgr ( \"#fffacd\" ) lightblue = hex_to_bgr ( \"#add8e6\" ) lightcoral = hex_to_bgr ( \"#f08080\" ) lightcyan = hex_to_bgr ( \"#e0ffff\" ) lightgoldenrodyellow = hex_to_bgr ( \"#fafad2\" ) lightgreen = hex_to_bgr ( \"#90ee90\" ) lightgray = hex_to_bgr ( \"#d3d3d3\" ) lightgrey = hex_to_bgr ( \"#d3d3d3\" ) lightpink = hex_to_bgr ( \"#ffb6c1\" ) lightsalmon = hex_to_bgr ( \"#ffa07a\" ) lightseagreen = hex_to_bgr ( \"#20b2aa\" ) lightskyblue = hex_to_bgr ( \"#87cefa\" ) lightslategray = hex_to_bgr ( \"#778899\" ) lightslategrey = hex_to_bgr ( \"#778899\" ) lightsteelblue = hex_to_bgr ( \"#b0c4de\" ) lightyellow = hex_to_bgr ( \"#ffffe0\" ) lime = hex_to_bgr ( \"#00ff00\" ) limegreen = hex_to_bgr ( \"#32cd32\" ) linen = hex_to_bgr ( \"#faf0e6\" ) magenta = hex_to_bgr ( \"#ff00ff\" ) maroon = hex_to_bgr ( \"#800000\" ) mediumaquamarine = hex_to_bgr ( \"#66cdaa\" ) mediumblue = hex_to_bgr ( \"#0000cd\" ) mediumorchid = hex_to_bgr ( \"#ba55d3\" ) mediumpurple = hex_to_bgr ( \"#9370db\" ) mediumseagreen = hex_to_bgr ( \"#3cb371\" ) mediumslateblue = hex_to_bgr ( \"#7b68ee\" ) mediumspringgreen = hex_to_bgr ( \"#00fa9a\" ) mediumturquoise = hex_to_bgr ( \"#48d1cc\" ) mediumvioletred = hex_to_bgr ( \"#c71585\" ) midnightblue = hex_to_bgr ( \"#191970\" ) mintcream = hex_to_bgr ( \"#f5fffa\" ) mistyrose = hex_to_bgr ( \"#ffe4e1\" ) moccasin = hex_to_bgr ( \"#ffe4b5\" ) navajowhite = hex_to_bgr ( \"#ffdead\" ) navy = hex_to_bgr ( \"#000080\" ) oldlace = hex_to_bgr ( \"#fdf5e6\" ) olive = hex_to_bgr ( \"#808000\" ) olivedrab = hex_to_bgr ( \"#6b8e23\" ) orange = hex_to_bgr ( \"#ffa500\" ) orangered = hex_to_bgr ( \"#ff4500\" ) orchid = hex_to_bgr ( \"#da70d6\" ) palegoldenrod = hex_to_bgr ( \"#eee8aa\" ) palegreen = hex_to_bgr ( \"#98fb98\" ) paleturquoise = hex_to_bgr ( \"#afeeee\" ) palevioletred = hex_to_bgr ( \"#db7093\" ) papayawhip = hex_to_bgr ( \"#ffefd5\" ) peachpuff = hex_to_bgr ( \"#ffdab9\" ) peru = hex_to_bgr ( \"#cd853f\" ) pink = hex_to_bgr ( \"#ffc0cb\" ) plum = hex_to_bgr ( \"#dda0dd\" ) powderblue = hex_to_bgr ( \"#b0e0e6\" ) purple = hex_to_bgr ( \"#800080\" ) rebeccapurple = hex_to_bgr ( \"#663399\" ) red = hex_to_bgr ( \"#ff0000\" ) rosybrown = hex_to_bgr ( \"#bc8f8f\" ) royalblue = hex_to_bgr ( \"#4169e1\" ) saddlebrown = hex_to_bgr ( \"#8b4513\" ) salmon = hex_to_bgr ( \"#fa8072\" ) sandybrown = hex_to_bgr ( \"#f4a460\" ) seagreen = hex_to_bgr ( \"#2e8b57\" ) seashell = hex_to_bgr ( \"#fff5ee\" ) sienna = hex_to_bgr ( \"#a0522d\" ) silver = hex_to_bgr ( \"#c0c0c0\" ) skyblue = hex_to_bgr ( \"#87ceeb\" ) slateblue = hex_to_bgr ( \"#6a5acd\" ) slategray = hex_to_bgr ( \"#708090\" ) slategrey = hex_to_bgr ( \"#708090\" ) snow = hex_to_bgr ( \"#fffafa\" ) springgreen = hex_to_bgr ( \"#00ff7f\" ) steelblue = hex_to_bgr ( \"#4682b4\" ) tan = hex_to_bgr ( \"#d2b48c\" ) teal = hex_to_bgr ( \"#008080\" ) thistle = hex_to_bgr ( \"#d8bfd8\" ) tomato = hex_to_bgr ( \"#ff6347\" ) turquoise = hex_to_bgr ( \"#40e0d0\" ) violet = hex_to_bgr ( \"#ee82ee\" ) wheat = hex_to_bgr ( \"#f5deb3\" ) white = hex_to_bgr ( \"#ffffff\" ) whitesmoke = hex_to_bgr ( \"#f5f5f5\" ) yellow = hex_to_bgr ( \"#ffff00\" ) yellowgreen = hex_to_bgr ( \"#9acd32\" ) # seaborn tab20 colors tab1 = hex_to_bgr ( \"#1f77b4\" ) tab2 = hex_to_bgr ( \"#aec7e8\" ) tab3 = hex_to_bgr ( \"#ff7f0e\" ) tab4 = hex_to_bgr ( \"#ffbb78\" ) tab5 = hex_to_bgr ( \"#2ca02c\" ) tab6 = hex_to_bgr ( \"#98df8a\" ) tab7 = hex_to_bgr ( \"#d62728\" ) tab8 = hex_to_bgr ( \"#ff9896\" ) tab9 = hex_to_bgr ( \"#9467bd\" ) tab10 = hex_to_bgr ( \"#c5b0d5\" ) tab11 = hex_to_bgr ( \"#8c564b\" ) tab12 = hex_to_bgr ( \"#c49c94\" ) tab13 = hex_to_bgr ( \"#e377c2\" ) tab14 = hex_to_bgr ( \"#f7b6d2\" ) tab15 = hex_to_bgr ( \"#7f7f7f\" ) tab16 = hex_to_bgr ( \"#c7c7c7\" ) tab17 = hex_to_bgr ( \"#bcbd22\" ) tab18 = hex_to_bgr ( \"#dbdb8d\" ) tab19 = hex_to_bgr ( \"#17becf\" ) tab20 = hex_to_bgr ( \"#9edae5\" ) # seaborn colorblind cb1 = hex_to_bgr ( \"#0173b2\" ) cb2 = hex_to_bgr ( \"#de8f05\" ) cb3 = hex_to_bgr ( \"#029e73\" ) cb4 = hex_to_bgr ( \"#d55e00\" ) cb5 = hex_to_bgr ( \"#cc78bc\" ) cb6 = hex_to_bgr ( \"#ca9161\" ) cb7 = hex_to_bgr ( \"#fbafe4\" ) cb8 = hex_to_bgr ( \"#949494\" ) cb9 = hex_to_bgr ( \"#ece133\" ) cb10 = hex_to_bgr ( \"#56b4e9\" ) Palette # Class to control the color pallete for drawing. Examples: Change palette: >>> from norfair import Palette >>> Palette . set ( \"colorblind\" ) >>> # or a custom palette >>> from norfair import Color >>> Palette . set ([ Color . red , Color . blue , \"#ffeeff\" ]) Source code in norfair/drawing/color.py 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 class Palette : \"\"\" Class to control the color pallete for drawing. Examples -------- Change palette: >>> from norfair import Palette >>> Palette.set(\"colorblind\") >>> # or a custom palette >>> from norfair import Color >>> Palette.set([Color.red, Color.blue, \"#ffeeff\"]) \"\"\" _colors = PALETTES [ \"tab10\" ] _default_color = Color . black @classmethod def set ( cls , palette : Union [ str , Iterable [ ColorLike ]]): \"\"\" Selects a color palette. Parameters ---------- palette : Union[str, Iterable[ColorLike]] can be either - the name of one of the predefined palettes `tab10`, `tab20`, or `colorblind` - a list of ColorLike objects that can be parsed by [`parse_color`][norfair.drawing.color.parse_color] \"\"\" if isinstance ( palette , str ): try : cls . _colors = PALETTES [ palette ] except KeyError as e : raise ValueError ( f \"Invalid palette name ' { palette } ', valid values are { PALETTES . keys () } \" ) from e else : colors = [] for c in palette : colors . append ( parse_color ( c )) cls . _colors = colors @classmethod def set_default_color ( cls , color : ColorLike ): \"\"\" Selects the default color of `choose_color` when hashable is None. Parameters ---------- color : ColorLike The new default color. \"\"\" cls . _default_color = parse_color ( color ) @classmethod def choose_color ( cls , hashable : Hashable ) -> ColorType : if hashable is None : return cls . _default_color return cls . _colors [ abs ( hash ( hashable )) % len ( cls . _colors )] set ( palette ) classmethod # Selects a color palette. Parameters: Name Type Description Default palette Union [ str , Iterable [ ColorLike ]] can be either - the name of one of the predefined palettes tab10 , tab20 , or colorblind - a list of ColorLike objects that can be parsed by parse_color required Source code in norfair/drawing/color.py 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 @classmethod def set ( cls , palette : Union [ str , Iterable [ ColorLike ]]): \"\"\" Selects a color palette. Parameters ---------- palette : Union[str, Iterable[ColorLike]] can be either - the name of one of the predefined palettes `tab10`, `tab20`, or `colorblind` - a list of ColorLike objects that can be parsed by [`parse_color`][norfair.drawing.color.parse_color] \"\"\" if isinstance ( palette , str ): try : cls . _colors = PALETTES [ palette ] except KeyError as e : raise ValueError ( f \"Invalid palette name ' { palette } ', valid values are { PALETTES . keys () } \" ) from e else : colors = [] for c in palette : colors . append ( parse_color ( c )) cls . _colors = colors set_default_color ( color ) classmethod # Selects the default color of choose_color when hashable is None. Parameters: Name Type Description Default color ColorLike The new default color. required Source code in norfair/drawing/color.py 355 356 357 358 359 360 361 362 363 364 365 @classmethod def set_default_color ( cls , color : ColorLike ): \"\"\" Selects the default color of `choose_color` when hashable is None. Parameters ---------- color : ColorLike The new default color. \"\"\" cls . _default_color = parse_color ( color ) hex_to_bgr ( hex_value ) # Converts conventional 6 digits hex colors to BGR tuples Parameters: Name Type Description Default hex_value str hex value with leading # for instance \"#ff0000\" required Returns: Type Description Tuple [ int , int , int ] BGR values Raises: Type Description ValueError if the string is invalid Source code in norfair/drawing/color.py 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 def hex_to_bgr ( hex_value : str ) -> ColorType : \"\"\"Converts conventional 6 digits hex colors to BGR tuples Parameters ---------- hex_value : str hex value with leading `#` for instance `\"#ff0000\"` Returns ------- Tuple[int, int, int] BGR values Raises ------ ValueError if the string is invalid \"\"\" if re . match ( \"#[a-f0-9] {6} $\" , hex_value ): return ( int ( hex_value [ 5 : 7 ], 16 ), int ( hex_value [ 3 : 5 ], 16 ), int ( hex_value [ 1 : 3 ], 16 ), ) if re . match ( \"#[a-f0-9] {3} $\" , hex_value ): return ( int ( hex_value [ 3 ] * 2 , 16 ), int ( hex_value [ 2 ] * 2 , 16 ), int ( hex_value [ 1 ] * 2 , 16 ), ) raise ValueError ( f \"' { hex_value } ' is not a valid color\" ) parse_color ( color_like ) # Makes best effort to parse the given value to a Color Parameters: Name Type Description Default color_like ColorLike Can be one of: a string with the 6 digits hex value ( \"#ff0000\" ) a string with one of the names defined in Colors ( \"red\" ) a BGR tuple ( (0, 0, 255) ) required Returns: Type Description Color The BGR tuple. Source code in norfair/drawing/color.py 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 def parse_color ( color_like : ColorLike ) -> ColorType : \"\"\"Makes best effort to parse the given value to a Color Parameters ---------- color_like : ColorLike Can be one of: 1. a string with the 6 digits hex value (`\"#ff0000\"`) 2. a string with one of the names defined in Colors (`\"red\"`) 3. a BGR tuple (`(0, 0, 255)`) Returns ------- Color The BGR tuple. \"\"\" if isinstance ( color_like , str ): if color_like . startswith ( \"#\" ): return hex_to_bgr ( color_like ) else : return getattr ( Color , color_like ) # TODO: validate? return tuple ([ int ( v ) for v in color_like ]) path # Paths # Class that draws the paths taken by a set of points of interest defined from the coordinates of each tracker estimation. Parameters: Name Type Description Default get_points_to_draw Optional [ Callable [[ np . array ], np . array ]], optional Function that takes a list of points (the .estimate attribute of a TrackedObject ) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. None thickness Optional [ int ], optional Thickness of the circles representing the paths of interest. None color Optional [ Tuple [ int , int , int ]], optional Color of the circles representing the paths of interest. None radius Optional [ int ], optional Radius of the circles representing the paths of interest. None attenuation float , optional A float number in [0, 1] that dictates the speed at which the path is erased. if it is 0 then the path is never erased. 0.01 Examples: >>> from norfair import Tracker , Video , Path >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> path_drawer = Path () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> tracked_objects = tracker . update ( detections ) >>> frame = path_drawer . draw ( frame , tracked_objects ) >>> video . write ( frame ) Source code in norfair/drawing/path.py 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 class Paths : \"\"\" Class that draws the paths taken by a set of points of interest defined from the coordinates of each tracker estimation. Parameters ---------- get_points_to_draw : Optional[Callable[[np.array], np.array]], optional Function that takes a list of points (the `.estimate` attribute of a [`TrackedObject`][norfair.tracker.TrackedObject]) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. thickness : Optional[int], optional Thickness of the circles representing the paths of interest. color : Optional[Tuple[int, int, int]], optional [Color][norfair.drawing.Color] of the circles representing the paths of interest. radius : Optional[int], optional Radius of the circles representing the paths of interest. attenuation : float, optional A float number in [0, 1] that dictates the speed at which the path is erased. if it is `0` then the path is never erased. Examples -------- >>> from norfair import Tracker, Video, Path >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> path_drawer = Path() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> tracked_objects = tracker.update(detections) >>> frame = path_drawer.draw(frame, tracked_objects) >>> video.write(frame) \"\"\" def __init__ ( self , get_points_to_draw : Optional [ Callable [[ np . array ], np . array ]] = None , thickness : Optional [ int ] = None , color : Optional [ Tuple [ int , int , int ]] = None , radius : Optional [ int ] = None , attenuation : float = 0.01 , ): if get_points_to_draw is None : def get_points_to_draw ( points ): return [ np . mean ( np . array ( points ), axis = 0 )] self . get_points_to_draw = get_points_to_draw self . radius = radius self . thickness = thickness self . color = color self . mask = None self . attenuation_factor = 1 - attenuation def draw ( self , frame : np . ndarray , tracked_objects : Sequence [ TrackedObject ] ) -> np . array : \"\"\" Draw the paths of the points interest on a frame. !!! warning This method does **not** draw frames in place as other drawers do, the resulting frame is returned. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. tracked_objects : Sequence[TrackedObject] List of [`TrackedObject`][norfair.tracker.TrackedObject] to get the points of interest in order to update the paths. Returns ------- np.array The resulting frame. \"\"\" if self . mask is None : frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) self . mask = np . zeros ( frame . shape , np . uint8 ) self . mask = ( self . mask * self . attenuation_factor ) . astype ( \"uint8\" ) for obj in tracked_objects : if obj . abs_to_rel is not None : warn_once ( \"It seems that your using the Path drawer together with MotionEstimator. This is not fully supported and the results will not be what's expected\" ) if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . estimate ) for point in points_to_draw : self . mask = Drawer . circle ( self . mask , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) return Drawer . alpha_blend ( self . mask , frame , alpha = 1 , beta = 1 ) draw ( frame , tracked_objects ) # Draw the paths of the points interest on a frame. Warning This method does not draw frames in place as other drawers do, the resulting frame is returned. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. required tracked_objects Sequence [ TrackedObject ] List of TrackedObject to get the points of interest in order to update the paths. required Returns: Type Description np . array The resulting frame. Source code in norfair/drawing/path.py 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 def draw ( self , frame : np . ndarray , tracked_objects : Sequence [ TrackedObject ] ) -> np . array : \"\"\" Draw the paths of the points interest on a frame. !!! warning This method does **not** draw frames in place as other drawers do, the resulting frame is returned. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. tracked_objects : Sequence[TrackedObject] List of [`TrackedObject`][norfair.tracker.TrackedObject] to get the points of interest in order to update the paths. Returns ------- np.array The resulting frame. \"\"\" if self . mask is None : frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) self . mask = np . zeros ( frame . shape , np . uint8 ) self . mask = ( self . mask * self . attenuation_factor ) . astype ( \"uint8\" ) for obj in tracked_objects : if obj . abs_to_rel is not None : warn_once ( \"It seems that your using the Path drawer together with MotionEstimator. This is not fully supported and the results will not be what's expected\" ) if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . estimate ) for point in points_to_draw : self . mask = Drawer . circle ( self . mask , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) return Drawer . alpha_blend ( self . mask , frame , alpha = 1 , beta = 1 ) AbsolutePaths # Class that draws the absolute paths taken by a set of points. Works just like Paths but supports camera motion. Warning This drawer is not optimized so it can be stremely slow. Performance degrades linearly with max_history * number_of_tracked_objects . Parameters: Name Type Description Default get_points_to_draw Optional [ Callable [[ np . array ], np . array ]], optional Function that takes a list of points (the .estimate attribute of a TrackedObject ) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. None thickness Optional [ int ], optional Thickness of the circles representing the paths of interest. None color Optional [ Tuple [ int , int , int ]], optional Color of the circles representing the paths of interest. None radius Optional [ int ], optional Radius of the circles representing the paths of interest. None max_history int , optional Number of past points to include in the path. High values make the drawing slower 20 Examples: >>> from norfair import Tracker , Video , Path >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> path_drawer = Path () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> tracked_objects = tracker . update ( detections ) >>> frame = path_drawer . draw ( frame , tracked_objects ) >>> video . write ( frame ) Source code in norfair/drawing/path.py 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 class AbsolutePaths : \"\"\" Class that draws the absolute paths taken by a set of points. Works just like [`Paths`][norfair.drawing.Paths] but supports camera motion. !!! warning This drawer is not optimized so it can be stremely slow. Performance degrades linearly with `max_history * number_of_tracked_objects`. Parameters ---------- get_points_to_draw : Optional[Callable[[np.array], np.array]], optional Function that takes a list of points (the `.estimate` attribute of a [`TrackedObject`][norfair.tracker.TrackedObject]) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. thickness : Optional[int], optional Thickness of the circles representing the paths of interest. color : Optional[Tuple[int, int, int]], optional [Color][norfair.drawing.Color] of the circles representing the paths of interest. radius : Optional[int], optional Radius of the circles representing the paths of interest. max_history : int, optional Number of past points to include in the path. High values make the drawing slower Examples -------- >>> from norfair import Tracker, Video, Path >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> path_drawer = Path() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> tracked_objects = tracker.update(detections) >>> frame = path_drawer.draw(frame, tracked_objects) >>> video.write(frame) \"\"\" def __init__ ( self , get_points_to_draw : Optional [ Callable [[ np . array ], np . array ]] = None , thickness : Optional [ int ] = None , color : Optional [ Tuple [ int , int , int ]] = None , radius : Optional [ int ] = None , max_history = 20 , ): if get_points_to_draw is None : def get_points_to_draw ( points ): return [ np . mean ( np . array ( points ), axis = 0 )] self . get_points_to_draw = get_points_to_draw self . radius = radius self . thickness = thickness self . color = color self . past_points = defaultdict ( lambda : []) self . max_history = max_history self . alphas = np . linspace ( 0.99 , 0.01 , max_history ) def draw ( self , frame , tracked_objects , coord_transform = None ): frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) for obj in tracked_objects : if not obj . live_points . any (): continue if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . get_estimate ( absolute = True )) for point in coord_transform . abs_to_rel ( points_to_draw ): Drawer . circle ( frame , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) last = points_to_draw for i , past_points in enumerate ( self . past_points [ obj . id ]): overlay = frame . copy () last = coord_transform . abs_to_rel ( last ) for j , point in enumerate ( coord_transform . abs_to_rel ( past_points )): Drawer . line ( overlay , tuple ( last [ j ] . astype ( int )), tuple ( point . astype ( int )), color = color , thickness = self . thickness , ) last = past_points alpha = self . alphas [ i ] frame = Drawer . alpha_blend ( overlay , frame , alpha = alpha ) self . past_points [ obj . id ] . insert ( 0 , points_to_draw ) self . past_points [ obj . id ] = self . past_points [ obj . id ][: self . max_history ] return frame fixed_camera # FixedCamera # Class used to stabilize video based on the camera motion. Starts with a larger frame, where the original frame is drawn on top of a black background. As the camera moves, the smaller frame moves in the opposite direction, stabilizing the objects in it. Useful for debugging or demoing the camera motion. Warning This only works with TranslationTransformation , using HomographyTransformation will result in unexpected behaviour. Warning If using other drawers, always apply this one last. Using other drawers on the scaled up frame will not work as expected. Note Sometimes the camera moves so far from the original point that the result won't fit in the scaled-up frame. In this case, a warning will be logged and the frames will be cropped to avoid errors. Parameters: Name Type Description Default scale float , optional The resulting video will have a resolution of scale * (H, W) where HxW is the resolution of the original video. Use a bigger scale if the camera is moving too much. 2 attenuation float , optional Controls how fast the older frames fade to black. 0.05 Examples: >>> # setup >>> tracker = Tracker ( \"frobenious\" , 100 ) >>> motion_estimator = MotionEstimator () >>> video = Video ( input_path = \"video.mp4\" ) >>> fixed_camera = FixedCamera () >>> # process video >>> for frame in video : >>> coord_transformations = motion_estimator . update ( frame ) >>> detections = get_detections ( frame ) >>> tracked_objects = tracker . update ( detections , coord_transformations ) >>> draw_tracked_objects ( frame , tracked_objects ) # fixed_camera should always be the last drawer >>> bigger_frame = fixed_camera . adjust_frame ( frame , coord_transformations ) >>> video . write ( bigger_frame ) Source code in norfair/drawing/fixed_camera.py 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 class FixedCamera : \"\"\" Class used to stabilize video based on the camera motion. Starts with a larger frame, where the original frame is drawn on top of a black background. As the camera moves, the smaller frame moves in the opposite direction, stabilizing the objects in it. Useful for debugging or demoing the camera motion. ![Example GIF](../../videos/camera_stabilization.gif) !!! Warning This only works with [`TranslationTransformation`][norfair.camera_motion.TranslationTransformation], using [`HomographyTransformation`][norfair.camera_motion.HomographyTransformation] will result in unexpected behaviour. !!! Warning If using other drawers, always apply this one last. Using other drawers on the scaled up frame will not work as expected. !!! Note Sometimes the camera moves so far from the original point that the result won't fit in the scaled-up frame. In this case, a warning will be logged and the frames will be cropped to avoid errors. Parameters ---------- scale : float, optional The resulting video will have a resolution of `scale * (H, W)` where HxW is the resolution of the original video. Use a bigger scale if the camera is moving too much. attenuation : float, optional Controls how fast the older frames fade to black. Examples -------- >>> # setup >>> tracker = Tracker(\"frobenious\", 100) >>> motion_estimator = MotionEstimator() >>> video = Video(input_path=\"video.mp4\") >>> fixed_camera = FixedCamera() >>> # process video >>> for frame in video: >>> coord_transformations = motion_estimator.update(frame) >>> detections = get_detections(frame) >>> tracked_objects = tracker.update(detections, coord_transformations) >>> draw_tracked_objects(frame, tracked_objects) # fixed_camera should always be the last drawer >>> bigger_frame = fixed_camera.adjust_frame(frame, coord_transformations) >>> video.write(bigger_frame) \"\"\" def __init__ ( self , scale : float = 2 , attenuation : float = 0.05 ): self . scale = scale self . _background = None self . _attenuation_factor = 1 - attenuation def adjust_frame ( self , frame : np . ndarray , coord_transformation : TranslationTransformation ) -> np . ndarray : \"\"\" Render scaled up frame. Parameters ---------- frame : np.ndarray The OpenCV frame. coord_transformation : TranslationTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] Returns ------- np.ndarray The new bigger frame with the original frame drawn on it. \"\"\" # initialize background if necessary if self . _background is None : original_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) scaled_size = tuple ( ( np . array ( original_size ) * np . array ( self . scale )) . round () . astype ( int ) ) self . _background = np . zeros ( [ scaled_size [ 1 ], scaled_size [ 0 ], frame . shape [ - 1 ]], frame . dtype , ) else : self . _background = ( self . _background * self . _attenuation_factor ) . astype ( frame . dtype ) # top_left is the anchor coordinate from where we start drawing the fame on top of the background # aim to draw it in the center of the background but transformations will move this point top_left = ( np . array ( self . _background . shape [: 2 ]) // 2 - np . array ( frame . shape [: 2 ]) // 2 ) top_left = ( coord_transformation . rel_to_abs ( top_left [:: - 1 ]) . round () . astype ( int )[:: - 1 ] ) # box of the background that will be updated and the limits of it background_y0 , background_y1 = ( top_left [ 0 ], top_left [ 0 ] + frame . shape [ 0 ]) background_x0 , background_x1 = ( top_left [ 1 ], top_left [ 1 ] + frame . shape [ 1 ]) background_size_y , background_size_x = self . _background . shape [: 2 ] # define box of the frame that will be used # if the scale is not enough to support the movement, warn the user but keep drawing # cropping the frame so that the operation doesn't fail frame_y0 , frame_y1 , frame_x0 , frame_x1 = ( 0 , frame . shape [ 0 ], 0 , frame . shape [ 1 ]) if ( background_y0 < 0 or background_x0 < 0 or background_y1 > background_size_y or background_x1 > background_size_x ): warn_once ( \"moving_camera_scale is not enough to cover the range of camera movement, frame will be cropped\" ) # crop left or top of the frame if necessary frame_y0 = max ( - background_y0 , 0 ) frame_x0 = max ( - background_x0 , 0 ) # crop right or bottom of the frame if necessary frame_y1 = max ( min ( background_size_y - background_y0 , background_y1 - background_y0 ), 0 ) frame_x1 = max ( min ( background_size_x - background_x0 , background_x1 - background_x0 ), 0 ) # handle cases where the limits of the background become negative which numpy will interpret incorrectly background_y0 = max ( background_y0 , 0 ) background_x0 = max ( background_x0 , 0 ) background_y1 = max ( background_y1 , 0 ) background_x1 = max ( background_x1 , 0 ) self . _background [ background_y0 : background_y1 , background_x0 : background_x1 , : ] = frame [ frame_y0 : frame_y1 , frame_x0 : frame_x1 , :] return self . _background adjust_frame ( frame , coord_transformation ) # Render scaled up frame. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame. required coord_transformation TranslationTransformation The coordinate transformation as returned by the MotionEstimator required Returns: Type Description np . ndarray The new bigger frame with the original frame drawn on it. Source code in norfair/drawing/fixed_camera.py 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 def adjust_frame ( self , frame : np . ndarray , coord_transformation : TranslationTransformation ) -> np . ndarray : \"\"\" Render scaled up frame. Parameters ---------- frame : np.ndarray The OpenCV frame. coord_transformation : TranslationTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] Returns ------- np.ndarray The new bigger frame with the original frame drawn on it. \"\"\" # initialize background if necessary if self . _background is None : original_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) scaled_size = tuple ( ( np . array ( original_size ) * np . array ( self . scale )) . round () . astype ( int ) ) self . _background = np . zeros ( [ scaled_size [ 1 ], scaled_size [ 0 ], frame . shape [ - 1 ]], frame . dtype , ) else : self . _background = ( self . _background * self . _attenuation_factor ) . astype ( frame . dtype ) # top_left is the anchor coordinate from where we start drawing the fame on top of the background # aim to draw it in the center of the background but transformations will move this point top_left = ( np . array ( self . _background . shape [: 2 ]) // 2 - np . array ( frame . shape [: 2 ]) // 2 ) top_left = ( coord_transformation . rel_to_abs ( top_left [:: - 1 ]) . round () . astype ( int )[:: - 1 ] ) # box of the background that will be updated and the limits of it background_y0 , background_y1 = ( top_left [ 0 ], top_left [ 0 ] + frame . shape [ 0 ]) background_x0 , background_x1 = ( top_left [ 1 ], top_left [ 1 ] + frame . shape [ 1 ]) background_size_y , background_size_x = self . _background . shape [: 2 ] # define box of the frame that will be used # if the scale is not enough to support the movement, warn the user but keep drawing # cropping the frame so that the operation doesn't fail frame_y0 , frame_y1 , frame_x0 , frame_x1 = ( 0 , frame . shape [ 0 ], 0 , frame . shape [ 1 ]) if ( background_y0 < 0 or background_x0 < 0 or background_y1 > background_size_y or background_x1 > background_size_x ): warn_once ( \"moving_camera_scale is not enough to cover the range of camera movement, frame will be cropped\" ) # crop left or top of the frame if necessary frame_y0 = max ( - background_y0 , 0 ) frame_x0 = max ( - background_x0 , 0 ) # crop right or bottom of the frame if necessary frame_y1 = max ( min ( background_size_y - background_y0 , background_y1 - background_y0 ), 0 ) frame_x1 = max ( min ( background_size_x - background_x0 , background_x1 - background_x0 ), 0 ) # handle cases where the limits of the background become negative which numpy will interpret incorrectly background_y0 = max ( background_y0 , 0 ) background_x0 = max ( background_x0 , 0 ) background_y1 = max ( background_y1 , 0 ) background_x1 = max ( background_x1 , 0 ) self . _background [ background_y0 : background_y1 , background_x0 : background_x1 , : ] = frame [ frame_y0 : frame_y1 , frame_x0 : frame_x1 , :] return self . _background absolute_grid # draw_absolute_grid ( frame , coord_transformations , grid_size = 20 , radius = 2 , thickness = 1 , color = Color . black , polar = False ) # Draw a grid of points in absolute coordinates. Useful for debugging camera motion. The points are drawn as if the camera were in the center of a sphere and points are drawn in the intersection of latitude and longitude lines over the surface of the sphere. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. required coord_transformations CoordinatesTransformation The coordinate transformation as returned by the MotionEstimator required grid_size int , optional How many points to draw. 20 radius int , optional Size of each point. 2 thickness int , optional Thickness of each point 1 color ColorType , optional Color of the points. Color.black polar Bool , optional If True, the points on the first frame are drawn as if the camera were pointing to a pole (viewed from the center of the earth). By default, False is used which means the points are drawn as if the camera were pointing to the Equator. False Source code in norfair/drawing/absolute_grid.py 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 def draw_absolute_grid ( frame : np . ndarray , coord_transformations : CoordinatesTransformation , grid_size : int = 20 , radius : int = 2 , thickness : int = 1 , color : ColorType = Color . black , polar : bool = False , ): \"\"\" Draw a grid of points in absolute coordinates. Useful for debugging camera motion. The points are drawn as if the camera were in the center of a sphere and points are drawn in the intersection of latitude and longitude lines over the surface of the sphere. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. coord_transformations : CoordinatesTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] grid_size : int, optional How many points to draw. radius : int, optional Size of each point. thickness : int, optional Thickness of each point color : ColorType, optional Color of the points. polar : Bool, optional If True, the points on the first frame are drawn as if the camera were pointing to a pole (viewed from the center of the earth). By default, False is used which means the points are drawn as if the camera were pointing to the Equator. \"\"\" h , w , _ = frame . shape # get absolute points grid points = _get_grid ( grid_size , w , h , polar = polar ) # transform the points to relative coordinates if coord_transformations is None : points_transformed = points else : points_transformed = coord_transformations . abs_to_rel ( points ) # filter points that are not visible visible_points = points_transformed [ ( points_transformed <= np . array ([ w , h ])) . all ( axis = 1 ) & ( points_transformed >= 0 ) . all ( axis = 1 ) ] for point in visible_points : Drawer . cross ( frame , point . astype ( int ), radius = radius , thickness = thickness , color = color )","title":"Drawing"},{"location":"reference/drawing/#drawing","text":"Collection of drawing functions","title":"Drawing"},{"location":"reference/drawing/#norfair.drawing.draw_points","text":"","title":"draw_points"},{"location":"reference/drawing/#norfair.drawing.draw_points.draw_points","text":"Draw the points included in a list of Detections or TrackedObjects. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. Modified in place. required drawables Union [ Sequence [ Detection ], Sequence [ TrackedObject ]], optional List of objects to draw, Detections and TrackedObjects are accepted. None radius Optional [ int ], optional Radius of the circles representing each point. By default a sensible value is picked considering the frame size. None thickness Optional [ int ], optional Thickness or width of the line. None color ColorLike , optional This parameter can take: A color as a tuple of ints describing the BGR (0, 0, 255) A 6-digit hex string \"#FF0000\" One of the defined color names \"red\" A string defining the strategy to choose colors from the Palette: based on the id of the objects \"by_id\" based on the label of the objects \"by_label\" random choice \"random\" If using by_id or by_label strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). 'by_id' color_by_label bool , optional Deprecated . set color=\"by_label\" . None draw_labels bool , optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. True draw_scores bool , optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False text_size Optional [ int ], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. None draw_ids bool , optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. True draw_points bool , optional Set to False to hide the points and just draw the text. True text_thickness Optional [ int ], optional Thickness of the font. By default it's scaled with the text_size . None text_color Optional [ ColorLike ], optional Color of the text. By default the same color as the box is used. None hide_dead_points bool , optional Set this param to False to always draw all points, even the ones considered \"dead\". A point is \"dead\" when the corresponding value of TrackedObject.live_points is set to False. If all objects are dead the object is not drawn. All points of a detection are considered to be alive. True detections Sequence [ Detection ], optional Deprecated . use drawables. None label_size Optional [ int ], optional Deprecated . text_size. None Returns: Type Description np . ndarray The resulting frame. Source code in norfair/drawing/draw_points.py 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 def draw_points ( frame : np . ndarray , drawables : Union [ Sequence [ Detection ], Sequence [ TrackedObject ]] = None , radius : Optional [ int ] = None , thickness : Optional [ int ] = None , color : ColorLike = \"by_id\" , color_by_label : bool = None , # deprecated draw_labels : bool = True , text_size : Optional [ int ] = None , draw_ids : bool = True , draw_points : bool = True , # pylint: disable=redefined-outer-name text_thickness : Optional [ int ] = None , text_color : Optional [ ColorLike ] = None , hide_dead_points : bool = True , detections : Sequence [ \"Detection\" ] = None , # deprecated label_size : Optional [ int ] = None , # deprecated draw_scores : bool = False , ) -> np . ndarray : \"\"\" Draw the points included in a list of Detections or TrackedObjects. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. Modified in place. drawables : Union[Sequence[Detection], Sequence[TrackedObject]], optional List of objects to draw, Detections and TrackedObjects are accepted. radius : Optional[int], optional Radius of the circles representing each point. By default a sensible value is picked considering the frame size. thickness : Optional[int], optional Thickness or width of the line. color : ColorLike, optional This parameter can take: 1. A color as a tuple of ints describing the BGR `(0, 0, 255)` 2. A 6-digit hex string `\"#FF0000\"` 3. One of the defined color names `\"red\"` 4. A string defining the strategy to choose colors from the Palette: 1. based on the id of the objects `\"by_id\"` 2. based on the label of the objects `\"by_label\"` 3. random choice `\"random\"` If using `by_id` or `by_label` strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). color_by_label : bool, optional **Deprecated**. set `color=\"by_label\"`. draw_labels : bool, optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. draw_scores : bool, optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. text_size : Optional[int], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. draw_ids : bool, optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. draw_points : bool, optional Set to False to hide the points and just draw the text. text_thickness : Optional[int], optional Thickness of the font. By default it's scaled with the `text_size`. text_color : Optional[ColorLike], optional Color of the text. By default the same color as the box is used. hide_dead_points : bool, optional Set this param to False to always draw all points, even the ones considered \"dead\". A point is \"dead\" when the corresponding value of `TrackedObject.live_points` is set to False. If all objects are dead the object is not drawn. All points of a detection are considered to be alive. detections : Sequence[Detection], optional **Deprecated**. use drawables. label_size : Optional[int], optional **Deprecated**. text_size. Returns ------- np.ndarray The resulting frame. \"\"\" # # handle deprecated parameters # if color_by_label is not None : warn_once ( 'Parameter \"color_by_label\" on function draw_points is deprecated, set `color=\"by_label\"` instead' ) color = \"by_label\" if detections is not None : warn_once ( \"Parameter 'detections' on function draw_points is deprecated, use 'drawables' instead\" ) drawables = detections if label_size is not None : warn_once ( \"Parameter 'label_size' on function draw_points is deprecated, use 'text_size' instead\" ) text_size = label_size # end if drawables is None : return if text_color is not None : text_color = parse_color ( text_color ) if color is None : color = \"by_id\" if thickness is None : thickness = - 1 if radius is None : radius = int ( round ( max ( max ( frame . shape ) * 0.002 , 1 ))) for o in drawables : if not isinstance ( o , Drawable ): d = Drawable ( o ) else : d = o if hide_dead_points and not d . live_points . any (): continue if color == \"by_id\" : obj_color = Palette . choose_color ( d . id ) elif color == \"by_label\" : obj_color = Palette . choose_color ( d . label ) elif color == \"random\" : obj_color = Palette . choose_color ( np . random . rand ()) else : obj_color = parse_color ( color ) if text_color is None : obj_text_color = obj_color else : obj_text_color = text_color if draw_points : for point , live in zip ( d . points , d . live_points ): if live or not hide_dead_points : Drawer . circle ( frame , tuple ( point . astype ( int )), radius = radius , color = obj_color , thickness = thickness , ) if draw_labels or draw_ids or draw_scores : position = d . points [ d . live_points ] . mean ( axis = 0 ) position -= radius text = _build_text ( d , draw_labels = draw_labels , draw_ids = draw_ids , draw_scores = draw_scores ) Drawer . text ( frame , text , tuple ( position . astype ( int )), size = text_size , color = obj_text_color , thickness = text_thickness , ) return frame","title":"draw_points()"},{"location":"reference/drawing/#norfair.drawing.draw_points.draw_tracked_objects","text":"Deprecated use draw_points Source code in norfair/drawing/draw_points.py 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 def draw_tracked_objects ( frame : np . ndarray , objects : Sequence [ \"TrackedObject\" ], radius : Optional [ int ] = None , color : Optional [ ColorLike ] = None , id_size : Optional [ float ] = None , id_thickness : Optional [ int ] = None , draw_points : bool = True , # pylint: disable=redefined-outer-name color_by_label : bool = False , draw_labels : bool = False , label_size : Optional [ int ] = None , ): \"\"\" **Deprecated** use [`draw_points`][norfair.drawing.draw_points.draw_points] \"\"\" warn_once ( \"draw_tracked_objects is deprecated, use draw_points instead\" ) frame_scale = frame . shape [ 0 ] / 100 if radius is None : radius = int ( frame_scale * 0.5 ) if id_size is None : id_size = frame_scale / 10 if id_thickness is None : id_thickness = int ( frame_scale / 5 ) if label_size is None : label_size = int ( max ( frame_scale / 100 , 1 )) _draw_points_alias ( frame = frame , drawables = objects , color = \"by_label\" if color_by_label else color , radius = radius , thickness = None , draw_labels = draw_labels , draw_ids = id_size is not None and id_size > 0 , draw_points = draw_points , text_size = label_size or id_size , text_thickness = id_thickness , text_color = None , hide_dead_points = True , ) # TODO: We used to have this function to debug # migrate it to use Drawer and clean it up # if possible maybe merge this functionality to the function above # def draw_debug_metrics( # frame: np.ndarray, # objects: Sequence[\"TrackedObject\"], # text_size: Optional[float] = None, # text_thickness: Optional[int] = None, # color: Optional[Tuple[int, int, int]] = None, # only_ids=None, # only_initializing_ids=None, # draw_score_threshold: float = 0, # color_by_label: bool = False, # draw_labels: bool = False, # ): # \"\"\"Draw objects with their debug information # It is recommended to set the input variable `objects` to `your_tracker_object.objects` # so you can also debug objects wich haven't finished initializing, and you get a more # complete view of what your tracker is doing on each step. # \"\"\" # frame_scale = frame.shape[0] / 100 # if text_size is None: # text_size = frame_scale / 10 # if text_thickness is None: # text_thickness = int(frame_scale / 5) # radius = int(frame_scale * 0.5) # for obj in objects: # if ( # not (obj.last_detection.scores is None) # and not (obj.last_detection.scores > draw_score_threshold).any() # ): # continue # if only_ids is not None: # if obj.id not in only_ids: # continue # if only_initializing_ids is not None: # if obj.initializing_id not in only_initializing_ids: # continue # if color_by_label: # text_color = Color.random(abs(hash(obj.label))) # elif color is None: # text_color = Color.random(obj.initializing_id) # else: # text_color = color # draw_position = _centroid( # obj.estimate[obj.last_detection.scores > draw_score_threshold] # if obj.last_detection.scores is not None # else obj.estimate # ) # for point in obj.estimate: # cv2.circle( # frame, # tuple(point.astype(int)), # radius=radius, # color=text_color, # thickness=-1, # ) # # Distance to last matched detection # if obj.last_distance is None: # last_dist = \"-\" # elif obj.last_distance > 999: # last_dist = \">\" # else: # last_dist = \"{:.2f}\".format(obj.last_distance) # # Distance to currently closest detection # if obj.current_min_distance is None: # current_min_dist = \"-\" # else: # current_min_dist = \"{:.2f}\".format(obj.current_min_distance) # # No support for multiline text in opencv :facepalm: # lines_to_draw = [ # \"{}|{}\".format(obj.id, obj.initializing_id), # \"a:{}\".format(obj.age), # \"h:{}\".format(obj.hit_counter), # \"ld:{}\".format(last_dist), # \"cd:{}\".format(current_min_dist), # ] # if draw_labels: # lines_to_draw.append(\"l:{}\".format(obj.label)) # for i, line in enumerate(lines_to_draw): # draw_position = ( # int(draw_position[0]), # int(draw_position[1] + i * text_size * 7 + 15), # ) # cv2.putText( # frame, # line, # draw_position, # cv2.FONT_HERSHEY_SIMPLEX, # text_size, # text_color, # text_thickness, # cv2.LINE_AA, # )","title":"draw_tracked_objects()"},{"location":"reference/drawing/#norfair.drawing.draw_boxes","text":"","title":"draw_boxes"},{"location":"reference/drawing/#norfair.drawing.draw_boxes.draw_boxes","text":"Draw bounding boxes corresponding to Detections or TrackedObjects. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. Modified in place. required drawables Union [ Sequence [ Detection ], Sequence [ TrackedObject ]], optional List of objects to draw, Detections and TrackedObjects are accepted. This objects are assumed to contain 2 bi-dimensional points defining the bounding box as [[x0, y0], [x1, y1]] . None color ColorLike , optional This parameter can take: A color as a tuple of ints describing the BGR (0, 0, 255) A 6-digit hex string \"#FF0000\" One of the defined color names \"red\" A string defining the strategy to choose colors from the Palette: based on the id of the objects \"by_id\" based on the label of the objects \"by_label\" random choice \"random\" If using by_id or by_label strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). 'by_id' thickness Optional [ int ], optional Thickness or width of the line. None random_color bool , optional Deprecated . Set color=\"random\". None color_by_label bool , optional Deprecated . Set color=\"by_label\". None draw_labels bool , optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False draw_scores bool , optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False text_size Optional [ float ], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. None draw_ids bool , optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. False text_color Optional [ ColorLike ], optional Color of the text. By default the same color as the box is used. None text_thickness Optional [ int ], optional Thickness of the font. By default it's scaled with the text_size . None draw_box bool , optional Set to False to hide the box and just draw the text. True detections Sequence [ Detection ], optional Deprecated . Use drawables. None line_color Optional [ ColorLike ] Deprecated . Use color. None line_width Optional [ int ] Deprecated . Use thickness. None label_size Optional [ int ] Deprecated . Use text_size. None Returns: Type Description np . ndarray The resulting frame. Source code in norfair/drawing/draw_boxes.py 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 def draw_boxes ( frame : np . ndarray , drawables : Union [ Sequence [ Detection ], Sequence [ TrackedObject ]] = None , color : ColorLike = \"by_id\" , thickness : Optional [ int ] = None , random_color : bool = None , # Deprecated color_by_label : bool = None , # Deprecated draw_labels : bool = False , text_size : Optional [ float ] = None , draw_ids : bool = False , text_color : Optional [ ColorLike ] = None , text_thickness : Optional [ int ] = None , draw_box : bool = True , detections : Sequence [ \"Detection\" ] = None , # Deprecated line_color : Optional [ ColorLike ] = None , # Deprecated line_width : Optional [ int ] = None , # Deprecated label_size : Optional [ int ] = None , # Deprecated\u00b4 draw_scores : bool = False , ) -> np . ndarray : \"\"\" Draw bounding boxes corresponding to Detections or TrackedObjects. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. Modified in place. drawables : Union[Sequence[Detection], Sequence[TrackedObject]], optional List of objects to draw, Detections and TrackedObjects are accepted. This objects are assumed to contain 2 bi-dimensional points defining the bounding box as `[[x0, y0], [x1, y1]]`. color : ColorLike, optional This parameter can take: 1. A color as a tuple of ints describing the BGR `(0, 0, 255)` 2. A 6-digit hex string `\"#FF0000\"` 3. One of the defined color names `\"red\"` 4. A string defining the strategy to choose colors from the Palette: 1. based on the id of the objects `\"by_id\"` 2. based on the label of the objects `\"by_label\"` 3. random choice `\"random\"` If using `by_id` or `by_label` strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). thickness : Optional[int], optional Thickness or width of the line. random_color : bool, optional **Deprecated**. Set color=\"random\". color_by_label : bool, optional **Deprecated**. Set color=\"by_label\". draw_labels : bool, optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. draw_scores : bool, optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. text_size : Optional[float], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. draw_ids : bool, optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. text_color : Optional[ColorLike], optional Color of the text. By default the same color as the box is used. text_thickness : Optional[int], optional Thickness of the font. By default it's scaled with the `text_size`. draw_box : bool, optional Set to False to hide the box and just draw the text. detections : Sequence[Detection], optional **Deprecated**. Use drawables. line_color: Optional[ColorLike], optional **Deprecated**. Use color. line_width: Optional[int], optional **Deprecated**. Use thickness. label_size: Optional[int], optional **Deprecated**. Use text_size. Returns ------- np.ndarray The resulting frame. \"\"\" # # handle deprecated parameters # if random_color is not None : warn_once ( 'Parameter \"random_color\" is deprecated, set `color=\"random\"` instead' ) color = \"random\" if color_by_label is not None : warn_once ( 'Parameter \"color_by_label\" is deprecated, set `color=\"by_label\"` instead' ) color = \"by_label\" if detections is not None : warn_once ( 'Parameter \"detections\" is deprecated, use \"drawables\" instead' ) drawables = detections if line_color is not None : warn_once ( 'Parameter \"line_color\" is deprecated, use \"color\" instead' ) color = line_color if line_width is not None : warn_once ( 'Parameter \"line_width\" is deprecated, use \"thickness\" instead' ) thickness = line_width if label_size is not None : warn_once ( 'Parameter \"label_size\" is deprecated, use \"text_size\" instead' ) text_size = label_size # end if color is None : color = \"by_id\" if thickness is None : thickness = int ( max ( frame . shape ) / 500 ) if drawables is None : return frame if text_color is not None : text_color = parse_color ( text_color ) for obj in drawables : if not isinstance ( obj , Drawable ): d = Drawable ( obj ) else : d = obj if color == \"by_id\" : obj_color = Palette . choose_color ( d . id ) elif color == \"by_label\" : obj_color = Palette . choose_color ( d . label ) elif color == \"random\" : obj_color = Palette . choose_color ( np . random . rand ()) else : obj_color = parse_color ( color ) points = d . points . astype ( int ) if draw_box : Drawer . rectangle ( frame , tuple ( points ), color = obj_color , thickness = thickness , ) text = _build_text ( d , draw_labels = draw_labels , draw_ids = draw_ids , draw_scores = draw_scores ) if text : if text_color is None : obj_text_color = obj_color else : obj_text_color = text_color # the anchor will become the bottom-left of the text, # we select-top left of the bbox compensating for the thickness of the box text_anchor = ( points [ 0 , 0 ] - thickness // 2 , points [ 0 , 1 ] - thickness // 2 - 1 , ) frame = Drawer . text ( frame , text , position = text_anchor , size = text_size , color = obj_text_color , thickness = text_thickness , ) return frame","title":"draw_boxes()"},{"location":"reference/drawing/#norfair.drawing.draw_boxes.draw_tracked_boxes","text":"Deprecated . Use draw_box Source code in norfair/drawing/draw_boxes.py 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 def draw_tracked_boxes ( frame : np . ndarray , objects : Sequence [ \"TrackedObject\" ], border_colors : Optional [ Tuple [ int , int , int ]] = None , border_width : Optional [ int ] = None , id_size : Optional [ int ] = None , id_thickness : Optional [ int ] = None , draw_box : bool = True , color_by_label : bool = False , draw_labels : bool = False , label_size : Optional [ int ] = None , label_width : Optional [ int ] = None , ) -> np . array : \"**Deprecated**. Use [`draw_box`][norfair.drawing.draw_boxes.draw_boxes]\" warn_once ( \"draw_tracked_boxes is deprecated, use draw_box instead\" ) return draw_boxes ( frame = frame , drawables = objects , color = \"by_label\" if color_by_label else border_colors , thickness = border_width , text_size = label_size or id_size , text_thickness = id_thickness or label_width , draw_labels = draw_labels , draw_ids = id_size is not None and id_size > 0 , draw_box = draw_box , )","title":"draw_tracked_boxes()"},{"location":"reference/drawing/#norfair.drawing.color","text":"","title":"color"},{"location":"reference/drawing/#norfair.drawing.color.Color","text":"Contains predefined colors. Colors are defined as a Tuple of integers between 0 and 255 expressing the values in BGR This is the format opencv uses. Source code in norfair/drawing/color.py 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 class Color : \"\"\" Contains predefined colors. Colors are defined as a Tuple of integers between 0 and 255 expressing the values in BGR This is the format opencv uses. \"\"\" # from PIL.ImageColors.colormap aliceblue = hex_to_bgr ( \"#f0f8ff\" ) antiquewhite = hex_to_bgr ( \"#faebd7\" ) aqua = hex_to_bgr ( \"#00ffff\" ) aquamarine = hex_to_bgr ( \"#7fffd4\" ) azure = hex_to_bgr ( \"#f0ffff\" ) beige = hex_to_bgr ( \"#f5f5dc\" ) bisque = hex_to_bgr ( \"#ffe4c4\" ) black = hex_to_bgr ( \"#000000\" ) blanchedalmond = hex_to_bgr ( \"#ffebcd\" ) blue = hex_to_bgr ( \"#0000ff\" ) blueviolet = hex_to_bgr ( \"#8a2be2\" ) brown = hex_to_bgr ( \"#a52a2a\" ) burlywood = hex_to_bgr ( \"#deb887\" ) cadetblue = hex_to_bgr ( \"#5f9ea0\" ) chartreuse = hex_to_bgr ( \"#7fff00\" ) chocolate = hex_to_bgr ( \"#d2691e\" ) coral = hex_to_bgr ( \"#ff7f50\" ) cornflowerblue = hex_to_bgr ( \"#6495ed\" ) cornsilk = hex_to_bgr ( \"#fff8dc\" ) crimson = hex_to_bgr ( \"#dc143c\" ) cyan = hex_to_bgr ( \"#00ffff\" ) darkblue = hex_to_bgr ( \"#00008b\" ) darkcyan = hex_to_bgr ( \"#008b8b\" ) darkgoldenrod = hex_to_bgr ( \"#b8860b\" ) darkgray = hex_to_bgr ( \"#a9a9a9\" ) darkgrey = hex_to_bgr ( \"#a9a9a9\" ) darkgreen = hex_to_bgr ( \"#006400\" ) darkkhaki = hex_to_bgr ( \"#bdb76b\" ) darkmagenta = hex_to_bgr ( \"#8b008b\" ) darkolivegreen = hex_to_bgr ( \"#556b2f\" ) darkorange = hex_to_bgr ( \"#ff8c00\" ) darkorchid = hex_to_bgr ( \"#9932cc\" ) darkred = hex_to_bgr ( \"#8b0000\" ) darksalmon = hex_to_bgr ( \"#e9967a\" ) darkseagreen = hex_to_bgr ( \"#8fbc8f\" ) darkslateblue = hex_to_bgr ( \"#483d8b\" ) darkslategray = hex_to_bgr ( \"#2f4f4f\" ) darkslategrey = hex_to_bgr ( \"#2f4f4f\" ) darkturquoise = hex_to_bgr ( \"#00ced1\" ) darkviolet = hex_to_bgr ( \"#9400d3\" ) deeppink = hex_to_bgr ( \"#ff1493\" ) deepskyblue = hex_to_bgr ( \"#00bfff\" ) dimgray = hex_to_bgr ( \"#696969\" ) dimgrey = hex_to_bgr ( \"#696969\" ) dodgerblue = hex_to_bgr ( \"#1e90ff\" ) firebrick = hex_to_bgr ( \"#b22222\" ) floralwhite = hex_to_bgr ( \"#fffaf0\" ) forestgreen = hex_to_bgr ( \"#228b22\" ) fuchsia = hex_to_bgr ( \"#ff00ff\" ) gainsboro = hex_to_bgr ( \"#dcdcdc\" ) ghostwhite = hex_to_bgr ( \"#f8f8ff\" ) gold = hex_to_bgr ( \"#ffd700\" ) goldenrod = hex_to_bgr ( \"#daa520\" ) gray = hex_to_bgr ( \"#808080\" ) grey = hex_to_bgr ( \"#808080\" ) green = ( 0 , 128 , 0 ) greenyellow = hex_to_bgr ( \"#adff2f\" ) honeydew = hex_to_bgr ( \"#f0fff0\" ) hotpink = hex_to_bgr ( \"#ff69b4\" ) indianred = hex_to_bgr ( \"#cd5c5c\" ) indigo = hex_to_bgr ( \"#4b0082\" ) ivory = hex_to_bgr ( \"#fffff0\" ) khaki = hex_to_bgr ( \"#f0e68c\" ) lavender = hex_to_bgr ( \"#e6e6fa\" ) lavenderblush = hex_to_bgr ( \"#fff0f5\" ) lawngreen = hex_to_bgr ( \"#7cfc00\" ) lemonchiffon = hex_to_bgr ( \"#fffacd\" ) lightblue = hex_to_bgr ( \"#add8e6\" ) lightcoral = hex_to_bgr ( \"#f08080\" ) lightcyan = hex_to_bgr ( \"#e0ffff\" ) lightgoldenrodyellow = hex_to_bgr ( \"#fafad2\" ) lightgreen = hex_to_bgr ( \"#90ee90\" ) lightgray = hex_to_bgr ( \"#d3d3d3\" ) lightgrey = hex_to_bgr ( \"#d3d3d3\" ) lightpink = hex_to_bgr ( \"#ffb6c1\" ) lightsalmon = hex_to_bgr ( \"#ffa07a\" ) lightseagreen = hex_to_bgr ( \"#20b2aa\" ) lightskyblue = hex_to_bgr ( \"#87cefa\" ) lightslategray = hex_to_bgr ( \"#778899\" ) lightslategrey = hex_to_bgr ( \"#778899\" ) lightsteelblue = hex_to_bgr ( \"#b0c4de\" ) lightyellow = hex_to_bgr ( \"#ffffe0\" ) lime = hex_to_bgr ( \"#00ff00\" ) limegreen = hex_to_bgr ( \"#32cd32\" ) linen = hex_to_bgr ( \"#faf0e6\" ) magenta = hex_to_bgr ( \"#ff00ff\" ) maroon = hex_to_bgr ( \"#800000\" ) mediumaquamarine = hex_to_bgr ( \"#66cdaa\" ) mediumblue = hex_to_bgr ( \"#0000cd\" ) mediumorchid = hex_to_bgr ( \"#ba55d3\" ) mediumpurple = hex_to_bgr ( \"#9370db\" ) mediumseagreen = hex_to_bgr ( \"#3cb371\" ) mediumslateblue = hex_to_bgr ( \"#7b68ee\" ) mediumspringgreen = hex_to_bgr ( \"#00fa9a\" ) mediumturquoise = hex_to_bgr ( \"#48d1cc\" ) mediumvioletred = hex_to_bgr ( \"#c71585\" ) midnightblue = hex_to_bgr ( \"#191970\" ) mintcream = hex_to_bgr ( \"#f5fffa\" ) mistyrose = hex_to_bgr ( \"#ffe4e1\" ) moccasin = hex_to_bgr ( \"#ffe4b5\" ) navajowhite = hex_to_bgr ( \"#ffdead\" ) navy = hex_to_bgr ( \"#000080\" ) oldlace = hex_to_bgr ( \"#fdf5e6\" ) olive = hex_to_bgr ( \"#808000\" ) olivedrab = hex_to_bgr ( \"#6b8e23\" ) orange = hex_to_bgr ( \"#ffa500\" ) orangered = hex_to_bgr ( \"#ff4500\" ) orchid = hex_to_bgr ( \"#da70d6\" ) palegoldenrod = hex_to_bgr ( \"#eee8aa\" ) palegreen = hex_to_bgr ( \"#98fb98\" ) paleturquoise = hex_to_bgr ( \"#afeeee\" ) palevioletred = hex_to_bgr ( \"#db7093\" ) papayawhip = hex_to_bgr ( \"#ffefd5\" ) peachpuff = hex_to_bgr ( \"#ffdab9\" ) peru = hex_to_bgr ( \"#cd853f\" ) pink = hex_to_bgr ( \"#ffc0cb\" ) plum = hex_to_bgr ( \"#dda0dd\" ) powderblue = hex_to_bgr ( \"#b0e0e6\" ) purple = hex_to_bgr ( \"#800080\" ) rebeccapurple = hex_to_bgr ( \"#663399\" ) red = hex_to_bgr ( \"#ff0000\" ) rosybrown = hex_to_bgr ( \"#bc8f8f\" ) royalblue = hex_to_bgr ( \"#4169e1\" ) saddlebrown = hex_to_bgr ( \"#8b4513\" ) salmon = hex_to_bgr ( \"#fa8072\" ) sandybrown = hex_to_bgr ( \"#f4a460\" ) seagreen = hex_to_bgr ( \"#2e8b57\" ) seashell = hex_to_bgr ( \"#fff5ee\" ) sienna = hex_to_bgr ( \"#a0522d\" ) silver = hex_to_bgr ( \"#c0c0c0\" ) skyblue = hex_to_bgr ( \"#87ceeb\" ) slateblue = hex_to_bgr ( \"#6a5acd\" ) slategray = hex_to_bgr ( \"#708090\" ) slategrey = hex_to_bgr ( \"#708090\" ) snow = hex_to_bgr ( \"#fffafa\" ) springgreen = hex_to_bgr ( \"#00ff7f\" ) steelblue = hex_to_bgr ( \"#4682b4\" ) tan = hex_to_bgr ( \"#d2b48c\" ) teal = hex_to_bgr ( \"#008080\" ) thistle = hex_to_bgr ( \"#d8bfd8\" ) tomato = hex_to_bgr ( \"#ff6347\" ) turquoise = hex_to_bgr ( \"#40e0d0\" ) violet = hex_to_bgr ( \"#ee82ee\" ) wheat = hex_to_bgr ( \"#f5deb3\" ) white = hex_to_bgr ( \"#ffffff\" ) whitesmoke = hex_to_bgr ( \"#f5f5f5\" ) yellow = hex_to_bgr ( \"#ffff00\" ) yellowgreen = hex_to_bgr ( \"#9acd32\" ) # seaborn tab20 colors tab1 = hex_to_bgr ( \"#1f77b4\" ) tab2 = hex_to_bgr ( \"#aec7e8\" ) tab3 = hex_to_bgr ( \"#ff7f0e\" ) tab4 = hex_to_bgr ( \"#ffbb78\" ) tab5 = hex_to_bgr ( \"#2ca02c\" ) tab6 = hex_to_bgr ( \"#98df8a\" ) tab7 = hex_to_bgr ( \"#d62728\" ) tab8 = hex_to_bgr ( \"#ff9896\" ) tab9 = hex_to_bgr ( \"#9467bd\" ) tab10 = hex_to_bgr ( \"#c5b0d5\" ) tab11 = hex_to_bgr ( \"#8c564b\" ) tab12 = hex_to_bgr ( \"#c49c94\" ) tab13 = hex_to_bgr ( \"#e377c2\" ) tab14 = hex_to_bgr ( \"#f7b6d2\" ) tab15 = hex_to_bgr ( \"#7f7f7f\" ) tab16 = hex_to_bgr ( \"#c7c7c7\" ) tab17 = hex_to_bgr ( \"#bcbd22\" ) tab18 = hex_to_bgr ( \"#dbdb8d\" ) tab19 = hex_to_bgr ( \"#17becf\" ) tab20 = hex_to_bgr ( \"#9edae5\" ) # seaborn colorblind cb1 = hex_to_bgr ( \"#0173b2\" ) cb2 = hex_to_bgr ( \"#de8f05\" ) cb3 = hex_to_bgr ( \"#029e73\" ) cb4 = hex_to_bgr ( \"#d55e00\" ) cb5 = hex_to_bgr ( \"#cc78bc\" ) cb6 = hex_to_bgr ( \"#ca9161\" ) cb7 = hex_to_bgr ( \"#fbafe4\" ) cb8 = hex_to_bgr ( \"#949494\" ) cb9 = hex_to_bgr ( \"#ece133\" ) cb10 = hex_to_bgr ( \"#56b4e9\" )","title":"Color"},{"location":"reference/drawing/#norfair.drawing.color.Palette","text":"Class to control the color pallete for drawing. Examples: Change palette: >>> from norfair import Palette >>> Palette . set ( \"colorblind\" ) >>> # or a custom palette >>> from norfair import Color >>> Palette . set ([ Color . red , Color . blue , \"#ffeeff\" ]) Source code in norfair/drawing/color.py 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 class Palette : \"\"\" Class to control the color pallete for drawing. Examples -------- Change palette: >>> from norfair import Palette >>> Palette.set(\"colorblind\") >>> # or a custom palette >>> from norfair import Color >>> Palette.set([Color.red, Color.blue, \"#ffeeff\"]) \"\"\" _colors = PALETTES [ \"tab10\" ] _default_color = Color . black @classmethod def set ( cls , palette : Union [ str , Iterable [ ColorLike ]]): \"\"\" Selects a color palette. Parameters ---------- palette : Union[str, Iterable[ColorLike]] can be either - the name of one of the predefined palettes `tab10`, `tab20`, or `colorblind` - a list of ColorLike objects that can be parsed by [`parse_color`][norfair.drawing.color.parse_color] \"\"\" if isinstance ( palette , str ): try : cls . _colors = PALETTES [ palette ] except KeyError as e : raise ValueError ( f \"Invalid palette name ' { palette } ', valid values are { PALETTES . keys () } \" ) from e else : colors = [] for c in palette : colors . append ( parse_color ( c )) cls . _colors = colors @classmethod def set_default_color ( cls , color : ColorLike ): \"\"\" Selects the default color of `choose_color` when hashable is None. Parameters ---------- color : ColorLike The new default color. \"\"\" cls . _default_color = parse_color ( color ) @classmethod def choose_color ( cls , hashable : Hashable ) -> ColorType : if hashable is None : return cls . _default_color return cls . _colors [ abs ( hash ( hashable )) % len ( cls . _colors )]","title":"Palette"},{"location":"reference/drawing/#norfair.drawing.color.Palette.set","text":"Selects a color palette. Parameters: Name Type Description Default palette Union [ str , Iterable [ ColorLike ]] can be either - the name of one of the predefined palettes tab10 , tab20 , or colorblind - a list of ColorLike objects that can be parsed by parse_color required Source code in norfair/drawing/color.py 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 @classmethod def set ( cls , palette : Union [ str , Iterable [ ColorLike ]]): \"\"\" Selects a color palette. Parameters ---------- palette : Union[str, Iterable[ColorLike]] can be either - the name of one of the predefined palettes `tab10`, `tab20`, or `colorblind` - a list of ColorLike objects that can be parsed by [`parse_color`][norfair.drawing.color.parse_color] \"\"\" if isinstance ( palette , str ): try : cls . _colors = PALETTES [ palette ] except KeyError as e : raise ValueError ( f \"Invalid palette name ' { palette } ', valid values are { PALETTES . keys () } \" ) from e else : colors = [] for c in palette : colors . append ( parse_color ( c )) cls . _colors = colors","title":"set()"},{"location":"reference/drawing/#norfair.drawing.color.Palette.set_default_color","text":"Selects the default color of choose_color when hashable is None. Parameters: Name Type Description Default color ColorLike The new default color. required Source code in norfair/drawing/color.py 355 356 357 358 359 360 361 362 363 364 365 @classmethod def set_default_color ( cls , color : ColorLike ): \"\"\" Selects the default color of `choose_color` when hashable is None. Parameters ---------- color : ColorLike The new default color. \"\"\" cls . _default_color = parse_color ( color )","title":"set_default_color()"},{"location":"reference/drawing/#norfair.drawing.color.hex_to_bgr","text":"Converts conventional 6 digits hex colors to BGR tuples Parameters: Name Type Description Default hex_value str hex value with leading # for instance \"#ff0000\" required Returns: Type Description Tuple [ int , int , int ] BGR values Raises: Type Description ValueError if the string is invalid Source code in norfair/drawing/color.py 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 def hex_to_bgr ( hex_value : str ) -> ColorType : \"\"\"Converts conventional 6 digits hex colors to BGR tuples Parameters ---------- hex_value : str hex value with leading `#` for instance `\"#ff0000\"` Returns ------- Tuple[int, int, int] BGR values Raises ------ ValueError if the string is invalid \"\"\" if re . match ( \"#[a-f0-9] {6} $\" , hex_value ): return ( int ( hex_value [ 5 : 7 ], 16 ), int ( hex_value [ 3 : 5 ], 16 ), int ( hex_value [ 1 : 3 ], 16 ), ) if re . match ( \"#[a-f0-9] {3} $\" , hex_value ): return ( int ( hex_value [ 3 ] * 2 , 16 ), int ( hex_value [ 2 ] * 2 , 16 ), int ( hex_value [ 1 ] * 2 , 16 ), ) raise ValueError ( f \"' { hex_value } ' is not a valid color\" )","title":"hex_to_bgr()"},{"location":"reference/drawing/#norfair.drawing.color.parse_color","text":"Makes best effort to parse the given value to a Color Parameters: Name Type Description Default color_like ColorLike Can be one of: a string with the 6 digits hex value ( \"#ff0000\" ) a string with one of the names defined in Colors ( \"red\" ) a BGR tuple ( (0, 0, 255) ) required Returns: Type Description Color The BGR tuple. Source code in norfair/drawing/color.py 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 def parse_color ( color_like : ColorLike ) -> ColorType : \"\"\"Makes best effort to parse the given value to a Color Parameters ---------- color_like : ColorLike Can be one of: 1. a string with the 6 digits hex value (`\"#ff0000\"`) 2. a string with one of the names defined in Colors (`\"red\"`) 3. a BGR tuple (`(0, 0, 255)`) Returns ------- Color The BGR tuple. \"\"\" if isinstance ( color_like , str ): if color_like . startswith ( \"#\" ): return hex_to_bgr ( color_like ) else : return getattr ( Color , color_like ) # TODO: validate? return tuple ([ int ( v ) for v in color_like ])","title":"parse_color()"},{"location":"reference/drawing/#norfair.drawing.path","text":"","title":"path"},{"location":"reference/drawing/#norfair.drawing.path.Paths","text":"Class that draws the paths taken by a set of points of interest defined from the coordinates of each tracker estimation. Parameters: Name Type Description Default get_points_to_draw Optional [ Callable [[ np . array ], np . array ]], optional Function that takes a list of points (the .estimate attribute of a TrackedObject ) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. None thickness Optional [ int ], optional Thickness of the circles representing the paths of interest. None color Optional [ Tuple [ int , int , int ]], optional Color of the circles representing the paths of interest. None radius Optional [ int ], optional Radius of the circles representing the paths of interest. None attenuation float , optional A float number in [0, 1] that dictates the speed at which the path is erased. if it is 0 then the path is never erased. 0.01 Examples: >>> from norfair import Tracker , Video , Path >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> path_drawer = Path () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> tracked_objects = tracker . update ( detections ) >>> frame = path_drawer . draw ( frame , tracked_objects ) >>> video . write ( frame ) Source code in norfair/drawing/path.py 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 class Paths : \"\"\" Class that draws the paths taken by a set of points of interest defined from the coordinates of each tracker estimation. Parameters ---------- get_points_to_draw : Optional[Callable[[np.array], np.array]], optional Function that takes a list of points (the `.estimate` attribute of a [`TrackedObject`][norfair.tracker.TrackedObject]) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. thickness : Optional[int], optional Thickness of the circles representing the paths of interest. color : Optional[Tuple[int, int, int]], optional [Color][norfair.drawing.Color] of the circles representing the paths of interest. radius : Optional[int], optional Radius of the circles representing the paths of interest. attenuation : float, optional A float number in [0, 1] that dictates the speed at which the path is erased. if it is `0` then the path is never erased. Examples -------- >>> from norfair import Tracker, Video, Path >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> path_drawer = Path() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> tracked_objects = tracker.update(detections) >>> frame = path_drawer.draw(frame, tracked_objects) >>> video.write(frame) \"\"\" def __init__ ( self , get_points_to_draw : Optional [ Callable [[ np . array ], np . array ]] = None , thickness : Optional [ int ] = None , color : Optional [ Tuple [ int , int , int ]] = None , radius : Optional [ int ] = None , attenuation : float = 0.01 , ): if get_points_to_draw is None : def get_points_to_draw ( points ): return [ np . mean ( np . array ( points ), axis = 0 )] self . get_points_to_draw = get_points_to_draw self . radius = radius self . thickness = thickness self . color = color self . mask = None self . attenuation_factor = 1 - attenuation def draw ( self , frame : np . ndarray , tracked_objects : Sequence [ TrackedObject ] ) -> np . array : \"\"\" Draw the paths of the points interest on a frame. !!! warning This method does **not** draw frames in place as other drawers do, the resulting frame is returned. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. tracked_objects : Sequence[TrackedObject] List of [`TrackedObject`][norfair.tracker.TrackedObject] to get the points of interest in order to update the paths. Returns ------- np.array The resulting frame. \"\"\" if self . mask is None : frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) self . mask = np . zeros ( frame . shape , np . uint8 ) self . mask = ( self . mask * self . attenuation_factor ) . astype ( \"uint8\" ) for obj in tracked_objects : if obj . abs_to_rel is not None : warn_once ( \"It seems that your using the Path drawer together with MotionEstimator. This is not fully supported and the results will not be what's expected\" ) if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . estimate ) for point in points_to_draw : self . mask = Drawer . circle ( self . mask , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) return Drawer . alpha_blend ( self . mask , frame , alpha = 1 , beta = 1 )","title":"Paths"},{"location":"reference/drawing/#norfair.drawing.path.Paths.draw","text":"Draw the paths of the points interest on a frame. Warning This method does not draw frames in place as other drawers do, the resulting frame is returned. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. required tracked_objects Sequence [ TrackedObject ] List of TrackedObject to get the points of interest in order to update the paths. required Returns: Type Description np . array The resulting frame. Source code in norfair/drawing/path.py 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 def draw ( self , frame : np . ndarray , tracked_objects : Sequence [ TrackedObject ] ) -> np . array : \"\"\" Draw the paths of the points interest on a frame. !!! warning This method does **not** draw frames in place as other drawers do, the resulting frame is returned. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. tracked_objects : Sequence[TrackedObject] List of [`TrackedObject`][norfair.tracker.TrackedObject] to get the points of interest in order to update the paths. Returns ------- np.array The resulting frame. \"\"\" if self . mask is None : frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) self . mask = np . zeros ( frame . shape , np . uint8 ) self . mask = ( self . mask * self . attenuation_factor ) . astype ( \"uint8\" ) for obj in tracked_objects : if obj . abs_to_rel is not None : warn_once ( \"It seems that your using the Path drawer together with MotionEstimator. This is not fully supported and the results will not be what's expected\" ) if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . estimate ) for point in points_to_draw : self . mask = Drawer . circle ( self . mask , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) return Drawer . alpha_blend ( self . mask , frame , alpha = 1 , beta = 1 )","title":"draw()"},{"location":"reference/drawing/#norfair.drawing.path.AbsolutePaths","text":"Class that draws the absolute paths taken by a set of points. Works just like Paths but supports camera motion. Warning This drawer is not optimized so it can be stremely slow. Performance degrades linearly with max_history * number_of_tracked_objects . Parameters: Name Type Description Default get_points_to_draw Optional [ Callable [[ np . array ], np . array ]], optional Function that takes a list of points (the .estimate attribute of a TrackedObject ) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. None thickness Optional [ int ], optional Thickness of the circles representing the paths of interest. None color Optional [ Tuple [ int , int , int ]], optional Color of the circles representing the paths of interest. None radius Optional [ int ], optional Radius of the circles representing the paths of interest. None max_history int , optional Number of past points to include in the path. High values make the drawing slower 20 Examples: >>> from norfair import Tracker , Video , Path >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> path_drawer = Path () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> tracked_objects = tracker . update ( detections ) >>> frame = path_drawer . draw ( frame , tracked_objects ) >>> video . write ( frame ) Source code in norfair/drawing/path.py 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 class AbsolutePaths : \"\"\" Class that draws the absolute paths taken by a set of points. Works just like [`Paths`][norfair.drawing.Paths] but supports camera motion. !!! warning This drawer is not optimized so it can be stremely slow. Performance degrades linearly with `max_history * number_of_tracked_objects`. Parameters ---------- get_points_to_draw : Optional[Callable[[np.array], np.array]], optional Function that takes a list of points (the `.estimate` attribute of a [`TrackedObject`][norfair.tracker.TrackedObject]) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. thickness : Optional[int], optional Thickness of the circles representing the paths of interest. color : Optional[Tuple[int, int, int]], optional [Color][norfair.drawing.Color] of the circles representing the paths of interest. radius : Optional[int], optional Radius of the circles representing the paths of interest. max_history : int, optional Number of past points to include in the path. High values make the drawing slower Examples -------- >>> from norfair import Tracker, Video, Path >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> path_drawer = Path() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> tracked_objects = tracker.update(detections) >>> frame = path_drawer.draw(frame, tracked_objects) >>> video.write(frame) \"\"\" def __init__ ( self , get_points_to_draw : Optional [ Callable [[ np . array ], np . array ]] = None , thickness : Optional [ int ] = None , color : Optional [ Tuple [ int , int , int ]] = None , radius : Optional [ int ] = None , max_history = 20 , ): if get_points_to_draw is None : def get_points_to_draw ( points ): return [ np . mean ( np . array ( points ), axis = 0 )] self . get_points_to_draw = get_points_to_draw self . radius = radius self . thickness = thickness self . color = color self . past_points = defaultdict ( lambda : []) self . max_history = max_history self . alphas = np . linspace ( 0.99 , 0.01 , max_history ) def draw ( self , frame , tracked_objects , coord_transform = None ): frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) for obj in tracked_objects : if not obj . live_points . any (): continue if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . get_estimate ( absolute = True )) for point in coord_transform . abs_to_rel ( points_to_draw ): Drawer . circle ( frame , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) last = points_to_draw for i , past_points in enumerate ( self . past_points [ obj . id ]): overlay = frame . copy () last = coord_transform . abs_to_rel ( last ) for j , point in enumerate ( coord_transform . abs_to_rel ( past_points )): Drawer . line ( overlay , tuple ( last [ j ] . astype ( int )), tuple ( point . astype ( int )), color = color , thickness = self . thickness , ) last = past_points alpha = self . alphas [ i ] frame = Drawer . alpha_blend ( overlay , frame , alpha = alpha ) self . past_points [ obj . id ] . insert ( 0 , points_to_draw ) self . past_points [ obj . id ] = self . past_points [ obj . id ][: self . max_history ] return frame","title":"AbsolutePaths"},{"location":"reference/drawing/#norfair.drawing.fixed_camera","text":"","title":"fixed_camera"},{"location":"reference/drawing/#norfair.drawing.fixed_camera.FixedCamera","text":"Class used to stabilize video based on the camera motion. Starts with a larger frame, where the original frame is drawn on top of a black background. As the camera moves, the smaller frame moves in the opposite direction, stabilizing the objects in it. Useful for debugging or demoing the camera motion. Warning This only works with TranslationTransformation , using HomographyTransformation will result in unexpected behaviour. Warning If using other drawers, always apply this one last. Using other drawers on the scaled up frame will not work as expected. Note Sometimes the camera moves so far from the original point that the result won't fit in the scaled-up frame. In this case, a warning will be logged and the frames will be cropped to avoid errors. Parameters: Name Type Description Default scale float , optional The resulting video will have a resolution of scale * (H, W) where HxW is the resolution of the original video. Use a bigger scale if the camera is moving too much. 2 attenuation float , optional Controls how fast the older frames fade to black. 0.05 Examples: >>> # setup >>> tracker = Tracker ( \"frobenious\" , 100 ) >>> motion_estimator = MotionEstimator () >>> video = Video ( input_path = \"video.mp4\" ) >>> fixed_camera = FixedCamera () >>> # process video >>> for frame in video : >>> coord_transformations = motion_estimator . update ( frame ) >>> detections = get_detections ( frame ) >>> tracked_objects = tracker . update ( detections , coord_transformations ) >>> draw_tracked_objects ( frame , tracked_objects ) # fixed_camera should always be the last drawer >>> bigger_frame = fixed_camera . adjust_frame ( frame , coord_transformations ) >>> video . write ( bigger_frame ) Source code in norfair/drawing/fixed_camera.py 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 class FixedCamera : \"\"\" Class used to stabilize video based on the camera motion. Starts with a larger frame, where the original frame is drawn on top of a black background. As the camera moves, the smaller frame moves in the opposite direction, stabilizing the objects in it. Useful for debugging or demoing the camera motion. ![Example GIF](../../videos/camera_stabilization.gif) !!! Warning This only works with [`TranslationTransformation`][norfair.camera_motion.TranslationTransformation], using [`HomographyTransformation`][norfair.camera_motion.HomographyTransformation] will result in unexpected behaviour. !!! Warning If using other drawers, always apply this one last. Using other drawers on the scaled up frame will not work as expected. !!! Note Sometimes the camera moves so far from the original point that the result won't fit in the scaled-up frame. In this case, a warning will be logged and the frames will be cropped to avoid errors. Parameters ---------- scale : float, optional The resulting video will have a resolution of `scale * (H, W)` where HxW is the resolution of the original video. Use a bigger scale if the camera is moving too much. attenuation : float, optional Controls how fast the older frames fade to black. Examples -------- >>> # setup >>> tracker = Tracker(\"frobenious\", 100) >>> motion_estimator = MotionEstimator() >>> video = Video(input_path=\"video.mp4\") >>> fixed_camera = FixedCamera() >>> # process video >>> for frame in video: >>> coord_transformations = motion_estimator.update(frame) >>> detections = get_detections(frame) >>> tracked_objects = tracker.update(detections, coord_transformations) >>> draw_tracked_objects(frame, tracked_objects) # fixed_camera should always be the last drawer >>> bigger_frame = fixed_camera.adjust_frame(frame, coord_transformations) >>> video.write(bigger_frame) \"\"\" def __init__ ( self , scale : float = 2 , attenuation : float = 0.05 ): self . scale = scale self . _background = None self . _attenuation_factor = 1 - attenuation def adjust_frame ( self , frame : np . ndarray , coord_transformation : TranslationTransformation ) -> np . ndarray : \"\"\" Render scaled up frame. Parameters ---------- frame : np.ndarray The OpenCV frame. coord_transformation : TranslationTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] Returns ------- np.ndarray The new bigger frame with the original frame drawn on it. \"\"\" # initialize background if necessary if self . _background is None : original_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) scaled_size = tuple ( ( np . array ( original_size ) * np . array ( self . scale )) . round () . astype ( int ) ) self . _background = np . zeros ( [ scaled_size [ 1 ], scaled_size [ 0 ], frame . shape [ - 1 ]], frame . dtype , ) else : self . _background = ( self . _background * self . _attenuation_factor ) . astype ( frame . dtype ) # top_left is the anchor coordinate from where we start drawing the fame on top of the background # aim to draw it in the center of the background but transformations will move this point top_left = ( np . array ( self . _background . shape [: 2 ]) // 2 - np . array ( frame . shape [: 2 ]) // 2 ) top_left = ( coord_transformation . rel_to_abs ( top_left [:: - 1 ]) . round () . astype ( int )[:: - 1 ] ) # box of the background that will be updated and the limits of it background_y0 , background_y1 = ( top_left [ 0 ], top_left [ 0 ] + frame . shape [ 0 ]) background_x0 , background_x1 = ( top_left [ 1 ], top_left [ 1 ] + frame . shape [ 1 ]) background_size_y , background_size_x = self . _background . shape [: 2 ] # define box of the frame that will be used # if the scale is not enough to support the movement, warn the user but keep drawing # cropping the frame so that the operation doesn't fail frame_y0 , frame_y1 , frame_x0 , frame_x1 = ( 0 , frame . shape [ 0 ], 0 , frame . shape [ 1 ]) if ( background_y0 < 0 or background_x0 < 0 or background_y1 > background_size_y or background_x1 > background_size_x ): warn_once ( \"moving_camera_scale is not enough to cover the range of camera movement, frame will be cropped\" ) # crop left or top of the frame if necessary frame_y0 = max ( - background_y0 , 0 ) frame_x0 = max ( - background_x0 , 0 ) # crop right or bottom of the frame if necessary frame_y1 = max ( min ( background_size_y - background_y0 , background_y1 - background_y0 ), 0 ) frame_x1 = max ( min ( background_size_x - background_x0 , background_x1 - background_x0 ), 0 ) # handle cases where the limits of the background become negative which numpy will interpret incorrectly background_y0 = max ( background_y0 , 0 ) background_x0 = max ( background_x0 , 0 ) background_y1 = max ( background_y1 , 0 ) background_x1 = max ( background_x1 , 0 ) self . _background [ background_y0 : background_y1 , background_x0 : background_x1 , : ] = frame [ frame_y0 : frame_y1 , frame_x0 : frame_x1 , :] return self . _background","title":"FixedCamera"},{"location":"reference/drawing/#norfair.drawing.fixed_camera.FixedCamera.adjust_frame","text":"Render scaled up frame. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame. required coord_transformation TranslationTransformation The coordinate transformation as returned by the MotionEstimator required Returns: Type Description np . ndarray The new bigger frame with the original frame drawn on it. Source code in norfair/drawing/fixed_camera.py 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 def adjust_frame ( self , frame : np . ndarray , coord_transformation : TranslationTransformation ) -> np . ndarray : \"\"\" Render scaled up frame. Parameters ---------- frame : np.ndarray The OpenCV frame. coord_transformation : TranslationTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] Returns ------- np.ndarray The new bigger frame with the original frame drawn on it. \"\"\" # initialize background if necessary if self . _background is None : original_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) scaled_size = tuple ( ( np . array ( original_size ) * np . array ( self . scale )) . round () . astype ( int ) ) self . _background = np . zeros ( [ scaled_size [ 1 ], scaled_size [ 0 ], frame . shape [ - 1 ]], frame . dtype , ) else : self . _background = ( self . _background * self . _attenuation_factor ) . astype ( frame . dtype ) # top_left is the anchor coordinate from where we start drawing the fame on top of the background # aim to draw it in the center of the background but transformations will move this point top_left = ( np . array ( self . _background . shape [: 2 ]) // 2 - np . array ( frame . shape [: 2 ]) // 2 ) top_left = ( coord_transformation . rel_to_abs ( top_left [:: - 1 ]) . round () . astype ( int )[:: - 1 ] ) # box of the background that will be updated and the limits of it background_y0 , background_y1 = ( top_left [ 0 ], top_left [ 0 ] + frame . shape [ 0 ]) background_x0 , background_x1 = ( top_left [ 1 ], top_left [ 1 ] + frame . shape [ 1 ]) background_size_y , background_size_x = self . _background . shape [: 2 ] # define box of the frame that will be used # if the scale is not enough to support the movement, warn the user but keep drawing # cropping the frame so that the operation doesn't fail frame_y0 , frame_y1 , frame_x0 , frame_x1 = ( 0 , frame . shape [ 0 ], 0 , frame . shape [ 1 ]) if ( background_y0 < 0 or background_x0 < 0 or background_y1 > background_size_y or background_x1 > background_size_x ): warn_once ( \"moving_camera_scale is not enough to cover the range of camera movement, frame will be cropped\" ) # crop left or top of the frame if necessary frame_y0 = max ( - background_y0 , 0 ) frame_x0 = max ( - background_x0 , 0 ) # crop right or bottom of the frame if necessary frame_y1 = max ( min ( background_size_y - background_y0 , background_y1 - background_y0 ), 0 ) frame_x1 = max ( min ( background_size_x - background_x0 , background_x1 - background_x0 ), 0 ) # handle cases where the limits of the background become negative which numpy will interpret incorrectly background_y0 = max ( background_y0 , 0 ) background_x0 = max ( background_x0 , 0 ) background_y1 = max ( background_y1 , 0 ) background_x1 = max ( background_x1 , 0 ) self . _background [ background_y0 : background_y1 , background_x0 : background_x1 , : ] = frame [ frame_y0 : frame_y1 , frame_x0 : frame_x1 , :] return self . _background","title":"adjust_frame()"},{"location":"reference/drawing/#norfair.drawing.absolute_grid","text":"","title":"absolute_grid"},{"location":"reference/drawing/#norfair.drawing.absolute_grid.draw_absolute_grid","text":"Draw a grid of points in absolute coordinates. Useful for debugging camera motion. The points are drawn as if the camera were in the center of a sphere and points are drawn in the intersection of latitude and longitude lines over the surface of the sphere. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. required coord_transformations CoordinatesTransformation The coordinate transformation as returned by the MotionEstimator required grid_size int , optional How many points to draw. 20 radius int , optional Size of each point. 2 thickness int , optional Thickness of each point 1 color ColorType , optional Color of the points. Color.black polar Bool , optional If True, the points on the first frame are drawn as if the camera were pointing to a pole (viewed from the center of the earth). By default, False is used which means the points are drawn as if the camera were pointing to the Equator. False Source code in norfair/drawing/absolute_grid.py 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 def draw_absolute_grid ( frame : np . ndarray , coord_transformations : CoordinatesTransformation , grid_size : int = 20 , radius : int = 2 , thickness : int = 1 , color : ColorType = Color . black , polar : bool = False , ): \"\"\" Draw a grid of points in absolute coordinates. Useful for debugging camera motion. The points are drawn as if the camera were in the center of a sphere and points are drawn in the intersection of latitude and longitude lines over the surface of the sphere. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. coord_transformations : CoordinatesTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] grid_size : int, optional How many points to draw. radius : int, optional Size of each point. thickness : int, optional Thickness of each point color : ColorType, optional Color of the points. polar : Bool, optional If True, the points on the first frame are drawn as if the camera were pointing to a pole (viewed from the center of the earth). By default, False is used which means the points are drawn as if the camera were pointing to the Equator. \"\"\" h , w , _ = frame . shape # get absolute points grid points = _get_grid ( grid_size , w , h , polar = polar ) # transform the points to relative coordinates if coord_transformations is None : points_transformed = points else : points_transformed = coord_transformations . abs_to_rel ( points ) # filter points that are not visible visible_points = points_transformed [ ( points_transformed <= np . array ([ w , h ])) . all ( axis = 1 ) & ( points_transformed >= 0 ) . all ( axis = 1 ) ] for point in visible_points : Drawer . cross ( frame , point . astype ( int ), radius = radius , thickness = thickness , color = color )","title":"draw_absolute_grid()"},{"location":"reference/filter/","text":"Filter # FilterPyKalmanFilterFactory # Bases: FilterFactory This class can be used either to change some parameters of the KalmanFilter that the tracker uses, or to fully customize the predictive filter implementation to use (as long as the methods and properties are compatible). The former case only requires changing the default parameters upon tracker creation: tracker = Tracker(..., filter_factory=FilterPyKalmanFilterFactory(R=100)) , while the latter requires creating your own class extending FilterPyKalmanFilterFactory , and rewriting its create_filter method to return your own customized filter. Parameters: Name Type Description Default R float , optional Multiplier for the sensor measurement noise matrix, by default 4.0 4.0 Q float , optional Multiplier for the process uncertainty, by default 0.1 0.1 P float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables, by default 10.0 10.0 See Also # filterpy.KalmanFilter . Source code in norfair/filter.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 class FilterPyKalmanFilterFactory ( FilterFactory ): \"\"\" This class can be used either to change some parameters of the [KalmanFilter](https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html) that the tracker uses, or to fully customize the predictive filter implementation to use (as long as the methods and properties are compatible). The former case only requires changing the default parameters upon tracker creation: `tracker = Tracker(..., filter_factory=FilterPyKalmanFilterFactory(R=100))`, while the latter requires creating your own class extending `FilterPyKalmanFilterFactory`, and rewriting its `create_filter` method to return your own customized filter. Parameters ---------- R : float, optional Multiplier for the sensor measurement noise matrix, by default 4.0 Q : float, optional Multiplier for the process uncertainty, by default 0.1 P : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables, by default 10.0 See Also -------- [`filterpy.KalmanFilter`](https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html). \"\"\" def __init__ ( self , R : float = 4.0 , Q : float = 0.1 , P : float = 10.0 ): self . R = R self . Q = Q self . P = P def create_filter ( self , initial_detection : np . ndarray ) -> KalmanFilter : \"\"\" This method returns a new predictive filter instance with the current setup, to be used by each new [`TrackedObject`][norfair.tracker.TrackedObject] that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters ---------- initial_detection : np.ndarray numpy array of shape `(number of points per object, 2)`, corresponding to the [`Detection.points`][norfair.tracker.Detection] of the tracked object being born, which shall be used as initial position estimation for it. Returns ------- KalmanFilter The kalman filter \"\"\" num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points dim_x = 2 * dim_z # We need to accommodate for velocities filter = KalmanFilter ( dim_x = dim_x , dim_z = dim_z ) # State transition matrix (models physics): numpy.array() filter . F = np . eye ( dim_x ) dt = 1 # At each step we update pos with v * dt filter . F [: dim_z , dim_z :] = dt * np . eye ( dim_z ) # Measurement function: numpy.array(dim_z, dim_x) filter . H = np . eye ( dim_z , dim_x , ) # Measurement uncertainty (sensor noise): numpy.array(dim_z, dim_z) filter . R *= self . R # Process uncertainty: numpy.array(dim_x, dim_x) # Don't decrease it too much or trackers pay too little attention to detections filter . Q [ dim_z :, dim_z :] *= self . Q # Initial state: numpy.array(dim_x, 1) filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T filter . x [ dim_z :] = 0 # Estimation uncertainty: numpy.array(dim_x, dim_x) filter . P [ dim_z :, dim_z :] *= self . P return filter create_filter ( initial_detection ) # This method returns a new predictive filter instance with the current setup, to be used by each new TrackedObject that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters: Name Type Description Default initial_detection np . ndarray numpy array of shape (number of points per object, 2) , corresponding to the Detection.points of the tracked object being born, which shall be used as initial position estimation for it. required Returns: Type Description KalmanFilter The kalman filter Source code in norfair/filter.py 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 def create_filter ( self , initial_detection : np . ndarray ) -> KalmanFilter : \"\"\" This method returns a new predictive filter instance with the current setup, to be used by each new [`TrackedObject`][norfair.tracker.TrackedObject] that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters ---------- initial_detection : np.ndarray numpy array of shape `(number of points per object, 2)`, corresponding to the [`Detection.points`][norfair.tracker.Detection] of the tracked object being born, which shall be used as initial position estimation for it. Returns ------- KalmanFilter The kalman filter \"\"\" num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points dim_x = 2 * dim_z # We need to accommodate for velocities filter = KalmanFilter ( dim_x = dim_x , dim_z = dim_z ) # State transition matrix (models physics): numpy.array() filter . F = np . eye ( dim_x ) dt = 1 # At each step we update pos with v * dt filter . F [: dim_z , dim_z :] = dt * np . eye ( dim_z ) # Measurement function: numpy.array(dim_z, dim_x) filter . H = np . eye ( dim_z , dim_x , ) # Measurement uncertainty (sensor noise): numpy.array(dim_z, dim_z) filter . R *= self . R # Process uncertainty: numpy.array(dim_x, dim_x) # Don't decrease it too much or trackers pay too little attention to detections filter . Q [ dim_z :, dim_z :] *= self . Q # Initial state: numpy.array(dim_x, 1) filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T filter . x [ dim_z :] = 0 # Estimation uncertainty: numpy.array(dim_x, dim_x) filter . P [ dim_z :, dim_z :] *= self . P return filter OptimizedKalmanFilterFactory # Bases: FilterFactory Creates faster Filters than FilterPyKalmanFilterFactory . It allows the user to create Kalman Filter optimized for tracking and set its parameters. Parameters: Name Type Description Default R float , optional Multiplier for the sensor measurement noise matrix. 4.0 Q float , optional Multiplier for the process uncertainty. 0.1 pos_variance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables. 10 pos_vel_covariance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to the covariance between position and speed. 0 vel_variance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to velocity (not position) variables. 1 Source code in norfair/filter.py 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 class OptimizedKalmanFilterFactory ( FilterFactory ): \"\"\" Creates faster Filters than [`FilterPyKalmanFilterFactory`][norfair.filter.FilterPyKalmanFilterFactory]. It allows the user to create Kalman Filter optimized for tracking and set its parameters. Parameters ---------- R : float, optional Multiplier for the sensor measurement noise matrix. Q : float, optional Multiplier for the process uncertainty. pos_variance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables. pos_vel_covariance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to the covariance between position and speed. vel_variance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to velocity (not position) variables. \"\"\" def __init__ ( self , R : float = 4.0 , Q : float = 0.1 , pos_variance : float = 10 , pos_vel_covariance : float = 0 , vel_variance : float = 1 , ): self . R = R self . Q = Q # entrances P matrix of KF self . pos_variance = pos_variance self . pos_vel_covariance = pos_vel_covariance self . vel_variance = vel_variance def create_filter ( self , initial_detection : np . ndarray ): num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points # flattened positions dim_x = 2 * dim_z # We need to accommodate for velocities custom_filter = OptimizedKalmanFilter ( dim_x , dim_z , pos_variance = self . pos_variance , pos_vel_covariance = self . pos_vel_covariance , vel_variance = self . vel_variance , q = self . Q , r = self . R , ) custom_filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T return custom_filter","title":"Filter"},{"location":"reference/filter/#filter","text":"","title":"Filter"},{"location":"reference/filter/#norfair.filter.FilterPyKalmanFilterFactory","text":"Bases: FilterFactory This class can be used either to change some parameters of the KalmanFilter that the tracker uses, or to fully customize the predictive filter implementation to use (as long as the methods and properties are compatible). The former case only requires changing the default parameters upon tracker creation: tracker = Tracker(..., filter_factory=FilterPyKalmanFilterFactory(R=100)) , while the latter requires creating your own class extending FilterPyKalmanFilterFactory , and rewriting its create_filter method to return your own customized filter. Parameters: Name Type Description Default R float , optional Multiplier for the sensor measurement noise matrix, by default 4.0 4.0 Q float , optional Multiplier for the process uncertainty, by default 0.1 0.1 P float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables, by default 10.0 10.0","title":"FilterPyKalmanFilterFactory"},{"location":"reference/filter/#norfair.filter.FilterPyKalmanFilterFactory--see-also","text":"filterpy.KalmanFilter . Source code in norfair/filter.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 class FilterPyKalmanFilterFactory ( FilterFactory ): \"\"\" This class can be used either to change some parameters of the [KalmanFilter](https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html) that the tracker uses, or to fully customize the predictive filter implementation to use (as long as the methods and properties are compatible). The former case only requires changing the default parameters upon tracker creation: `tracker = Tracker(..., filter_factory=FilterPyKalmanFilterFactory(R=100))`, while the latter requires creating your own class extending `FilterPyKalmanFilterFactory`, and rewriting its `create_filter` method to return your own customized filter. Parameters ---------- R : float, optional Multiplier for the sensor measurement noise matrix, by default 4.0 Q : float, optional Multiplier for the process uncertainty, by default 0.1 P : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables, by default 10.0 See Also -------- [`filterpy.KalmanFilter`](https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html). \"\"\" def __init__ ( self , R : float = 4.0 , Q : float = 0.1 , P : float = 10.0 ): self . R = R self . Q = Q self . P = P def create_filter ( self , initial_detection : np . ndarray ) -> KalmanFilter : \"\"\" This method returns a new predictive filter instance with the current setup, to be used by each new [`TrackedObject`][norfair.tracker.TrackedObject] that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters ---------- initial_detection : np.ndarray numpy array of shape `(number of points per object, 2)`, corresponding to the [`Detection.points`][norfair.tracker.Detection] of the tracked object being born, which shall be used as initial position estimation for it. Returns ------- KalmanFilter The kalman filter \"\"\" num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points dim_x = 2 * dim_z # We need to accommodate for velocities filter = KalmanFilter ( dim_x = dim_x , dim_z = dim_z ) # State transition matrix (models physics): numpy.array() filter . F = np . eye ( dim_x ) dt = 1 # At each step we update pos with v * dt filter . F [: dim_z , dim_z :] = dt * np . eye ( dim_z ) # Measurement function: numpy.array(dim_z, dim_x) filter . H = np . eye ( dim_z , dim_x , ) # Measurement uncertainty (sensor noise): numpy.array(dim_z, dim_z) filter . R *= self . R # Process uncertainty: numpy.array(dim_x, dim_x) # Don't decrease it too much or trackers pay too little attention to detections filter . Q [ dim_z :, dim_z :] *= self . Q # Initial state: numpy.array(dim_x, 1) filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T filter . x [ dim_z :] = 0 # Estimation uncertainty: numpy.array(dim_x, dim_x) filter . P [ dim_z :, dim_z :] *= self . P return filter","title":"See Also"},{"location":"reference/filter/#norfair.filter.FilterPyKalmanFilterFactory.create_filter","text":"This method returns a new predictive filter instance with the current setup, to be used by each new TrackedObject that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters: Name Type Description Default initial_detection np . ndarray numpy array of shape (number of points per object, 2) , corresponding to the Detection.points of the tracked object being born, which shall be used as initial position estimation for it. required Returns: Type Description KalmanFilter The kalman filter Source code in norfair/filter.py 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 def create_filter ( self , initial_detection : np . ndarray ) -> KalmanFilter : \"\"\" This method returns a new predictive filter instance with the current setup, to be used by each new [`TrackedObject`][norfair.tracker.TrackedObject] that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters ---------- initial_detection : np.ndarray numpy array of shape `(number of points per object, 2)`, corresponding to the [`Detection.points`][norfair.tracker.Detection] of the tracked object being born, which shall be used as initial position estimation for it. Returns ------- KalmanFilter The kalman filter \"\"\" num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points dim_x = 2 * dim_z # We need to accommodate for velocities filter = KalmanFilter ( dim_x = dim_x , dim_z = dim_z ) # State transition matrix (models physics): numpy.array() filter . F = np . eye ( dim_x ) dt = 1 # At each step we update pos with v * dt filter . F [: dim_z , dim_z :] = dt * np . eye ( dim_z ) # Measurement function: numpy.array(dim_z, dim_x) filter . H = np . eye ( dim_z , dim_x , ) # Measurement uncertainty (sensor noise): numpy.array(dim_z, dim_z) filter . R *= self . R # Process uncertainty: numpy.array(dim_x, dim_x) # Don't decrease it too much or trackers pay too little attention to detections filter . Q [ dim_z :, dim_z :] *= self . Q # Initial state: numpy.array(dim_x, 1) filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T filter . x [ dim_z :] = 0 # Estimation uncertainty: numpy.array(dim_x, dim_x) filter . P [ dim_z :, dim_z :] *= self . P return filter","title":"create_filter()"},{"location":"reference/filter/#norfair.filter.OptimizedKalmanFilterFactory","text":"Bases: FilterFactory Creates faster Filters than FilterPyKalmanFilterFactory . It allows the user to create Kalman Filter optimized for tracking and set its parameters. Parameters: Name Type Description Default R float , optional Multiplier for the sensor measurement noise matrix. 4.0 Q float , optional Multiplier for the process uncertainty. 0.1 pos_variance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables. 10 pos_vel_covariance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to the covariance between position and speed. 0 vel_variance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to velocity (not position) variables. 1 Source code in norfair/filter.py 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 class OptimizedKalmanFilterFactory ( FilterFactory ): \"\"\" Creates faster Filters than [`FilterPyKalmanFilterFactory`][norfair.filter.FilterPyKalmanFilterFactory]. It allows the user to create Kalman Filter optimized for tracking and set its parameters. Parameters ---------- R : float, optional Multiplier for the sensor measurement noise matrix. Q : float, optional Multiplier for the process uncertainty. pos_variance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables. pos_vel_covariance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to the covariance between position and speed. vel_variance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to velocity (not position) variables. \"\"\" def __init__ ( self , R : float = 4.0 , Q : float = 0.1 , pos_variance : float = 10 , pos_vel_covariance : float = 0 , vel_variance : float = 1 , ): self . R = R self . Q = Q # entrances P matrix of KF self . pos_variance = pos_variance self . pos_vel_covariance = pos_vel_covariance self . vel_variance = vel_variance def create_filter ( self , initial_detection : np . ndarray ): num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points # flattened positions dim_x = 2 * dim_z # We need to accommodate for velocities custom_filter = OptimizedKalmanFilter ( dim_x , dim_z , pos_variance = self . pos_variance , pos_vel_covariance = self . pos_vel_covariance , vel_variance = self . vel_variance , q = self . Q , r = self . R , ) custom_filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T return custom_filter","title":"OptimizedKalmanFilterFactory"},{"location":"reference/metrics/","text":"Metrics # PredictionsTextFile # Generates a text file with your predicted tracked objects, in the MOTChallenge format. It needs the 'input_path', which is the path to the sequence being processed, the 'save_path', and optionally the 'information_file' (in case you don't give an 'information_file', is assumed there is one in the input_path folder). Source code in norfair/metrics.py 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 class PredictionsTextFile : \"\"\"Generates a text file with your predicted tracked objects, in the MOTChallenge format. It needs the 'input_path', which is the path to the sequence being processed, the 'save_path', and optionally the 'information_file' (in case you don't give an 'information_file', is assumed there is one in the input_path folder). \"\"\" def __init__ ( self , input_path , save_path = \".\" , information_file = None ): file_name = os . path . split ( input_path )[ 1 ] if information_file is None : seqinfo_path = os . path . join ( input_path , \"seqinfo.ini\" ) information_file = InformationFile ( file_path = seqinfo_path ) self . length = information_file . search ( variable_name = \"seqLength\" ) predictions_folder = os . path . join ( save_path , \"predictions\" ) if not os . path . exists ( predictions_folder ): os . makedirs ( predictions_folder ) out_file_name = os . path . join ( predictions_folder , file_name + \".txt\" ) self . text_file = open ( out_file_name , \"w+\" ) self . frame_number = 1 def update ( self , predictions , frame_number = None ): if frame_number is None : frame_number = self . frame_number \"\"\" Write tracked object information in the output file (for this frame), in the format frame_number, id, bb_left, bb_top, bb_width, bb_height, -1, -1, -1, -1 \"\"\" for obj in predictions : frame_str = str ( int ( frame_number )) id_str = str ( int ( obj . id )) bb_left_str = str (( obj . estimate [ 0 , 0 ])) bb_top_str = str (( obj . estimate [ 0 , 1 ])) # [0,1] bb_width_str = str (( obj . estimate [ 1 , 0 ] - obj . estimate [ 0 , 0 ])) bb_height_str = str (( obj . estimate [ 1 , 1 ] - obj . estimate [ 0 , 1 ])) row_text_out = ( frame_str + \",\" + id_str + \",\" + bb_left_str + \",\" + bb_top_str + \",\" + bb_width_str + \",\" + bb_height_str + \",-1,-1,-1,-1\" ) self . text_file . write ( row_text_out ) self . text_file . write ( \" \\n \" ) self . frame_number += 1 if self . frame_number > self . length : self . text_file . close () DetectionFileParser # Get Norfair detections from MOTChallenge text files containing detections Source code in norfair/metrics.py 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 class DetectionFileParser : \"\"\"Get Norfair detections from MOTChallenge text files containing detections\"\"\" def __init__ ( self , input_path , information_file = None ): self . frame_number = 1 # Get detecions matrix data with rows corresponding to: # frame, id, bb_left, bb_top, bb_right, bb_down, conf, x, y, z detections_path = os . path . join ( input_path , \"det/det.txt\" ) self . matrix_detections = np . loadtxt ( detections_path , dtype = \"f\" , delimiter = \",\" ) row_order = np . argsort ( self . matrix_detections [:, 0 ]) self . matrix_detections = self . matrix_detections [ row_order ] # Coordinates refer to box corners self . matrix_detections [:, 4 ] = ( self . matrix_detections [:, 2 ] + self . matrix_detections [:, 4 ] ) self . matrix_detections [:, 5 ] = ( self . matrix_detections [:, 3 ] + self . matrix_detections [:, 5 ] ) if information_file is None : seqinfo_path = os . path . join ( input_path , \"seqinfo.ini\" ) information_file = InformationFile ( file_path = seqinfo_path ) self . length = information_file . search ( variable_name = \"seqLength\" ) self . sorted_by_frame = [] for frame_number in range ( 1 , self . length + 1 ): self . sorted_by_frame . append ( self . get_dets_from_frame ( frame_number )) def get_dets_from_frame ( self , frame_number ): \"\"\"this function returns a list of norfair Detections class, corresponding to frame=frame_number\"\"\" indexes = np . argwhere ( self . matrix_detections [:, 0 ] == frame_number ) detections = [] if len ( indexes ) > 0 : actual_det = self . matrix_detections [ indexes ] actual_det . shape = [ actual_det . shape [ 0 ], actual_det . shape [ 2 ]] for det in actual_det : points = np . array ([[ det [ 2 ], det [ 3 ]], [ det [ 4 ], det [ 5 ]]]) conf = det [ 6 ] new_detection = Detection ( points , np . array ([ conf , conf ])) detections . append ( new_detection ) self . actual_detections = detections return detections def __iter__ ( self ): self . frame_number = 1 return self def __next__ ( self ): if self . frame_number <= self . length : self . frame_number += 1 # Frame_number is always 1 unit bigger than the corresponding index in self.sorted_by_frame, and # also we just incremented the frame_number, so now is 2 units bigger than the corresponding index return self . sorted_by_frame [ self . frame_number - 2 ] raise StopIteration () get_dets_from_frame ( frame_number ) # this function returns a list of norfair Detections class, corresponding to frame=frame_number Source code in norfair/metrics.py 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 def get_dets_from_frame ( self , frame_number ): \"\"\"this function returns a list of norfair Detections class, corresponding to frame=frame_number\"\"\" indexes = np . argwhere ( self . matrix_detections [:, 0 ] == frame_number ) detections = [] if len ( indexes ) > 0 : actual_det = self . matrix_detections [ indexes ] actual_det . shape = [ actual_det . shape [ 0 ], actual_det . shape [ 2 ]] for det in actual_det : points = np . array ([[ det [ 2 ], det [ 3 ]], [ det [ 4 ], det [ 5 ]]]) conf = det [ 6 ] new_detection = Detection ( points , np . array ([ conf , conf ])) detections . append ( new_detection ) self . actual_detections = detections return detections load_motchallenge ( matrix_data , min_confidence =- 1 ) # Load MOT challenge data. This is a modification of the function load_motchallenge from the py-motmetrics library, defined in io.py In this version, the pandas dataframe is generated from a numpy array (matrix_data) instead of a text file. Params # matrix_data : array of float that has [frame, id, X, Y, width, height, conf, cassId, visibility] in each row, for each prediction on a particular video min_confidence : float Rows with confidence less than this threshold are removed. Defaults to -1. You should set this to 1 when loading ground truth MOTChallenge data, so that invalid rectangles in the ground truth are not considered during matching. Returns: Name Type Description df pandas . DataFrame The returned dataframe has the following columns 'X', 'Y', 'Width', 'Height', 'Confidence', 'ClassId', 'Visibility' The dataframe is indexed by ('FrameId', 'Id') Source code in norfair/metrics.py 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 def load_motchallenge ( matrix_data , min_confidence =- 1 ): \"\"\"Load MOT challenge data. This is a modification of the function load_motchallenge from the py-motmetrics library, defined in io.py In this version, the pandas dataframe is generated from a numpy array (matrix_data) instead of a text file. Params ------ matrix_data : array of float that has [frame, id, X, Y, width, height, conf, cassId, visibility] in each row, for each prediction on a particular video min_confidence : float Rows with confidence less than this threshold are removed. Defaults to -1. You should set this to 1 when loading ground truth MOTChallenge data, so that invalid rectangles in the ground truth are not considered during matching. Returns ------ df : pandas.DataFrame The returned dataframe has the following columns 'X', 'Y', 'Width', 'Height', 'Confidence', 'ClassId', 'Visibility' The dataframe is indexed by ('FrameId', 'Id') \"\"\" df = pd . DataFrame ( data = matrix_data , columns = [ \"FrameId\" , \"Id\" , \"X\" , \"Y\" , \"Width\" , \"Height\" , \"Confidence\" , \"ClassId\" , \"Visibility\" , \"unused\" , ], ) df = df . set_index ([ \"FrameId\" , \"Id\" ]) # Account for matlab convention. df [[ \"X\" , \"Y\" ]] -= ( 1 , 1 ) # Removed trailing column del df [ \"unused\" ] # Remove all rows without sufficient confidence return df [ df [ \"Confidence\" ] >= min_confidence ] compare_dataframes ( gts , ts ) # Builds accumulator for each sequence. Source code in norfair/metrics.py 297 298 299 300 301 302 303 304 305 306 307 308 309 def compare_dataframes ( gts , ts ): \"\"\"Builds accumulator for each sequence.\"\"\" accs = [] names = [] for k , tsacc in ts . items (): print ( \"Comparing \" , k , \"...\" ) if k in gts : accs . append ( mm . utils . compare_to_groundtruth ( gts [ k ], tsacc , \"iou\" , distth = 0.5 ) ) names . append ( k ) return accs , names","title":"Metrics"},{"location":"reference/metrics/#metrics","text":"","title":"Metrics"},{"location":"reference/metrics/#norfair.metrics.PredictionsTextFile","text":"Generates a text file with your predicted tracked objects, in the MOTChallenge format. It needs the 'input_path', which is the path to the sequence being processed, the 'save_path', and optionally the 'information_file' (in case you don't give an 'information_file', is assumed there is one in the input_path folder). Source code in norfair/metrics.py 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 class PredictionsTextFile : \"\"\"Generates a text file with your predicted tracked objects, in the MOTChallenge format. It needs the 'input_path', which is the path to the sequence being processed, the 'save_path', and optionally the 'information_file' (in case you don't give an 'information_file', is assumed there is one in the input_path folder). \"\"\" def __init__ ( self , input_path , save_path = \".\" , information_file = None ): file_name = os . path . split ( input_path )[ 1 ] if information_file is None : seqinfo_path = os . path . join ( input_path , \"seqinfo.ini\" ) information_file = InformationFile ( file_path = seqinfo_path ) self . length = information_file . search ( variable_name = \"seqLength\" ) predictions_folder = os . path . join ( save_path , \"predictions\" ) if not os . path . exists ( predictions_folder ): os . makedirs ( predictions_folder ) out_file_name = os . path . join ( predictions_folder , file_name + \".txt\" ) self . text_file = open ( out_file_name , \"w+\" ) self . frame_number = 1 def update ( self , predictions , frame_number = None ): if frame_number is None : frame_number = self . frame_number \"\"\" Write tracked object information in the output file (for this frame), in the format frame_number, id, bb_left, bb_top, bb_width, bb_height, -1, -1, -1, -1 \"\"\" for obj in predictions : frame_str = str ( int ( frame_number )) id_str = str ( int ( obj . id )) bb_left_str = str (( obj . estimate [ 0 , 0 ])) bb_top_str = str (( obj . estimate [ 0 , 1 ])) # [0,1] bb_width_str = str (( obj . estimate [ 1 , 0 ] - obj . estimate [ 0 , 0 ])) bb_height_str = str (( obj . estimate [ 1 , 1 ] - obj . estimate [ 0 , 1 ])) row_text_out = ( frame_str + \",\" + id_str + \",\" + bb_left_str + \",\" + bb_top_str + \",\" + bb_width_str + \",\" + bb_height_str + \",-1,-1,-1,-1\" ) self . text_file . write ( row_text_out ) self . text_file . write ( \" \\n \" ) self . frame_number += 1 if self . frame_number > self . length : self . text_file . close ()","title":"PredictionsTextFile"},{"location":"reference/metrics/#norfair.metrics.DetectionFileParser","text":"Get Norfair detections from MOTChallenge text files containing detections Source code in norfair/metrics.py 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 class DetectionFileParser : \"\"\"Get Norfair detections from MOTChallenge text files containing detections\"\"\" def __init__ ( self , input_path , information_file = None ): self . frame_number = 1 # Get detecions matrix data with rows corresponding to: # frame, id, bb_left, bb_top, bb_right, bb_down, conf, x, y, z detections_path = os . path . join ( input_path , \"det/det.txt\" ) self . matrix_detections = np . loadtxt ( detections_path , dtype = \"f\" , delimiter = \",\" ) row_order = np . argsort ( self . matrix_detections [:, 0 ]) self . matrix_detections = self . matrix_detections [ row_order ] # Coordinates refer to box corners self . matrix_detections [:, 4 ] = ( self . matrix_detections [:, 2 ] + self . matrix_detections [:, 4 ] ) self . matrix_detections [:, 5 ] = ( self . matrix_detections [:, 3 ] + self . matrix_detections [:, 5 ] ) if information_file is None : seqinfo_path = os . path . join ( input_path , \"seqinfo.ini\" ) information_file = InformationFile ( file_path = seqinfo_path ) self . length = information_file . search ( variable_name = \"seqLength\" ) self . sorted_by_frame = [] for frame_number in range ( 1 , self . length + 1 ): self . sorted_by_frame . append ( self . get_dets_from_frame ( frame_number )) def get_dets_from_frame ( self , frame_number ): \"\"\"this function returns a list of norfair Detections class, corresponding to frame=frame_number\"\"\" indexes = np . argwhere ( self . matrix_detections [:, 0 ] == frame_number ) detections = [] if len ( indexes ) > 0 : actual_det = self . matrix_detections [ indexes ] actual_det . shape = [ actual_det . shape [ 0 ], actual_det . shape [ 2 ]] for det in actual_det : points = np . array ([[ det [ 2 ], det [ 3 ]], [ det [ 4 ], det [ 5 ]]]) conf = det [ 6 ] new_detection = Detection ( points , np . array ([ conf , conf ])) detections . append ( new_detection ) self . actual_detections = detections return detections def __iter__ ( self ): self . frame_number = 1 return self def __next__ ( self ): if self . frame_number <= self . length : self . frame_number += 1 # Frame_number is always 1 unit bigger than the corresponding index in self.sorted_by_frame, and # also we just incremented the frame_number, so now is 2 units bigger than the corresponding index return self . sorted_by_frame [ self . frame_number - 2 ] raise StopIteration ()","title":"DetectionFileParser"},{"location":"reference/metrics/#norfair.metrics.DetectionFileParser.get_dets_from_frame","text":"this function returns a list of norfair Detections class, corresponding to frame=frame_number Source code in norfair/metrics.py 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 def get_dets_from_frame ( self , frame_number ): \"\"\"this function returns a list of norfair Detections class, corresponding to frame=frame_number\"\"\" indexes = np . argwhere ( self . matrix_detections [:, 0 ] == frame_number ) detections = [] if len ( indexes ) > 0 : actual_det = self . matrix_detections [ indexes ] actual_det . shape = [ actual_det . shape [ 0 ], actual_det . shape [ 2 ]] for det in actual_det : points = np . array ([[ det [ 2 ], det [ 3 ]], [ det [ 4 ], det [ 5 ]]]) conf = det [ 6 ] new_detection = Detection ( points , np . array ([ conf , conf ])) detections . append ( new_detection ) self . actual_detections = detections return detections","title":"get_dets_from_frame()"},{"location":"reference/metrics/#norfair.metrics.load_motchallenge","text":"Load MOT challenge data. This is a modification of the function load_motchallenge from the py-motmetrics library, defined in io.py In this version, the pandas dataframe is generated from a numpy array (matrix_data) instead of a text file.","title":"load_motchallenge()"},{"location":"reference/metrics/#norfair.metrics.load_motchallenge--params","text":"matrix_data : array of float that has [frame, id, X, Y, width, height, conf, cassId, visibility] in each row, for each prediction on a particular video min_confidence : float Rows with confidence less than this threshold are removed. Defaults to -1. You should set this to 1 when loading ground truth MOTChallenge data, so that invalid rectangles in the ground truth are not considered during matching. Returns: Name Type Description df pandas . DataFrame The returned dataframe has the following columns 'X', 'Y', 'Width', 'Height', 'Confidence', 'ClassId', 'Visibility' The dataframe is indexed by ('FrameId', 'Id') Source code in norfair/metrics.py 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 def load_motchallenge ( matrix_data , min_confidence =- 1 ): \"\"\"Load MOT challenge data. This is a modification of the function load_motchallenge from the py-motmetrics library, defined in io.py In this version, the pandas dataframe is generated from a numpy array (matrix_data) instead of a text file. Params ------ matrix_data : array of float that has [frame, id, X, Y, width, height, conf, cassId, visibility] in each row, for each prediction on a particular video min_confidence : float Rows with confidence less than this threshold are removed. Defaults to -1. You should set this to 1 when loading ground truth MOTChallenge data, so that invalid rectangles in the ground truth are not considered during matching. Returns ------ df : pandas.DataFrame The returned dataframe has the following columns 'X', 'Y', 'Width', 'Height', 'Confidence', 'ClassId', 'Visibility' The dataframe is indexed by ('FrameId', 'Id') \"\"\" df = pd . DataFrame ( data = matrix_data , columns = [ \"FrameId\" , \"Id\" , \"X\" , \"Y\" , \"Width\" , \"Height\" , \"Confidence\" , \"ClassId\" , \"Visibility\" , \"unused\" , ], ) df = df . set_index ([ \"FrameId\" , \"Id\" ]) # Account for matlab convention. df [[ \"X\" , \"Y\" ]] -= ( 1 , 1 ) # Removed trailing column del df [ \"unused\" ] # Remove all rows without sufficient confidence return df [ df [ \"Confidence\" ] >= min_confidence ]","title":"Params"},{"location":"reference/metrics/#norfair.metrics.compare_dataframes","text":"Builds accumulator for each sequence. Source code in norfair/metrics.py 297 298 299 300 301 302 303 304 305 306 307 308 309 def compare_dataframes ( gts , ts ): \"\"\"Builds accumulator for each sequence.\"\"\" accs = [] names = [] for k , tsacc in ts . items (): print ( \"Comparing \" , k , \"...\" ) if k in gts : accs . append ( mm . utils . compare_to_groundtruth ( gts [ k ], tsacc , \"iou\" , distth = 0.5 ) ) names . append ( k ) return accs , names","title":"compare_dataframes()"},{"location":"reference/tracker/","text":"Tracker # Tracker # The class in charge of performing the tracking of the detections produced by a detector. Parameters: Name Type Description Default distance_function Union [ str , Callable [[ Detection , TrackedObject ], float ]] Function used by the tracker to determine the distance between newly detected objects and the objects that are currently being tracked. This function should take 2 input arguments, the first being a Detection , and the second a TrackedObject . It has to return a float with the distance it calculates. Some common distances are implemented in distances , as a shortcut the tracker accepts the name of these predefined distances . Scipy's predefined distances are also accepted. A str with one of the available metrics in scipy.spatial.distance.cdist . required distance_threshold float Defines what is the maximum distance that can constitute a match. Detections and tracked objects whose distances are above this threshold won't be matched by the tracker. required hit_counter_max int , optional Each tracked objects keeps an internal hit counter which tracks how often it's getting matched to a detection, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. This argument defines how large this inertia can grow, and therefore defines how long an object can live without getting matched to any detections, before it is displaced as a dead object, if no ReID distance function is implemented it will be destroyed. 15 initialization_delay Optional [ int ], optional Determines how large the object's hit counter must be in order to be considered as initialized, and get returned to the user as a real object. It must be smaller than hit_counter_max or otherwise the object would never be initialized. If set to 0, objects will get returned to the user as soon as they are detected for the first time, which can be problematic as this can result in objects appearing and immediately dissapearing. Defaults to hit_counter_max / 2 None pointwise_hit_counter_max int , optional Each tracked object keeps track of how often the points it's tracking have been getting matched. Points that are getting matched ( pointwise_hit_counter > 0 ) are said to be live, and points which aren't ( pointwise_hit_counter = 0 ) are said to not be live. This is used to determine things like which individual points in a tracked object get drawn by draw_tracked_objects and which don't. This argument defines how large the inertia for each point of a tracker can grow. 4 detection_threshold float , optional Sets the threshold at which the scores of the points in a detection being fed into the tracker must dip below to be ignored by the tracker. 0 filter_factory FilterFactory , optional This parameter can be used to change what filter the TrackedObject instances created by the tracker will use. Defaults to OptimizedKalmanFilterFactory() OptimizedKalmanFilterFactory() past_detections_length int , optional How many past detections to save for each tracked object. Norfair tries to distribute these past detections uniformly through the object's lifetime so they're more representative. Very useful if you want to add metric learning to your model, as you can associate an embedding to each detection and access them in your distance function. 4 reid_distance_function Optional [ Callable [[ TrackedObject , TrackedObject ], float ]] Function used by the tracker to determine the ReID distance between newly detected trackers and unmatched trackers by the distance function. This function should take 2 input arguments, the first being tracked objects in the initialization phase of type TrackedObject , and the second being tracked objects that have been unmatched of type TrackedObject . It returns a float with the distance it calculates. None reid_distance_threshold float Defines what is the maximum ReID distance that can constitute a match. Tracked objects whose distance is above this threshold won't be merged, if they are the oldest tracked object will be maintained with the position of the new tracked object. 0 reid_hit_counter_max Optional [ int ] Each tracked object keeps an internal ReID hit counter which tracks how often it's getting recognized by another tracker, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. If used, this argument ( reid_hit_counter_max ) defines how long an object can live without getting matched to any detections, before it is destroyed. None Source code in norfair/tracker.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 class Tracker : \"\"\" The class in charge of performing the tracking of the detections produced by a detector. Parameters ---------- distance_function : Union[str, Callable[[Detection, TrackedObject], float]] Function used by the tracker to determine the distance between newly detected objects and the objects that are currently being tracked. This function should take 2 input arguments, the first being a [Detection][norfair.tracker.Detection], and the second a [TrackedObject][norfair.tracker.TrackedObject]. It has to return a `float` with the distance it calculates. Some common distances are implemented in [distances][], as a shortcut the tracker accepts the name of these [predefined distances][norfair.distances.get_distance_by_name]. Scipy's predefined distances are also accepted. A `str` with one of the available metrics in [`scipy.spatial.distance.cdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html). distance_threshold : float Defines what is the maximum distance that can constitute a match. Detections and tracked objects whose distances are above this threshold won't be matched by the tracker. hit_counter_max : int, optional Each tracked objects keeps an internal hit counter which tracks how often it's getting matched to a detection, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. This argument defines how large this inertia can grow, and therefore defines how long an object can live without getting matched to any detections, before it is displaced as a dead object, if no ReID distance function is implemented it will be destroyed. initialization_delay : Optional[int], optional Determines how large the object's hit counter must be in order to be considered as initialized, and get returned to the user as a real object. It must be smaller than `hit_counter_max` or otherwise the object would never be initialized. If set to 0, objects will get returned to the user as soon as they are detected for the first time, which can be problematic as this can result in objects appearing and immediately dissapearing. Defaults to `hit_counter_max / 2` pointwise_hit_counter_max : int, optional Each tracked object keeps track of how often the points it's tracking have been getting matched. Points that are getting matched (`pointwise_hit_counter > 0`) are said to be live, and points which aren't (`pointwise_hit_counter = 0`) are said to not be live. This is used to determine things like which individual points in a tracked object get drawn by [`draw_tracked_objects`][norfair.drawing.draw_tracked_objects] and which don't. This argument defines how large the inertia for each point of a tracker can grow. detection_threshold : float, optional Sets the threshold at which the scores of the points in a detection being fed into the tracker must dip below to be ignored by the tracker. filter_factory : FilterFactory, optional This parameter can be used to change what filter the [`TrackedObject`][norfair.tracker.TrackedObject] instances created by the tracker will use. Defaults to [`OptimizedKalmanFilterFactory()`][norfair.filter.OptimizedKalmanFilterFactory] past_detections_length : int, optional How many past detections to save for each tracked object. Norfair tries to distribute these past detections uniformly through the object's lifetime so they're more representative. Very useful if you want to add metric learning to your model, as you can associate an embedding to each detection and access them in your distance function. reid_distance_function: Optional[Callable[[\"TrackedObject\", \"TrackedObject\"], float]] Function used by the tracker to determine the ReID distance between newly detected trackers and unmatched trackers by the distance function. This function should take 2 input arguments, the first being tracked objects in the initialization phase of type [`TrackedObject`][norfair.tracker.TrackedObject], and the second being tracked objects that have been unmatched of type [`TrackedObject`][norfair.tracker.TrackedObject]. It returns a `float` with the distance it calculates. reid_distance_threshold: float Defines what is the maximum ReID distance that can constitute a match. Tracked objects whose distance is above this threshold won't be merged, if they are the oldest tracked object will be maintained with the position of the new tracked object. reid_hit_counter_max: Optional[int] Each tracked object keeps an internal ReID hit counter which tracks how often it's getting recognized by another tracker, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. If used, this argument (`reid_hit_counter_max`) defines how long an object can live without getting matched to any detections, before it is destroyed. \"\"\" def __init__ ( self , distance_function : Union [ str , Callable [[ \"Detection\" , \"TrackedObject\" ], float ]], distance_threshold : float , hit_counter_max : int = 15 , initialization_delay : Optional [ int ] = None , pointwise_hit_counter_max : int = 4 , detection_threshold : float = 0 , filter_factory : FilterFactory = OptimizedKalmanFilterFactory (), past_detections_length : int = 4 , reid_distance_function : Optional [ Callable [[ \"TrackedObject\" , \"TrackedObject\" ], float ] ] = None , reid_distance_threshold : float = 0 , reid_hit_counter_max : Optional [ int ] = None , ): self . tracked_objects : Sequence [ \"TrackedObject\" ] = [] if isinstance ( distance_function , str ): distance_function = get_distance_by_name ( distance_function ) elif isinstance ( distance_function , Callable ): warning ( \"You are using a scalar distance function. If you want to speed up the\" \" tracking process please consider using a vectorized distance\" f \" function such as { AVAILABLE_VECTORIZED_DISTANCES } .\" ) distance_function = ScalarDistance ( distance_function ) else : raise ValueError ( \"Argument `distance_function` should be a string or function but is\" f \" { type ( distance_function ) } instead.\" ) self . distance_function = distance_function self . hit_counter_max = hit_counter_max self . reid_hit_counter_max = reid_hit_counter_max self . pointwise_hit_counter_max = pointwise_hit_counter_max self . filter_factory = filter_factory if past_detections_length >= 0 : self . past_detections_length = past_detections_length else : raise ValueError ( f \"Argument `past_detections_length` is { past_detections_length } and should be larger than 0.\" ) if initialization_delay is None : self . initialization_delay = int ( self . hit_counter_max / 2 ) elif initialization_delay < 0 or initialization_delay >= self . hit_counter_max : raise ValueError ( f \"Argument 'initialization_delay' for 'Tracker' class should be an int between 0 and (hit_counter_max = { hit_counter_max } ). The selected value is { initialization_delay } . \\n \" ) else : self . initialization_delay = initialization_delay self . distance_threshold = distance_threshold self . detection_threshold = detection_threshold if reid_distance_function is not None : self . reid_distance_function = ScalarDistance ( reid_distance_function ) else : self . reid_distance_function = reid_distance_function self . reid_distance_threshold = reid_distance_threshold self . _obj_factory = _TrackedObjectFactory () def update ( self , detections : Optional [ List [ \"Detection\" ]] = None , period : int = 1 , coord_transformations : Optional [ CoordinatesTransformation ] = None , ) -> List [ \"TrackedObject\" ]: \"\"\" Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters ---------- detections : Optional[List[Detection]], optional A list of [`Detection`][norfair.tracker.Detection] which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. period : int, optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. coord_transformations: Optional[CoordinatesTransformation] The coordinate transformation calculated by the [MotionEstimator][norfair.camera_motion.MotionEstimator]. Returns ------- List[TrackedObject] The list of active tracked objects. \"\"\" if coord_transformations is not None : for det in detections : det . update_coordinate_transformation ( coord_transformations ) # Remove stale trackers and make candidate object real if the hit counter is positive alive_objects = [] dead_objects = [] if self . reid_hit_counter_max is None : self . tracked_objects = [ o for o in self . tracked_objects if o . hit_counter_is_positive ] alive_objects = self . tracked_objects else : tracked_objects = [] for o in self . tracked_objects : if o . reid_hit_counter_is_positive : tracked_objects . append ( o ) if o . hit_counter_is_positive : alive_objects . append ( o ) else : dead_objects . append ( o ) self . tracked_objects = tracked_objects # Update tracker for obj in self . tracked_objects : obj . tracker_step () obj . update_coordinate_transformation ( coord_transformations ) # Update initialized tracked objects with detections ( unmatched_detections , _ , unmatched_init_trackers , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if not o . is_initializing ], detections , period , ) # Update not yet initialized tracked objects with yet unmatched detections ( unmatched_detections , matched_not_init_trackers , _ , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if o . is_initializing ], unmatched_detections , period , ) if self . reid_distance_function is not None : # Match unmatched initialized tracked objects with not yet initialized tracked objects _ , _ , _ = self . _update_objects_in_place ( self . reid_distance_function , self . reid_distance_threshold , unmatched_init_trackers + dead_objects , matched_not_init_trackers , period , ) # Create new tracked objects from remaining unmatched detections for detection in unmatched_detections : self . tracked_objects . append ( self . _obj_factory . create ( initial_detection = detection , hit_counter_max = self . hit_counter_max , initialization_delay = self . initialization_delay , pointwise_hit_counter_max = self . pointwise_hit_counter_max , detection_threshold = self . detection_threshold , period = period , filter_factory = self . filter_factory , past_detections_length = self . past_detections_length , reid_hit_counter_max = self . reid_hit_counter_max , coord_transformations = coord_transformations , ) ) return self . get_active_objects () @property def current_object_count ( self ) -> int : \"\"\"Number of active TrackedObjects\"\"\" return len ( self . get_active_objects ()) @property def total_object_count ( self ) -> int : \"\"\"Total number of TrackedObjects initialized in the by this Tracker\"\"\" return self . _obj_factory . count def get_active_objects ( self ) -> List [ \"TrackedObject\" ]: \"\"\"Get the list of active objects Returns ------- List[\"TrackedObject\"] The list of active objects \"\"\" return [ o for o in self . tracked_objects if not o . is_initializing and o . hit_counter_is_positive ] def _update_objects_in_place ( self , distance_function , distance_threshold , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], period : int , ): if candidates is not None and len ( candidates ) > 0 : distance_matrix = distance_function . get_distances ( objects , candidates ) if np . isnan ( distance_matrix ) . any (): print ( \" \\n Received nan values from distance function, please check your distance function for errors!\" ) exit () # Used just for debugging distance function if distance_matrix . any (): for i , minimum in enumerate ( distance_matrix . min ( axis = 0 )): objects [ i ] . current_min_distance = ( minimum if minimum < distance_threshold else None ) matched_cand_indices , matched_obj_indices = self . match_dets_and_objs ( distance_matrix , distance_threshold ) if len ( matched_cand_indices ) > 0 : unmatched_candidates = [ d for i , d in enumerate ( candidates ) if i not in matched_cand_indices ] unmatched_objects = [ d for i , d in enumerate ( objects ) if i not in matched_obj_indices ] matched_objects = [] # Handle matched people/detections for ( match_cand_idx , match_obj_idx ) in zip ( matched_cand_indices , matched_obj_indices ): match_distance = distance_matrix [ match_cand_idx , match_obj_idx ] matched_candidate = candidates [ match_cand_idx ] matched_object = objects [ match_obj_idx ] if match_distance < distance_threshold : if isinstance ( matched_candidate , Detection ): matched_object . hit ( matched_candidate , period = period ) matched_object . last_distance = match_distance matched_objects . append ( matched_object ) elif isinstance ( matched_candidate , TrackedObject ): # Merge new TrackedObject with the old one matched_object . merge ( matched_candidate ) # If we are matching TrackedObject instances we want to get rid of the # already matched candidate to avoid matching it again in future frames self . tracked_objects . remove ( matched_candidate ) else : unmatched_candidates . append ( matched_candidate ) unmatched_objects . append ( matched_object ) else : unmatched_candidates , matched_objects , unmatched_objects = ( candidates , [], objects , ) else : unmatched_candidates , matched_objects , unmatched_objects = [], [], objects return unmatched_candidates , matched_objects , unmatched_objects def match_dets_and_objs ( self , distance_matrix : np . ndarray , distance_threshold ): \"\"\"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen \"\"\" # NOTE: This implementation is terribly inefficient, but it doesn't # seem to affect the fps at all. distance_matrix = distance_matrix . copy () if distance_matrix . size > 0 : det_idxs = [] obj_idxs = [] current_min = distance_matrix . min () while current_min < distance_threshold : flattened_arg_min = distance_matrix . argmin () det_idx = flattened_arg_min // distance_matrix . shape [ 1 ] obj_idx = flattened_arg_min % distance_matrix . shape [ 1 ] det_idxs . append ( det_idx ) obj_idxs . append ( obj_idx ) distance_matrix [ det_idx , :] = distance_threshold + 1 distance_matrix [:, obj_idx ] = distance_threshold + 1 current_min = distance_matrix . min () return det_idxs , obj_idxs else : return [], [] current_object_count : int property # Number of active TrackedObjects total_object_count : int property # Total number of TrackedObjects initialized in the by this Tracker update ( detections = None , period = 1 , coord_transformations = None ) # Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters: Name Type Description Default detections Optional [ List [ Detection ]], optional A list of Detection which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. None period int , optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. 1 coord_transformations Optional [ CoordinatesTransformation ] The coordinate transformation calculated by the MotionEstimator . None Returns: Type Description List [ TrackedObject ] The list of active tracked objects. Source code in norfair/tracker.py 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 def update ( self , detections : Optional [ List [ \"Detection\" ]] = None , period : int = 1 , coord_transformations : Optional [ CoordinatesTransformation ] = None , ) -> List [ \"TrackedObject\" ]: \"\"\" Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters ---------- detections : Optional[List[Detection]], optional A list of [`Detection`][norfair.tracker.Detection] which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. period : int, optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. coord_transformations: Optional[CoordinatesTransformation] The coordinate transformation calculated by the [MotionEstimator][norfair.camera_motion.MotionEstimator]. Returns ------- List[TrackedObject] The list of active tracked objects. \"\"\" if coord_transformations is not None : for det in detections : det . update_coordinate_transformation ( coord_transformations ) # Remove stale trackers and make candidate object real if the hit counter is positive alive_objects = [] dead_objects = [] if self . reid_hit_counter_max is None : self . tracked_objects = [ o for o in self . tracked_objects if o . hit_counter_is_positive ] alive_objects = self . tracked_objects else : tracked_objects = [] for o in self . tracked_objects : if o . reid_hit_counter_is_positive : tracked_objects . append ( o ) if o . hit_counter_is_positive : alive_objects . append ( o ) else : dead_objects . append ( o ) self . tracked_objects = tracked_objects # Update tracker for obj in self . tracked_objects : obj . tracker_step () obj . update_coordinate_transformation ( coord_transformations ) # Update initialized tracked objects with detections ( unmatched_detections , _ , unmatched_init_trackers , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if not o . is_initializing ], detections , period , ) # Update not yet initialized tracked objects with yet unmatched detections ( unmatched_detections , matched_not_init_trackers , _ , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if o . is_initializing ], unmatched_detections , period , ) if self . reid_distance_function is not None : # Match unmatched initialized tracked objects with not yet initialized tracked objects _ , _ , _ = self . _update_objects_in_place ( self . reid_distance_function , self . reid_distance_threshold , unmatched_init_trackers + dead_objects , matched_not_init_trackers , period , ) # Create new tracked objects from remaining unmatched detections for detection in unmatched_detections : self . tracked_objects . append ( self . _obj_factory . create ( initial_detection = detection , hit_counter_max = self . hit_counter_max , initialization_delay = self . initialization_delay , pointwise_hit_counter_max = self . pointwise_hit_counter_max , detection_threshold = self . detection_threshold , period = period , filter_factory = self . filter_factory , past_detections_length = self . past_detections_length , reid_hit_counter_max = self . reid_hit_counter_max , coord_transformations = coord_transformations , ) ) return self . get_active_objects () get_active_objects () # Get the list of active objects Returns: Type Description List [ TrackedObject ] The list of active objects Source code in norfair/tracker.py 272 273 274 275 276 277 278 279 280 281 282 283 284 def get_active_objects ( self ) -> List [ \"TrackedObject\" ]: \"\"\"Get the list of active objects Returns ------- List[\"TrackedObject\"] The list of active objects \"\"\" return [ o for o in self . tracked_objects if not o . is_initializing and o . hit_counter_is_positive ] match_dets_and_objs ( distance_matrix , distance_threshold ) # Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen Source code in norfair/tracker.py 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 def match_dets_and_objs ( self , distance_matrix : np . ndarray , distance_threshold ): \"\"\"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen \"\"\" # NOTE: This implementation is terribly inefficient, but it doesn't # seem to affect the fps at all. distance_matrix = distance_matrix . copy () if distance_matrix . size > 0 : det_idxs = [] obj_idxs = [] current_min = distance_matrix . min () while current_min < distance_threshold : flattened_arg_min = distance_matrix . argmin () det_idx = flattened_arg_min // distance_matrix . shape [ 1 ] obj_idx = flattened_arg_min % distance_matrix . shape [ 1 ] det_idxs . append ( det_idx ) obj_idxs . append ( obj_idx ) distance_matrix [ det_idx , :] = distance_threshold + 1 distance_matrix [:, obj_idx ] = distance_threshold + 1 current_min = distance_matrix . min () return det_idxs , obj_idxs else : return [], [] TrackedObject # The objects returned by the tracker's update function on each iteration. They represent the objects currently being tracked by the tracker. Users should not instantiate TrackedObjects manually; the Tracker will be in charge of creating them. Attributes: Name Type Description estimate np . ndarray Where the tracker predicts the point will be in the current frame based on past detections. A numpy array with the same shape as the detections being fed to the tracker that produced it. id Optional [ int ] The unique identifier assigned to this object by the tracker. Set to None if the object is initializing. global_id Optional [ int ] The globally unique identifier assigned to this object. Set to None if the object is initializing last_detection Detection The last detection that matched with this tracked object. Useful if you are storing embeddings in your detections and want to do metric learning, or for debugging. last_distance Optional [ float ] The distance the tracker had with the last object it matched with. age int The age of this object measured in number of frames. live_points A boolean mask with shape (n_points,) . Points marked as True have recently been matched with detections. Points marked as False haven't and are to be considered stale, and should be ignored. Functions like draw_tracked_objects use this property to determine which points not to draw. initializing_id int On top of id , objects also have an initializing_id which is the id they are given internally by the Tracker ; this id is used solely for debugging. Each new object created by the Tracker starts as an uninitialized TrackedObject , which needs to reach a certain match rate to be converted into a full blown TrackedObject . initializing_id is the id temporarily assigned to TrackedObject while they are getting initialized. Source code in norfair/tracker.py 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 class TrackedObject : \"\"\" The objects returned by the tracker's `update` function on each iteration. They represent the objects currently being tracked by the tracker. Users should not instantiate TrackedObjects manually; the Tracker will be in charge of creating them. Attributes ---------- estimate : np.ndarray Where the tracker predicts the point will be in the current frame based on past detections. A numpy array with the same shape as the detections being fed to the tracker that produced it. id : Optional[int] The unique identifier assigned to this object by the tracker. Set to `None` if the object is initializing. global_id : Optional[int] The globally unique identifier assigned to this object. Set to `None` if the object is initializing last_detection : Detection The last detection that matched with this tracked object. Useful if you are storing embeddings in your detections and want to do metric learning, or for debugging. last_distance : Optional[float] The distance the tracker had with the last object it matched with. age : int The age of this object measured in number of frames. live_points : A boolean mask with shape `(n_points,)`. Points marked as `True` have recently been matched with detections. Points marked as `False` haven't and are to be considered stale, and should be ignored. Functions like [`draw_tracked_objects`][norfair.drawing.draw_tracked_objects] use this property to determine which points not to draw. initializing_id : int On top of `id`, objects also have an `initializing_id` which is the id they are given internally by the `Tracker`; this id is used solely for debugging. Each new object created by the `Tracker` starts as an uninitialized `TrackedObject`, which needs to reach a certain match rate to be converted into a full blown `TrackedObject`. `initializing_id` is the id temporarily assigned to `TrackedObject` while they are getting initialized. \"\"\" def __init__ ( self , obj_factory : _TrackedObjectFactory , initial_detection : \"Detection\" , hit_counter_max : int , initialization_delay : int , pointwise_hit_counter_max : int , detection_threshold : float , period : int , filter_factory : \"FilterFactory\" , past_detections_length : int , reid_hit_counter_max : Optional [ int ], coord_transformations : Optional [ CoordinatesTransformation ] = None , ): if not isinstance ( initial_detection , Detection ): print ( f \" \\n [red]ERROR[/red]: The detection list fed into `tracker.update()` should be composed of { Detection } objects not { type ( initial_detection ) } . \\n \" ) exit () self . _obj_factory = obj_factory self . dim_points = initial_detection . absolute_points . shape [ 1 ] self . num_points = initial_detection . absolute_points . shape [ 0 ] self . hit_counter_max : int = hit_counter_max self . pointwise_hit_counter_max : int = max ( pointwise_hit_counter_max , period ) self . initialization_delay = initialization_delay self . detection_threshold : float = detection_threshold self . initial_period : int = period self . hit_counter : int = period self . reid_hit_counter_max = reid_hit_counter_max self . reid_hit_counter : Optional [ int ] = None self . last_distance : Optional [ float ] = None self . current_min_distance : Optional [ float ] = None self . last_detection : \"Detection\" = initial_detection self . age : int = 0 self . is_initializing : bool = self . hit_counter <= self . initialization_delay self . initializing_id : Optional [ int ] = self . _obj_factory . get_initializing_id () self . id : Optional [ int ] = None self . global_id : Optional [ int ] = None if not self . is_initializing : self . _acquire_ids () if initial_detection . scores is None : self . detected_at_least_once_points = np . array ([ True ] * self . num_points ) else : self . detected_at_least_once_points = ( initial_detection . scores > self . detection_threshold ) self . point_hit_counter : np . ndarray = self . detected_at_least_once_points . astype ( int ) initial_detection . age = self . age self . past_detections_length = past_detections_length if past_detections_length > 0 : self . past_detections : Sequence [ \"Detection\" ] = [ initial_detection ] else : self . past_detections : Sequence [ \"Detection\" ] = [] # Create Kalman Filter self . filter = filter_factory . create_filter ( initial_detection . absolute_points ) self . dim_z = self . dim_points * self . num_points self . label = initial_detection . label self . abs_to_rel = None if coord_transformations is not None : self . update_coordinate_transformation ( coord_transformations ) def tracker_step ( self ): if self . reid_hit_counter is None : if self . hit_counter <= 0 : self . reid_hit_counter = self . reid_hit_counter_max else : self . reid_hit_counter -= 1 self . hit_counter -= 1 self . point_hit_counter -= 1 self . age += 1 # Advances the tracker's state self . filter . predict () @property def hit_counter_is_positive ( self ): return self . hit_counter >= 0 @property def reid_hit_counter_is_positive ( self ): return self . reid_hit_counter is None or self . reid_hit_counter >= 0 @property def estimate_velocity ( self ) -> np . ndarray : \"\"\"Get the velocity estimate of the object from the Kalman filter. This velocity is in the absolute coordinate system. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the velocity estimate of the object on each axis. \"\"\" return self . filter . x . T . flatten ()[ self . dim_z :] . reshape ( - 1 , self . dim_points ) @property def estimate ( self ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. \"\"\" return self . get_estimate () def get_estimate ( self , absolute = False ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters ---------- absolute : bool, optional If true the coordinates are returned in absolute format, by default False, by default False. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises ------ ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. \"\"\" positions = self . filter . x . T . flatten ()[: self . dim_z ] . reshape ( - 1 , self . dim_points ) if self . abs_to_rel is None : if not absolute : return positions else : raise ValueError ( \"You must provide 'coord_transformations' to the tracker to get absolute coordinates\" ) else : if absolute : return positions else : return self . abs_to_rel ( positions ) @property def live_points ( self ): return self . point_hit_counter > 0 def hit ( self , detection : \"Detection\" , period : int = 1 ): \"\"\"Update tracked object with a new detection Parameters ---------- detection : Detection the new detection matched to this tracked object period : int, optional frames corresponding to the period of time since last update. \"\"\" self . _conditionally_add_to_past_detections ( detection ) self . last_detection = detection self . hit_counter = min ( self . hit_counter + 2 * period , self . hit_counter_max ) if self . is_initializing and self . hit_counter > self . initialization_delay : self . is_initializing = False self . _acquire_ids () # We use a kalman filter in which we consider each coordinate on each point as a sensor. # This is a hacky way to update only certain sensors (only x, y coordinates for # points which were detected). # TODO: Use keypoint confidence information to change R on each sensor instead? if detection . scores is not None : assert len ( detection . scores . shape ) == 1 points_over_threshold_mask = detection . scores > self . detection_threshold matched_sensors_mask = np . array ( [( m ,) * self . dim_points for m in points_over_threshold_mask ] ) . flatten () H_pos = np . diag ( matched_sensors_mask ) . astype ( float ) # We measure x, y positions self . point_hit_counter [ points_over_threshold_mask ] += 2 * period else : points_over_threshold_mask = np . array ([ True ] * self . num_points ) H_pos = np . identity ( self . num_points * self . dim_points ) self . point_hit_counter += 2 * period self . point_hit_counter [ self . point_hit_counter >= self . pointwise_hit_counter_max ] = self . pointwise_hit_counter_max self . point_hit_counter [ self . point_hit_counter < 0 ] = 0 H_vel = np . zeros ( H_pos . shape ) # But we don't directly measure velocity H = np . hstack ([ H_pos , H_vel ]) self . filter . update ( np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T , None , H ) detected_at_least_once_mask = np . array ( [( m ,) * self . dim_points for m in self . detected_at_least_once_points ] ) . flatten () now_detected_mask = np . hstack ( ( points_over_threshold_mask ,) * self . dim_points ) . flatten () first_detection_mask = np . logical_and ( now_detected_mask , np . logical_not ( detected_at_least_once_mask ) ) self . filter . x [: self . dim_z ][ first_detection_mask ] = np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T [ first_detection_mask ] # Force points being detected for the first time to have velocity = 0 # This is needed because some detectors (like OpenPose) set points with # low confidence to coordinates (0, 0). And when they then get their first # real detection this creates a huge velocity vector in our KalmanFilter # and causes the tracker to start with wildly inaccurate estimations which # eventually coverge to the real detections. self . filter . x [ self . dim_z :][ np . logical_not ( detected_at_least_once_mask )] = 0 self . detected_at_least_once_points = np . logical_or ( self . detected_at_least_once_points , points_over_threshold_mask ) def __repr__ ( self ): if self . last_distance is None : placeholder_text = \" \\033 [1mObject_ {} \\033 [0m(age: {} , hit_counter: {} , last_distance: {} , init_id: {} )\" else : placeholder_text = \" \\033 [1mObject_ {} \\033 [0m(age: {} , hit_counter: {} , last_distance: {:.2f} , init_id: {} )\" return placeholder_text . format ( self . id , self . age , self . hit_counter , self . last_distance , self . initializing_id , ) def _conditionally_add_to_past_detections ( self , detection ): \"\"\"Adds detections into (and pops detections away) from `past_detections` It does so by keeping a fixed amount of past detections saved into each TrackedObject, while maintaining them distributed uniformly through the object's lifetime. \"\"\" if self . past_detections_length == 0 : return if len ( self . past_detections ) < self . past_detections_length : detection . age = self . age self . past_detections . append ( detection ) elif self . age >= self . past_detections [ 0 ] . age * self . past_detections_length : self . past_detections . pop ( 0 ) detection . age = self . age self . past_detections . append ( detection ) def merge ( self , tracked_object ): \"\"\"Merge with a not yet initialized TrackedObject instance\"\"\" self . reid_hit_counter = None self . hit_counter = self . initial_period * 2 self . point_hit_counter = tracked_object . point_hit_counter self . last_distance = tracked_object . last_distance self . current_min_distance = tracked_object . current_min_distance self . last_detection = tracked_object . last_detection self . detected_at_least_once_points = ( tracked_object . detected_at_least_once_points ) self . filter = tracked_object . filter for past_detection in tracked_object . past_detections : self . _conditionally_add_to_past_detections ( past_detection ) def update_coordinate_transformation ( self , coordinate_transformation : CoordinatesTransformation ): if coordinate_transformation is not None : self . abs_to_rel = coordinate_transformation . abs_to_rel def _acquire_ids ( self ): self . id , self . global_id = self . _obj_factory . get_ids () estimate_velocity : np . ndarray property # Get the velocity estimate of the object from the Kalman filter. This velocity is in the absolute coordinate system. Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the velocity estimate of the object on each axis. estimate : np . ndarray property # Get the position estimate of the object from the Kalman filter. Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. get_estimate ( absolute = False ) # Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters: Name Type Description Default absolute bool , optional If true the coordinates are returned in absolute format, by default False, by default False. False Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises: Type Description ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. Source code in norfair/tracker.py 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 def get_estimate ( self , absolute = False ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters ---------- absolute : bool, optional If true the coordinates are returned in absolute format, by default False, by default False. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises ------ ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. \"\"\" positions = self . filter . x . T . flatten ()[: self . dim_z ] . reshape ( - 1 , self . dim_points ) if self . abs_to_rel is None : if not absolute : return positions else : raise ValueError ( \"You must provide 'coord_transformations' to the tracker to get absolute coordinates\" ) else : if absolute : return positions else : return self . abs_to_rel ( positions ) hit ( detection , period = 1 ) # Update tracked object with a new detection Parameters: Name Type Description Default detection Detection the new detection matched to this tracked object required period int , optional frames corresponding to the period of time since last update. 1 Source code in norfair/tracker.py 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 def hit ( self , detection : \"Detection\" , period : int = 1 ): \"\"\"Update tracked object with a new detection Parameters ---------- detection : Detection the new detection matched to this tracked object period : int, optional frames corresponding to the period of time since last update. \"\"\" self . _conditionally_add_to_past_detections ( detection ) self . last_detection = detection self . hit_counter = min ( self . hit_counter + 2 * period , self . hit_counter_max ) if self . is_initializing and self . hit_counter > self . initialization_delay : self . is_initializing = False self . _acquire_ids () # We use a kalman filter in which we consider each coordinate on each point as a sensor. # This is a hacky way to update only certain sensors (only x, y coordinates for # points which were detected). # TODO: Use keypoint confidence information to change R on each sensor instead? if detection . scores is not None : assert len ( detection . scores . shape ) == 1 points_over_threshold_mask = detection . scores > self . detection_threshold matched_sensors_mask = np . array ( [( m ,) * self . dim_points for m in points_over_threshold_mask ] ) . flatten () H_pos = np . diag ( matched_sensors_mask ) . astype ( float ) # We measure x, y positions self . point_hit_counter [ points_over_threshold_mask ] += 2 * period else : points_over_threshold_mask = np . array ([ True ] * self . num_points ) H_pos = np . identity ( self . num_points * self . dim_points ) self . point_hit_counter += 2 * period self . point_hit_counter [ self . point_hit_counter >= self . pointwise_hit_counter_max ] = self . pointwise_hit_counter_max self . point_hit_counter [ self . point_hit_counter < 0 ] = 0 H_vel = np . zeros ( H_pos . shape ) # But we don't directly measure velocity H = np . hstack ([ H_pos , H_vel ]) self . filter . update ( np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T , None , H ) detected_at_least_once_mask = np . array ( [( m ,) * self . dim_points for m in self . detected_at_least_once_points ] ) . flatten () now_detected_mask = np . hstack ( ( points_over_threshold_mask ,) * self . dim_points ) . flatten () first_detection_mask = np . logical_and ( now_detected_mask , np . logical_not ( detected_at_least_once_mask ) ) self . filter . x [: self . dim_z ][ first_detection_mask ] = np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T [ first_detection_mask ] # Force points being detected for the first time to have velocity = 0 # This is needed because some detectors (like OpenPose) set points with # low confidence to coordinates (0, 0). And when they then get their first # real detection this creates a huge velocity vector in our KalmanFilter # and causes the tracker to start with wildly inaccurate estimations which # eventually coverge to the real detections. self . filter . x [ self . dim_z :][ np . logical_not ( detected_at_least_once_mask )] = 0 self . detected_at_least_once_points = np . logical_or ( self . detected_at_least_once_points , points_over_threshold_mask ) merge ( tracked_object ) # Merge with a not yet initialized TrackedObject instance Source code in norfair/tracker.py 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 def merge ( self , tracked_object ): \"\"\"Merge with a not yet initialized TrackedObject instance\"\"\" self . reid_hit_counter = None self . hit_counter = self . initial_period * 2 self . point_hit_counter = tracked_object . point_hit_counter self . last_distance = tracked_object . last_distance self . current_min_distance = tracked_object . current_min_distance self . last_detection = tracked_object . last_detection self . detected_at_least_once_points = ( tracked_object . detected_at_least_once_points ) self . filter = tracked_object . filter for past_detection in tracked_object . past_detections : self . _conditionally_add_to_past_detections ( past_detection ) Detection # Detections returned by the detector must be converted to a Detection object before being used by Norfair. Parameters: Name Type Description Default points np . ndarray Points detected. Must be a rank 2 array with shape (n_points, n_dimensions) where n_dimensions is 2 or 3. required scores np . ndarray , optional An array of length n_points which assigns a score to each of the points defined in points . This is used to inform the tracker of which points to ignore; any point with a score below detection_threshold will be ignored. This useful for cases in which detections don't always have every point present, as is often the case in pose estimators. None data Any , optional The place to store any extra data which may be useful when calculating the distance function. Anything stored here will be available to use inside the distance function. This enables the development of more interesting trackers which can do things like assign an appearance embedding to each detection to aid in its tracking. None label Hashable , optional When working with multiple classes the detection's label can be stored to be used as a matching condition when associating tracked objects with new detections. Label's type must be hashable for drawing purposes. None embedding Any , optional The embedding for the reid_distance. None Source code in norfair/tracker.py 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 class Detection : \"\"\"Detections returned by the detector must be converted to a `Detection` object before being used by Norfair. Parameters ---------- points : np.ndarray Points detected. Must be a rank 2 array with shape `(n_points, n_dimensions)` where n_dimensions is 2 or 3. scores : np.ndarray, optional An array of length `n_points` which assigns a score to each of the points defined in `points`. This is used to inform the tracker of which points to ignore; any point with a score below `detection_threshold` will be ignored. This useful for cases in which detections don't always have every point present, as is often the case in pose estimators. data : Any, optional The place to store any extra data which may be useful when calculating the distance function. Anything stored here will be available to use inside the distance function. This enables the development of more interesting trackers which can do things like assign an appearance embedding to each detection to aid in its tracking. label : Hashable, optional When working with multiple classes the detection's label can be stored to be used as a matching condition when associating tracked objects with new detections. Label's type must be hashable for drawing purposes. embedding : Any, optional The embedding for the reid_distance. \"\"\" def __init__ ( self , points : np . ndarray , scores : np . ndarray = None , data : Any = None , label : Hashable = None , embedding = None , ): self . points = validate_points ( points ) self . scores = scores self . data = data self . label = label self . absolute_points = self . points . copy () self . embedding = embedding self . age = None def update_coordinate_transformation ( self , coordinate_transformation : CoordinatesTransformation ): if coordinate_transformation is not None : self . absolute_points = coordinate_transformation . rel_to_abs ( self . absolute_points )","title":"Tracker"},{"location":"reference/tracker/#tracker","text":"","title":"Tracker"},{"location":"reference/tracker/#norfair.tracker.Tracker","text":"The class in charge of performing the tracking of the detections produced by a detector. Parameters: Name Type Description Default distance_function Union [ str , Callable [[ Detection , TrackedObject ], float ]] Function used by the tracker to determine the distance between newly detected objects and the objects that are currently being tracked. This function should take 2 input arguments, the first being a Detection , and the second a TrackedObject . It has to return a float with the distance it calculates. Some common distances are implemented in distances , as a shortcut the tracker accepts the name of these predefined distances . Scipy's predefined distances are also accepted. A str with one of the available metrics in scipy.spatial.distance.cdist . required distance_threshold float Defines what is the maximum distance that can constitute a match. Detections and tracked objects whose distances are above this threshold won't be matched by the tracker. required hit_counter_max int , optional Each tracked objects keeps an internal hit counter which tracks how often it's getting matched to a detection, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. This argument defines how large this inertia can grow, and therefore defines how long an object can live without getting matched to any detections, before it is displaced as a dead object, if no ReID distance function is implemented it will be destroyed. 15 initialization_delay Optional [ int ], optional Determines how large the object's hit counter must be in order to be considered as initialized, and get returned to the user as a real object. It must be smaller than hit_counter_max or otherwise the object would never be initialized. If set to 0, objects will get returned to the user as soon as they are detected for the first time, which can be problematic as this can result in objects appearing and immediately dissapearing. Defaults to hit_counter_max / 2 None pointwise_hit_counter_max int , optional Each tracked object keeps track of how often the points it's tracking have been getting matched. Points that are getting matched ( pointwise_hit_counter > 0 ) are said to be live, and points which aren't ( pointwise_hit_counter = 0 ) are said to not be live. This is used to determine things like which individual points in a tracked object get drawn by draw_tracked_objects and which don't. This argument defines how large the inertia for each point of a tracker can grow. 4 detection_threshold float , optional Sets the threshold at which the scores of the points in a detection being fed into the tracker must dip below to be ignored by the tracker. 0 filter_factory FilterFactory , optional This parameter can be used to change what filter the TrackedObject instances created by the tracker will use. Defaults to OptimizedKalmanFilterFactory() OptimizedKalmanFilterFactory() past_detections_length int , optional How many past detections to save for each tracked object. Norfair tries to distribute these past detections uniformly through the object's lifetime so they're more representative. Very useful if you want to add metric learning to your model, as you can associate an embedding to each detection and access them in your distance function. 4 reid_distance_function Optional [ Callable [[ TrackedObject , TrackedObject ], float ]] Function used by the tracker to determine the ReID distance between newly detected trackers and unmatched trackers by the distance function. This function should take 2 input arguments, the first being tracked objects in the initialization phase of type TrackedObject , and the second being tracked objects that have been unmatched of type TrackedObject . It returns a float with the distance it calculates. None reid_distance_threshold float Defines what is the maximum ReID distance that can constitute a match. Tracked objects whose distance is above this threshold won't be merged, if they are the oldest tracked object will be maintained with the position of the new tracked object. 0 reid_hit_counter_max Optional [ int ] Each tracked object keeps an internal ReID hit counter which tracks how often it's getting recognized by another tracker, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. If used, this argument ( reid_hit_counter_max ) defines how long an object can live without getting matched to any detections, before it is destroyed. None Source code in norfair/tracker.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 class Tracker : \"\"\" The class in charge of performing the tracking of the detections produced by a detector. Parameters ---------- distance_function : Union[str, Callable[[Detection, TrackedObject], float]] Function used by the tracker to determine the distance between newly detected objects and the objects that are currently being tracked. This function should take 2 input arguments, the first being a [Detection][norfair.tracker.Detection], and the second a [TrackedObject][norfair.tracker.TrackedObject]. It has to return a `float` with the distance it calculates. Some common distances are implemented in [distances][], as a shortcut the tracker accepts the name of these [predefined distances][norfair.distances.get_distance_by_name]. Scipy's predefined distances are also accepted. A `str` with one of the available metrics in [`scipy.spatial.distance.cdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html). distance_threshold : float Defines what is the maximum distance that can constitute a match. Detections and tracked objects whose distances are above this threshold won't be matched by the tracker. hit_counter_max : int, optional Each tracked objects keeps an internal hit counter which tracks how often it's getting matched to a detection, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. This argument defines how large this inertia can grow, and therefore defines how long an object can live without getting matched to any detections, before it is displaced as a dead object, if no ReID distance function is implemented it will be destroyed. initialization_delay : Optional[int], optional Determines how large the object's hit counter must be in order to be considered as initialized, and get returned to the user as a real object. It must be smaller than `hit_counter_max` or otherwise the object would never be initialized. If set to 0, objects will get returned to the user as soon as they are detected for the first time, which can be problematic as this can result in objects appearing and immediately dissapearing. Defaults to `hit_counter_max / 2` pointwise_hit_counter_max : int, optional Each tracked object keeps track of how often the points it's tracking have been getting matched. Points that are getting matched (`pointwise_hit_counter > 0`) are said to be live, and points which aren't (`pointwise_hit_counter = 0`) are said to not be live. This is used to determine things like which individual points in a tracked object get drawn by [`draw_tracked_objects`][norfair.drawing.draw_tracked_objects] and which don't. This argument defines how large the inertia for each point of a tracker can grow. detection_threshold : float, optional Sets the threshold at which the scores of the points in a detection being fed into the tracker must dip below to be ignored by the tracker. filter_factory : FilterFactory, optional This parameter can be used to change what filter the [`TrackedObject`][norfair.tracker.TrackedObject] instances created by the tracker will use. Defaults to [`OptimizedKalmanFilterFactory()`][norfair.filter.OptimizedKalmanFilterFactory] past_detections_length : int, optional How many past detections to save for each tracked object. Norfair tries to distribute these past detections uniformly through the object's lifetime so they're more representative. Very useful if you want to add metric learning to your model, as you can associate an embedding to each detection and access them in your distance function. reid_distance_function: Optional[Callable[[\"TrackedObject\", \"TrackedObject\"], float]] Function used by the tracker to determine the ReID distance between newly detected trackers and unmatched trackers by the distance function. This function should take 2 input arguments, the first being tracked objects in the initialization phase of type [`TrackedObject`][norfair.tracker.TrackedObject], and the second being tracked objects that have been unmatched of type [`TrackedObject`][norfair.tracker.TrackedObject]. It returns a `float` with the distance it calculates. reid_distance_threshold: float Defines what is the maximum ReID distance that can constitute a match. Tracked objects whose distance is above this threshold won't be merged, if they are the oldest tracked object will be maintained with the position of the new tracked object. reid_hit_counter_max: Optional[int] Each tracked object keeps an internal ReID hit counter which tracks how often it's getting recognized by another tracker, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. If used, this argument (`reid_hit_counter_max`) defines how long an object can live without getting matched to any detections, before it is destroyed. \"\"\" def __init__ ( self , distance_function : Union [ str , Callable [[ \"Detection\" , \"TrackedObject\" ], float ]], distance_threshold : float , hit_counter_max : int = 15 , initialization_delay : Optional [ int ] = None , pointwise_hit_counter_max : int = 4 , detection_threshold : float = 0 , filter_factory : FilterFactory = OptimizedKalmanFilterFactory (), past_detections_length : int = 4 , reid_distance_function : Optional [ Callable [[ \"TrackedObject\" , \"TrackedObject\" ], float ] ] = None , reid_distance_threshold : float = 0 , reid_hit_counter_max : Optional [ int ] = None , ): self . tracked_objects : Sequence [ \"TrackedObject\" ] = [] if isinstance ( distance_function , str ): distance_function = get_distance_by_name ( distance_function ) elif isinstance ( distance_function , Callable ): warning ( \"You are using a scalar distance function. If you want to speed up the\" \" tracking process please consider using a vectorized distance\" f \" function such as { AVAILABLE_VECTORIZED_DISTANCES } .\" ) distance_function = ScalarDistance ( distance_function ) else : raise ValueError ( \"Argument `distance_function` should be a string or function but is\" f \" { type ( distance_function ) } instead.\" ) self . distance_function = distance_function self . hit_counter_max = hit_counter_max self . reid_hit_counter_max = reid_hit_counter_max self . pointwise_hit_counter_max = pointwise_hit_counter_max self . filter_factory = filter_factory if past_detections_length >= 0 : self . past_detections_length = past_detections_length else : raise ValueError ( f \"Argument `past_detections_length` is { past_detections_length } and should be larger than 0.\" ) if initialization_delay is None : self . initialization_delay = int ( self . hit_counter_max / 2 ) elif initialization_delay < 0 or initialization_delay >= self . hit_counter_max : raise ValueError ( f \"Argument 'initialization_delay' for 'Tracker' class should be an int between 0 and (hit_counter_max = { hit_counter_max } ). The selected value is { initialization_delay } . \\n \" ) else : self . initialization_delay = initialization_delay self . distance_threshold = distance_threshold self . detection_threshold = detection_threshold if reid_distance_function is not None : self . reid_distance_function = ScalarDistance ( reid_distance_function ) else : self . reid_distance_function = reid_distance_function self . reid_distance_threshold = reid_distance_threshold self . _obj_factory = _TrackedObjectFactory () def update ( self , detections : Optional [ List [ \"Detection\" ]] = None , period : int = 1 , coord_transformations : Optional [ CoordinatesTransformation ] = None , ) -> List [ \"TrackedObject\" ]: \"\"\" Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters ---------- detections : Optional[List[Detection]], optional A list of [`Detection`][norfair.tracker.Detection] which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. period : int, optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. coord_transformations: Optional[CoordinatesTransformation] The coordinate transformation calculated by the [MotionEstimator][norfair.camera_motion.MotionEstimator]. Returns ------- List[TrackedObject] The list of active tracked objects. \"\"\" if coord_transformations is not None : for det in detections : det . update_coordinate_transformation ( coord_transformations ) # Remove stale trackers and make candidate object real if the hit counter is positive alive_objects = [] dead_objects = [] if self . reid_hit_counter_max is None : self . tracked_objects = [ o for o in self . tracked_objects if o . hit_counter_is_positive ] alive_objects = self . tracked_objects else : tracked_objects = [] for o in self . tracked_objects : if o . reid_hit_counter_is_positive : tracked_objects . append ( o ) if o . hit_counter_is_positive : alive_objects . append ( o ) else : dead_objects . append ( o ) self . tracked_objects = tracked_objects # Update tracker for obj in self . tracked_objects : obj . tracker_step () obj . update_coordinate_transformation ( coord_transformations ) # Update initialized tracked objects with detections ( unmatched_detections , _ , unmatched_init_trackers , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if not o . is_initializing ], detections , period , ) # Update not yet initialized tracked objects with yet unmatched detections ( unmatched_detections , matched_not_init_trackers , _ , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if o . is_initializing ], unmatched_detections , period , ) if self . reid_distance_function is not None : # Match unmatched initialized tracked objects with not yet initialized tracked objects _ , _ , _ = self . _update_objects_in_place ( self . reid_distance_function , self . reid_distance_threshold , unmatched_init_trackers + dead_objects , matched_not_init_trackers , period , ) # Create new tracked objects from remaining unmatched detections for detection in unmatched_detections : self . tracked_objects . append ( self . _obj_factory . create ( initial_detection = detection , hit_counter_max = self . hit_counter_max , initialization_delay = self . initialization_delay , pointwise_hit_counter_max = self . pointwise_hit_counter_max , detection_threshold = self . detection_threshold , period = period , filter_factory = self . filter_factory , past_detections_length = self . past_detections_length , reid_hit_counter_max = self . reid_hit_counter_max , coord_transformations = coord_transformations , ) ) return self . get_active_objects () @property def current_object_count ( self ) -> int : \"\"\"Number of active TrackedObjects\"\"\" return len ( self . get_active_objects ()) @property def total_object_count ( self ) -> int : \"\"\"Total number of TrackedObjects initialized in the by this Tracker\"\"\" return self . _obj_factory . count def get_active_objects ( self ) -> List [ \"TrackedObject\" ]: \"\"\"Get the list of active objects Returns ------- List[\"TrackedObject\"] The list of active objects \"\"\" return [ o for o in self . tracked_objects if not o . is_initializing and o . hit_counter_is_positive ] def _update_objects_in_place ( self , distance_function , distance_threshold , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], period : int , ): if candidates is not None and len ( candidates ) > 0 : distance_matrix = distance_function . get_distances ( objects , candidates ) if np . isnan ( distance_matrix ) . any (): print ( \" \\n Received nan values from distance function, please check your distance function for errors!\" ) exit () # Used just for debugging distance function if distance_matrix . any (): for i , minimum in enumerate ( distance_matrix . min ( axis = 0 )): objects [ i ] . current_min_distance = ( minimum if minimum < distance_threshold else None ) matched_cand_indices , matched_obj_indices = self . match_dets_and_objs ( distance_matrix , distance_threshold ) if len ( matched_cand_indices ) > 0 : unmatched_candidates = [ d for i , d in enumerate ( candidates ) if i not in matched_cand_indices ] unmatched_objects = [ d for i , d in enumerate ( objects ) if i not in matched_obj_indices ] matched_objects = [] # Handle matched people/detections for ( match_cand_idx , match_obj_idx ) in zip ( matched_cand_indices , matched_obj_indices ): match_distance = distance_matrix [ match_cand_idx , match_obj_idx ] matched_candidate = candidates [ match_cand_idx ] matched_object = objects [ match_obj_idx ] if match_distance < distance_threshold : if isinstance ( matched_candidate , Detection ): matched_object . hit ( matched_candidate , period = period ) matched_object . last_distance = match_distance matched_objects . append ( matched_object ) elif isinstance ( matched_candidate , TrackedObject ): # Merge new TrackedObject with the old one matched_object . merge ( matched_candidate ) # If we are matching TrackedObject instances we want to get rid of the # already matched candidate to avoid matching it again in future frames self . tracked_objects . remove ( matched_candidate ) else : unmatched_candidates . append ( matched_candidate ) unmatched_objects . append ( matched_object ) else : unmatched_candidates , matched_objects , unmatched_objects = ( candidates , [], objects , ) else : unmatched_candidates , matched_objects , unmatched_objects = [], [], objects return unmatched_candidates , matched_objects , unmatched_objects def match_dets_and_objs ( self , distance_matrix : np . ndarray , distance_threshold ): \"\"\"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen \"\"\" # NOTE: This implementation is terribly inefficient, but it doesn't # seem to affect the fps at all. distance_matrix = distance_matrix . copy () if distance_matrix . size > 0 : det_idxs = [] obj_idxs = [] current_min = distance_matrix . min () while current_min < distance_threshold : flattened_arg_min = distance_matrix . argmin () det_idx = flattened_arg_min // distance_matrix . shape [ 1 ] obj_idx = flattened_arg_min % distance_matrix . shape [ 1 ] det_idxs . append ( det_idx ) obj_idxs . append ( obj_idx ) distance_matrix [ det_idx , :] = distance_threshold + 1 distance_matrix [:, obj_idx ] = distance_threshold + 1 current_min = distance_matrix . min () return det_idxs , obj_idxs else : return [], []","title":"Tracker"},{"location":"reference/tracker/#norfair.tracker.Tracker.current_object_count","text":"Number of active TrackedObjects","title":"current_object_count"},{"location":"reference/tracker/#norfair.tracker.Tracker.total_object_count","text":"Total number of TrackedObjects initialized in the by this Tracker","title":"total_object_count"},{"location":"reference/tracker/#norfair.tracker.Tracker.update","text":"Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters: Name Type Description Default detections Optional [ List [ Detection ]], optional A list of Detection which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. None period int , optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. 1 coord_transformations Optional [ CoordinatesTransformation ] The coordinate transformation calculated by the MotionEstimator . None Returns: Type Description List [ TrackedObject ] The list of active tracked objects. Source code in norfair/tracker.py 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 def update ( self , detections : Optional [ List [ \"Detection\" ]] = None , period : int = 1 , coord_transformations : Optional [ CoordinatesTransformation ] = None , ) -> List [ \"TrackedObject\" ]: \"\"\" Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters ---------- detections : Optional[List[Detection]], optional A list of [`Detection`][norfair.tracker.Detection] which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. period : int, optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. coord_transformations: Optional[CoordinatesTransformation] The coordinate transformation calculated by the [MotionEstimator][norfair.camera_motion.MotionEstimator]. Returns ------- List[TrackedObject] The list of active tracked objects. \"\"\" if coord_transformations is not None : for det in detections : det . update_coordinate_transformation ( coord_transformations ) # Remove stale trackers and make candidate object real if the hit counter is positive alive_objects = [] dead_objects = [] if self . reid_hit_counter_max is None : self . tracked_objects = [ o for o in self . tracked_objects if o . hit_counter_is_positive ] alive_objects = self . tracked_objects else : tracked_objects = [] for o in self . tracked_objects : if o . reid_hit_counter_is_positive : tracked_objects . append ( o ) if o . hit_counter_is_positive : alive_objects . append ( o ) else : dead_objects . append ( o ) self . tracked_objects = tracked_objects # Update tracker for obj in self . tracked_objects : obj . tracker_step () obj . update_coordinate_transformation ( coord_transformations ) # Update initialized tracked objects with detections ( unmatched_detections , _ , unmatched_init_trackers , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if not o . is_initializing ], detections , period , ) # Update not yet initialized tracked objects with yet unmatched detections ( unmatched_detections , matched_not_init_trackers , _ , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if o . is_initializing ], unmatched_detections , period , ) if self . reid_distance_function is not None : # Match unmatched initialized tracked objects with not yet initialized tracked objects _ , _ , _ = self . _update_objects_in_place ( self . reid_distance_function , self . reid_distance_threshold , unmatched_init_trackers + dead_objects , matched_not_init_trackers , period , ) # Create new tracked objects from remaining unmatched detections for detection in unmatched_detections : self . tracked_objects . append ( self . _obj_factory . create ( initial_detection = detection , hit_counter_max = self . hit_counter_max , initialization_delay = self . initialization_delay , pointwise_hit_counter_max = self . pointwise_hit_counter_max , detection_threshold = self . detection_threshold , period = period , filter_factory = self . filter_factory , past_detections_length = self . past_detections_length , reid_hit_counter_max = self . reid_hit_counter_max , coord_transformations = coord_transformations , ) ) return self . get_active_objects ()","title":"update()"},{"location":"reference/tracker/#norfair.tracker.Tracker.get_active_objects","text":"Get the list of active objects Returns: Type Description List [ TrackedObject ] The list of active objects Source code in norfair/tracker.py 272 273 274 275 276 277 278 279 280 281 282 283 284 def get_active_objects ( self ) -> List [ \"TrackedObject\" ]: \"\"\"Get the list of active objects Returns ------- List[\"TrackedObject\"] The list of active objects \"\"\" return [ o for o in self . tracked_objects if not o . is_initializing and o . hit_counter_is_positive ]","title":"get_active_objects()"},{"location":"reference/tracker/#norfair.tracker.Tracker.match_dets_and_objs","text":"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen Source code in norfair/tracker.py 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 def match_dets_and_objs ( self , distance_matrix : np . ndarray , distance_threshold ): \"\"\"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen \"\"\" # NOTE: This implementation is terribly inefficient, but it doesn't # seem to affect the fps at all. distance_matrix = distance_matrix . copy () if distance_matrix . size > 0 : det_idxs = [] obj_idxs = [] current_min = distance_matrix . min () while current_min < distance_threshold : flattened_arg_min = distance_matrix . argmin () det_idx = flattened_arg_min // distance_matrix . shape [ 1 ] obj_idx = flattened_arg_min % distance_matrix . shape [ 1 ] det_idxs . append ( det_idx ) obj_idxs . append ( obj_idx ) distance_matrix [ det_idx , :] = distance_threshold + 1 distance_matrix [:, obj_idx ] = distance_threshold + 1 current_min = distance_matrix . min () return det_idxs , obj_idxs else : return [], []","title":"match_dets_and_objs()"},{"location":"reference/tracker/#norfair.tracker.TrackedObject","text":"The objects returned by the tracker's update function on each iteration. They represent the objects currently being tracked by the tracker. Users should not instantiate TrackedObjects manually; the Tracker will be in charge of creating them. Attributes: Name Type Description estimate np . ndarray Where the tracker predicts the point will be in the current frame based on past detections. A numpy array with the same shape as the detections being fed to the tracker that produced it. id Optional [ int ] The unique identifier assigned to this object by the tracker. Set to None if the object is initializing. global_id Optional [ int ] The globally unique identifier assigned to this object. Set to None if the object is initializing last_detection Detection The last detection that matched with this tracked object. Useful if you are storing embeddings in your detections and want to do metric learning, or for debugging. last_distance Optional [ float ] The distance the tracker had with the last object it matched with. age int The age of this object measured in number of frames. live_points A boolean mask with shape (n_points,) . Points marked as True have recently been matched with detections. Points marked as False haven't and are to be considered stale, and should be ignored. Functions like draw_tracked_objects use this property to determine which points not to draw. initializing_id int On top of id , objects also have an initializing_id which is the id they are given internally by the Tracker ; this id is used solely for debugging. Each new object created by the Tracker starts as an uninitialized TrackedObject , which needs to reach a certain match rate to be converted into a full blown TrackedObject . initializing_id is the id temporarily assigned to TrackedObject while they are getting initialized. Source code in norfair/tracker.py 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 class TrackedObject : \"\"\" The objects returned by the tracker's `update` function on each iteration. They represent the objects currently being tracked by the tracker. Users should not instantiate TrackedObjects manually; the Tracker will be in charge of creating them. Attributes ---------- estimate : np.ndarray Where the tracker predicts the point will be in the current frame based on past detections. A numpy array with the same shape as the detections being fed to the tracker that produced it. id : Optional[int] The unique identifier assigned to this object by the tracker. Set to `None` if the object is initializing. global_id : Optional[int] The globally unique identifier assigned to this object. Set to `None` if the object is initializing last_detection : Detection The last detection that matched with this tracked object. Useful if you are storing embeddings in your detections and want to do metric learning, or for debugging. last_distance : Optional[float] The distance the tracker had with the last object it matched with. age : int The age of this object measured in number of frames. live_points : A boolean mask with shape `(n_points,)`. Points marked as `True` have recently been matched with detections. Points marked as `False` haven't and are to be considered stale, and should be ignored. Functions like [`draw_tracked_objects`][norfair.drawing.draw_tracked_objects] use this property to determine which points not to draw. initializing_id : int On top of `id`, objects also have an `initializing_id` which is the id they are given internally by the `Tracker`; this id is used solely for debugging. Each new object created by the `Tracker` starts as an uninitialized `TrackedObject`, which needs to reach a certain match rate to be converted into a full blown `TrackedObject`. `initializing_id` is the id temporarily assigned to `TrackedObject` while they are getting initialized. \"\"\" def __init__ ( self , obj_factory : _TrackedObjectFactory , initial_detection : \"Detection\" , hit_counter_max : int , initialization_delay : int , pointwise_hit_counter_max : int , detection_threshold : float , period : int , filter_factory : \"FilterFactory\" , past_detections_length : int , reid_hit_counter_max : Optional [ int ], coord_transformations : Optional [ CoordinatesTransformation ] = None , ): if not isinstance ( initial_detection , Detection ): print ( f \" \\n [red]ERROR[/red]: The detection list fed into `tracker.update()` should be composed of { Detection } objects not { type ( initial_detection ) } . \\n \" ) exit () self . _obj_factory = obj_factory self . dim_points = initial_detection . absolute_points . shape [ 1 ] self . num_points = initial_detection . absolute_points . shape [ 0 ] self . hit_counter_max : int = hit_counter_max self . pointwise_hit_counter_max : int = max ( pointwise_hit_counter_max , period ) self . initialization_delay = initialization_delay self . detection_threshold : float = detection_threshold self . initial_period : int = period self . hit_counter : int = period self . reid_hit_counter_max = reid_hit_counter_max self . reid_hit_counter : Optional [ int ] = None self . last_distance : Optional [ float ] = None self . current_min_distance : Optional [ float ] = None self . last_detection : \"Detection\" = initial_detection self . age : int = 0 self . is_initializing : bool = self . hit_counter <= self . initialization_delay self . initializing_id : Optional [ int ] = self . _obj_factory . get_initializing_id () self . id : Optional [ int ] = None self . global_id : Optional [ int ] = None if not self . is_initializing : self . _acquire_ids () if initial_detection . scores is None : self . detected_at_least_once_points = np . array ([ True ] * self . num_points ) else : self . detected_at_least_once_points = ( initial_detection . scores > self . detection_threshold ) self . point_hit_counter : np . ndarray = self . detected_at_least_once_points . astype ( int ) initial_detection . age = self . age self . past_detections_length = past_detections_length if past_detections_length > 0 : self . past_detections : Sequence [ \"Detection\" ] = [ initial_detection ] else : self . past_detections : Sequence [ \"Detection\" ] = [] # Create Kalman Filter self . filter = filter_factory . create_filter ( initial_detection . absolute_points ) self . dim_z = self . dim_points * self . num_points self . label = initial_detection . label self . abs_to_rel = None if coord_transformations is not None : self . update_coordinate_transformation ( coord_transformations ) def tracker_step ( self ): if self . reid_hit_counter is None : if self . hit_counter <= 0 : self . reid_hit_counter = self . reid_hit_counter_max else : self . reid_hit_counter -= 1 self . hit_counter -= 1 self . point_hit_counter -= 1 self . age += 1 # Advances the tracker's state self . filter . predict () @property def hit_counter_is_positive ( self ): return self . hit_counter >= 0 @property def reid_hit_counter_is_positive ( self ): return self . reid_hit_counter is None or self . reid_hit_counter >= 0 @property def estimate_velocity ( self ) -> np . ndarray : \"\"\"Get the velocity estimate of the object from the Kalman filter. This velocity is in the absolute coordinate system. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the velocity estimate of the object on each axis. \"\"\" return self . filter . x . T . flatten ()[ self . dim_z :] . reshape ( - 1 , self . dim_points ) @property def estimate ( self ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. \"\"\" return self . get_estimate () def get_estimate ( self , absolute = False ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters ---------- absolute : bool, optional If true the coordinates are returned in absolute format, by default False, by default False. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises ------ ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. \"\"\" positions = self . filter . x . T . flatten ()[: self . dim_z ] . reshape ( - 1 , self . dim_points ) if self . abs_to_rel is None : if not absolute : return positions else : raise ValueError ( \"You must provide 'coord_transformations' to the tracker to get absolute coordinates\" ) else : if absolute : return positions else : return self . abs_to_rel ( positions ) @property def live_points ( self ): return self . point_hit_counter > 0 def hit ( self , detection : \"Detection\" , period : int = 1 ): \"\"\"Update tracked object with a new detection Parameters ---------- detection : Detection the new detection matched to this tracked object period : int, optional frames corresponding to the period of time since last update. \"\"\" self . _conditionally_add_to_past_detections ( detection ) self . last_detection = detection self . hit_counter = min ( self . hit_counter + 2 * period , self . hit_counter_max ) if self . is_initializing and self . hit_counter > self . initialization_delay : self . is_initializing = False self . _acquire_ids () # We use a kalman filter in which we consider each coordinate on each point as a sensor. # This is a hacky way to update only certain sensors (only x, y coordinates for # points which were detected). # TODO: Use keypoint confidence information to change R on each sensor instead? if detection . scores is not None : assert len ( detection . scores . shape ) == 1 points_over_threshold_mask = detection . scores > self . detection_threshold matched_sensors_mask = np . array ( [( m ,) * self . dim_points for m in points_over_threshold_mask ] ) . flatten () H_pos = np . diag ( matched_sensors_mask ) . astype ( float ) # We measure x, y positions self . point_hit_counter [ points_over_threshold_mask ] += 2 * period else : points_over_threshold_mask = np . array ([ True ] * self . num_points ) H_pos = np . identity ( self . num_points * self . dim_points ) self . point_hit_counter += 2 * period self . point_hit_counter [ self . point_hit_counter >= self . pointwise_hit_counter_max ] = self . pointwise_hit_counter_max self . point_hit_counter [ self . point_hit_counter < 0 ] = 0 H_vel = np . zeros ( H_pos . shape ) # But we don't directly measure velocity H = np . hstack ([ H_pos , H_vel ]) self . filter . update ( np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T , None , H ) detected_at_least_once_mask = np . array ( [( m ,) * self . dim_points for m in self . detected_at_least_once_points ] ) . flatten () now_detected_mask = np . hstack ( ( points_over_threshold_mask ,) * self . dim_points ) . flatten () first_detection_mask = np . logical_and ( now_detected_mask , np . logical_not ( detected_at_least_once_mask ) ) self . filter . x [: self . dim_z ][ first_detection_mask ] = np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T [ first_detection_mask ] # Force points being detected for the first time to have velocity = 0 # This is needed because some detectors (like OpenPose) set points with # low confidence to coordinates (0, 0). And when they then get their first # real detection this creates a huge velocity vector in our KalmanFilter # and causes the tracker to start with wildly inaccurate estimations which # eventually coverge to the real detections. self . filter . x [ self . dim_z :][ np . logical_not ( detected_at_least_once_mask )] = 0 self . detected_at_least_once_points = np . logical_or ( self . detected_at_least_once_points , points_over_threshold_mask ) def __repr__ ( self ): if self . last_distance is None : placeholder_text = \" \\033 [1mObject_ {} \\033 [0m(age: {} , hit_counter: {} , last_distance: {} , init_id: {} )\" else : placeholder_text = \" \\033 [1mObject_ {} \\033 [0m(age: {} , hit_counter: {} , last_distance: {:.2f} , init_id: {} )\" return placeholder_text . format ( self . id , self . age , self . hit_counter , self . last_distance , self . initializing_id , ) def _conditionally_add_to_past_detections ( self , detection ): \"\"\"Adds detections into (and pops detections away) from `past_detections` It does so by keeping a fixed amount of past detections saved into each TrackedObject, while maintaining them distributed uniformly through the object's lifetime. \"\"\" if self . past_detections_length == 0 : return if len ( self . past_detections ) < self . past_detections_length : detection . age = self . age self . past_detections . append ( detection ) elif self . age >= self . past_detections [ 0 ] . age * self . past_detections_length : self . past_detections . pop ( 0 ) detection . age = self . age self . past_detections . append ( detection ) def merge ( self , tracked_object ): \"\"\"Merge with a not yet initialized TrackedObject instance\"\"\" self . reid_hit_counter = None self . hit_counter = self . initial_period * 2 self . point_hit_counter = tracked_object . point_hit_counter self . last_distance = tracked_object . last_distance self . current_min_distance = tracked_object . current_min_distance self . last_detection = tracked_object . last_detection self . detected_at_least_once_points = ( tracked_object . detected_at_least_once_points ) self . filter = tracked_object . filter for past_detection in tracked_object . past_detections : self . _conditionally_add_to_past_detections ( past_detection ) def update_coordinate_transformation ( self , coordinate_transformation : CoordinatesTransformation ): if coordinate_transformation is not None : self . abs_to_rel = coordinate_transformation . abs_to_rel def _acquire_ids ( self ): self . id , self . global_id = self . _obj_factory . get_ids ()","title":"TrackedObject"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.estimate_velocity","text":"Get the velocity estimate of the object from the Kalman filter. This velocity is in the absolute coordinate system. Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the velocity estimate of the object on each axis.","title":"estimate_velocity"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.estimate","text":"Get the position estimate of the object from the Kalman filter. Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis.","title":"estimate"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.get_estimate","text":"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters: Name Type Description Default absolute bool , optional If true the coordinates are returned in absolute format, by default False, by default False. False Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises: Type Description ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. Source code in norfair/tracker.py 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 def get_estimate ( self , absolute = False ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters ---------- absolute : bool, optional If true the coordinates are returned in absolute format, by default False, by default False. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises ------ ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. \"\"\" positions = self . filter . x . T . flatten ()[: self . dim_z ] . reshape ( - 1 , self . dim_points ) if self . abs_to_rel is None : if not absolute : return positions else : raise ValueError ( \"You must provide 'coord_transformations' to the tracker to get absolute coordinates\" ) else : if absolute : return positions else : return self . abs_to_rel ( positions )","title":"get_estimate()"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.hit","text":"Update tracked object with a new detection Parameters: Name Type Description Default detection Detection the new detection matched to this tracked object required period int , optional frames corresponding to the period of time since last update. 1 Source code in norfair/tracker.py 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 def hit ( self , detection : \"Detection\" , period : int = 1 ): \"\"\"Update tracked object with a new detection Parameters ---------- detection : Detection the new detection matched to this tracked object period : int, optional frames corresponding to the period of time since last update. \"\"\" self . _conditionally_add_to_past_detections ( detection ) self . last_detection = detection self . hit_counter = min ( self . hit_counter + 2 * period , self . hit_counter_max ) if self . is_initializing and self . hit_counter > self . initialization_delay : self . is_initializing = False self . _acquire_ids () # We use a kalman filter in which we consider each coordinate on each point as a sensor. # This is a hacky way to update only certain sensors (only x, y coordinates for # points which were detected). # TODO: Use keypoint confidence information to change R on each sensor instead? if detection . scores is not None : assert len ( detection . scores . shape ) == 1 points_over_threshold_mask = detection . scores > self . detection_threshold matched_sensors_mask = np . array ( [( m ,) * self . dim_points for m in points_over_threshold_mask ] ) . flatten () H_pos = np . diag ( matched_sensors_mask ) . astype ( float ) # We measure x, y positions self . point_hit_counter [ points_over_threshold_mask ] += 2 * period else : points_over_threshold_mask = np . array ([ True ] * self . num_points ) H_pos = np . identity ( self . num_points * self . dim_points ) self . point_hit_counter += 2 * period self . point_hit_counter [ self . point_hit_counter >= self . pointwise_hit_counter_max ] = self . pointwise_hit_counter_max self . point_hit_counter [ self . point_hit_counter < 0 ] = 0 H_vel = np . zeros ( H_pos . shape ) # But we don't directly measure velocity H = np . hstack ([ H_pos , H_vel ]) self . filter . update ( np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T , None , H ) detected_at_least_once_mask = np . array ( [( m ,) * self . dim_points for m in self . detected_at_least_once_points ] ) . flatten () now_detected_mask = np . hstack ( ( points_over_threshold_mask ,) * self . dim_points ) . flatten () first_detection_mask = np . logical_and ( now_detected_mask , np . logical_not ( detected_at_least_once_mask ) ) self . filter . x [: self . dim_z ][ first_detection_mask ] = np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T [ first_detection_mask ] # Force points being detected for the first time to have velocity = 0 # This is needed because some detectors (like OpenPose) set points with # low confidence to coordinates (0, 0). And when they then get their first # real detection this creates a huge velocity vector in our KalmanFilter # and causes the tracker to start with wildly inaccurate estimations which # eventually coverge to the real detections. self . filter . x [ self . dim_z :][ np . logical_not ( detected_at_least_once_mask )] = 0 self . detected_at_least_once_points = np . logical_or ( self . detected_at_least_once_points , points_over_threshold_mask )","title":"hit()"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.merge","text":"Merge with a not yet initialized TrackedObject instance Source code in norfair/tracker.py 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 def merge ( self , tracked_object ): \"\"\"Merge with a not yet initialized TrackedObject instance\"\"\" self . reid_hit_counter = None self . hit_counter = self . initial_period * 2 self . point_hit_counter = tracked_object . point_hit_counter self . last_distance = tracked_object . last_distance self . current_min_distance = tracked_object . current_min_distance self . last_detection = tracked_object . last_detection self . detected_at_least_once_points = ( tracked_object . detected_at_least_once_points ) self . filter = tracked_object . filter for past_detection in tracked_object . past_detections : self . _conditionally_add_to_past_detections ( past_detection )","title":"merge()"},{"location":"reference/tracker/#norfair.tracker.Detection","text":"Detections returned by the detector must be converted to a Detection object before being used by Norfair. Parameters: Name Type Description Default points np . ndarray Points detected. Must be a rank 2 array with shape (n_points, n_dimensions) where n_dimensions is 2 or 3. required scores np . ndarray , optional An array of length n_points which assigns a score to each of the points defined in points . This is used to inform the tracker of which points to ignore; any point with a score below detection_threshold will be ignored. This useful for cases in which detections don't always have every point present, as is often the case in pose estimators. None data Any , optional The place to store any extra data which may be useful when calculating the distance function. Anything stored here will be available to use inside the distance function. This enables the development of more interesting trackers which can do things like assign an appearance embedding to each detection to aid in its tracking. None label Hashable , optional When working with multiple classes the detection's label can be stored to be used as a matching condition when associating tracked objects with new detections. Label's type must be hashable for drawing purposes. None embedding Any , optional The embedding for the reid_distance. None Source code in norfair/tracker.py 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 class Detection : \"\"\"Detections returned by the detector must be converted to a `Detection` object before being used by Norfair. Parameters ---------- points : np.ndarray Points detected. Must be a rank 2 array with shape `(n_points, n_dimensions)` where n_dimensions is 2 or 3. scores : np.ndarray, optional An array of length `n_points` which assigns a score to each of the points defined in `points`. This is used to inform the tracker of which points to ignore; any point with a score below `detection_threshold` will be ignored. This useful for cases in which detections don't always have every point present, as is often the case in pose estimators. data : Any, optional The place to store any extra data which may be useful when calculating the distance function. Anything stored here will be available to use inside the distance function. This enables the development of more interesting trackers which can do things like assign an appearance embedding to each detection to aid in its tracking. label : Hashable, optional When working with multiple classes the detection's label can be stored to be used as a matching condition when associating tracked objects with new detections. Label's type must be hashable for drawing purposes. embedding : Any, optional The embedding for the reid_distance. \"\"\" def __init__ ( self , points : np . ndarray , scores : np . ndarray = None , data : Any = None , label : Hashable = None , embedding = None , ): self . points = validate_points ( points ) self . scores = scores self . data = data self . label = label self . absolute_points = self . points . copy () self . embedding = embedding self . age = None def update_coordinate_transformation ( self , coordinate_transformation : CoordinatesTransformation ): if coordinate_transformation is not None : self . absolute_points = coordinate_transformation . rel_to_abs ( self . absolute_points )","title":"Detection"},{"location":"reference/utils/","text":"Utils # print_objects_as_table ( tracked_objects ) # Used for helping in debugging Source code in norfair/utils.py 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 def print_objects_as_table ( tracked_objects : Sequence ): \"\"\"Used for helping in debugging\"\"\" print () console = Console () table = Table ( show_header = True , header_style = \"bold magenta\" ) table . add_column ( \"Id\" , style = \"yellow\" , justify = \"center\" ) table . add_column ( \"Age\" , justify = \"right\" ) table . add_column ( \"Hit Counter\" , justify = \"right\" ) table . add_column ( \"Last distance\" , justify = \"right\" ) table . add_column ( \"Init Id\" , justify = \"center\" ) for obj in tracked_objects : table . add_row ( str ( obj . id ), str ( obj . age ), str ( obj . hit_counter ), f \" { obj . last_distance : .4f } \" , str ( obj . initializing_id ), ) console . print ( table ) get_cutout ( points , image ) # Returns a rectangular cut-out from a set of points on an image Source code in norfair/utils.py 65 66 67 68 69 70 71 def get_cutout ( points , image ): \"\"\"Returns a rectangular cut-out from a set of points on an image\"\"\" max_x = int ( max ( points [:, 0 ])) min_x = int ( min ( points [:, 0 ])) max_y = int ( max ( points [:, 1 ])) min_y = int ( min ( points [:, 1 ])) return image [ min_y : max_y , min_x : max_x ] warn_once ( message ) cached # Write a warning message only once. Source code in norfair/utils.py 95 96 97 98 99 100 @lru_cache ( maxsize = None ) def warn_once ( message ): \"\"\" Write a warning message only once. \"\"\" warn ( message )","title":"Utils"},{"location":"reference/utils/#utils","text":"","title":"Utils"},{"location":"reference/utils/#norfair.utils.print_objects_as_table","text":"Used for helping in debugging Source code in norfair/utils.py 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 def print_objects_as_table ( tracked_objects : Sequence ): \"\"\"Used for helping in debugging\"\"\" print () console = Console () table = Table ( show_header = True , header_style = \"bold magenta\" ) table . add_column ( \"Id\" , style = \"yellow\" , justify = \"center\" ) table . add_column ( \"Age\" , justify = \"right\" ) table . add_column ( \"Hit Counter\" , justify = \"right\" ) table . add_column ( \"Last distance\" , justify = \"right\" ) table . add_column ( \"Init Id\" , justify = \"center\" ) for obj in tracked_objects : table . add_row ( str ( obj . id ), str ( obj . age ), str ( obj . hit_counter ), f \" { obj . last_distance : .4f } \" , str ( obj . initializing_id ), ) console . print ( table )","title":"print_objects_as_table()"},{"location":"reference/utils/#norfair.utils.get_cutout","text":"Returns a rectangular cut-out from a set of points on an image Source code in norfair/utils.py 65 66 67 68 69 70 71 def get_cutout ( points , image ): \"\"\"Returns a rectangular cut-out from a set of points on an image\"\"\" max_x = int ( max ( points [:, 0 ])) min_x = int ( min ( points [:, 0 ])) max_y = int ( max ( points [:, 1 ])) min_y = int ( min ( points [:, 1 ])) return image [ min_y : max_y , min_x : max_x ]","title":"get_cutout()"},{"location":"reference/utils/#norfair.utils.warn_once","text":"Write a warning message only once. Source code in norfair/utils.py 95 96 97 98 99 100 @lru_cache ( maxsize = None ) def warn_once ( message ): \"\"\" Write a warning message only once. \"\"\" warn ( message )","title":"warn_once()"},{"location":"reference/video/","text":"Video # Video # Class that provides a simple and pythonic way to interact with video. It returns regular OpenCV frames which enables the usage of the huge number of tools OpenCV provides to modify images. Parameters: Name Type Description Default camera Optional [ int ], optional An integer representing the device id of the camera to be used as the video source. Webcams tend to have an id of 0 . Arguments camera and input_path can't be used at the same time, one must be chosen. None input_path Optional [ str ], optional A string consisting of the path to the video file to be used as the video source. Arguments camera and input_path can't be used at the same time, one must be chosen. None output_path str , optional The path to the output video to be generated. Can be a folder were the file will be created or a full path with a file name. '.' output_fps Optional [ float ], optional The frames per second at which to encode the output video file. If not provided it is set to be equal to the input video source's fps. This argument is useful when using live video cameras as a video source, where the user may know the input fps, but where the frames are being fed to the output video at a rate that is lower than the video source's fps, due to the latency added by the detector. None label str , optional Label to add to the progress bar that appears when processing the current video. '' output_fourcc Optional [ str ], optional OpenCV encoding for output video file. By default we use mp4v for .mp4 and XVID for .avi . This is a combination that works on most systems but it results in larger files. To get smaller files use avc1 or H264 if available. Notice that some fourcc are not compatible with some extensions. None output_extension str , optional File extension used for the output video. Ignored if output_path is not a folder. 'mp4' Examples: >>> video = Video ( input_path = \"video.mp4\" ) >>> for frame in video : >>> # << Your modifications to the frame would go here >> >>> video . write ( frame ) Source code in norfair/video.py 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 class Video : \"\"\" Class that provides a simple and pythonic way to interact with video. It returns regular OpenCV frames which enables the usage of the huge number of tools OpenCV provides to modify images. Parameters ---------- camera : Optional[int], optional An integer representing the device id of the camera to be used as the video source. Webcams tend to have an id of `0`. Arguments `camera` and `input_path` can't be used at the same time, one must be chosen. input_path : Optional[str], optional A string consisting of the path to the video file to be used as the video source. Arguments `camera` and `input_path` can't be used at the same time, one must be chosen. output_path : str, optional The path to the output video to be generated. Can be a folder were the file will be created or a full path with a file name. output_fps : Optional[float], optional The frames per second at which to encode the output video file. If not provided it is set to be equal to the input video source's fps. This argument is useful when using live video cameras as a video source, where the user may know the input fps, but where the frames are being fed to the output video at a rate that is lower than the video source's fps, due to the latency added by the detector. label : str, optional Label to add to the progress bar that appears when processing the current video. output_fourcc : Optional[str], optional OpenCV encoding for output video file. By default we use `mp4v` for `.mp4` and `XVID` for `.avi`. This is a combination that works on most systems but it results in larger files. To get smaller files use `avc1` or `H264` if available. Notice that some fourcc are not compatible with some extensions. output_extension : str, optional File extension used for the output video. Ignored if `output_path` is not a folder. Examples -------- >>> video = Video(input_path=\"video.mp4\") >>> for frame in video: >>> # << Your modifications to the frame would go here >> >>> video.write(frame) \"\"\" def __init__ ( self , camera : Optional [ int ] = None , input_path : Optional [ str ] = None , output_path : str = \".\" , output_fps : Optional [ float ] = None , label : str = \"\" , output_fourcc : Optional [ str ] = None , output_extension : str = \"mp4\" , ): self . camera = camera self . input_path = input_path self . output_path = output_path self . label = label self . output_fourcc = output_fourcc self . output_extension = output_extension self . output_video : Optional [ cv2 . VideoWriter ] = None # Input validation if ( input_path is None and camera is None ) or ( input_path is not None and camera is not None ): raise ValueError ( \"You must set either 'camera' or 'input_path' arguments when setting 'Video' class\" ) if camera is not None and type ( camera ) is not int : raise ValueError ( \"Argument 'camera' refers to the device-id of your camera, and must be an int. Setting it to 0 usually works if you don't know the id.\" ) # Read Input Video if self . input_path is not None : if \"~\" in self . input_path : self . input_path = os . path . expanduser ( self . input_path ) if not os . path . isfile ( self . input_path ): self . _fail ( f \"[bold red]Error:[/bold red] File ' { self . input_path } ' does not exist.\" ) self . video_capture = cv2 . VideoCapture ( self . input_path ) total_frames = int ( self . video_capture . get ( cv2 . CAP_PROP_FRAME_COUNT )) if total_frames == 0 : self . _fail ( f \"[bold red]Error:[/bold red] ' { self . input_path } ' does not seem to be a video file supported by OpenCV. If the video file is not the problem, please check that your OpenCV installation is working correctly.\" ) description = os . path . basename ( self . input_path ) else : self . video_capture = cv2 . VideoCapture ( self . camera ) total_frames = 0 description = f \"Camera( { self . camera } )\" self . output_fps = ( output_fps if output_fps is not None else self . video_capture . get ( cv2 . CAP_PROP_FPS ) ) self . input_height = self . video_capture . get ( cv2 . CAP_PROP_FRAME_HEIGHT ) self . input_width = self . video_capture . get ( cv2 . CAP_PROP_FRAME_WIDTH ) self . frame_counter = 0 # Setup progressbar if self . label : description += f \" | { self . label } \" progress_bar_fields : List [ Union [ str , ProgressColumn ]] = [ \"[progress.description] {task.description} \" , BarColumn (), \"[yellow] {task.fields[process_fps]:.2f} fps[/yellow]\" , ] if self . input_path is not None : progress_bar_fields . insert ( 2 , \"[progress.percentage] {task.percentage:>3.0f} %\" ) progress_bar_fields . insert ( 3 , TimeRemainingColumn (), ) self . progress_bar = Progress ( * progress_bar_fields , auto_refresh = False , redirect_stdout = False , redirect_stderr = False , ) self . task = self . progress_bar . add_task ( self . abbreviate_description ( description ), total = total_frames , start = self . input_path is not None , process_fps = 0 , ) # This is a generator, note the yield keyword below. def __iter__ ( self ): with self . progress_bar as progress_bar : start = time . time () # Iterate over video while True : self . frame_counter += 1 ret , frame = self . video_capture . read () if ret is False or frame is None : break process_fps = self . frame_counter / ( time . time () - start ) progress_bar . update ( self . task , advance = 1 , refresh = True , process_fps = process_fps ) yield frame # Cleanup if self . output_video is not None : self . output_video . release () print ( f \"[white]Output video file saved to: { self . get_output_file_path () } [/white]\" ) self . video_capture . release () cv2 . destroyAllWindows () def _fail ( self , msg : str ): print ( msg ) exit () def write ( self , frame : np . ndarray ) -> int : \"\"\" Write one frame to the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to write to file. Returns ------- int _description_ \"\"\" if self . output_video is None : # The user may need to access the output file path on their code output_file_path = self . get_output_file_path () fourcc = cv2 . VideoWriter_fourcc ( * self . get_codec_fourcc ( output_file_path )) # Set on first frame write in case the user resizes the frame in some way output_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) self . output_video = cv2 . VideoWriter ( output_file_path , fourcc , self . output_fps , output_size , ) self . output_video . write ( frame ) return cv2 . waitKey ( 1 ) def show ( self , frame : np . ndarray , downsample_ratio : float = 1.0 ) -> int : \"\"\" Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to be displayed. downsample_ratio : float, optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. Returns ------- int _description_ \"\"\" # Resize to lower resolution for faster streaming over slow connections if downsample_ratio != 1.0 : frame = cv2 . resize ( frame , ( frame . shape [ 1 ] // downsample_ratio , frame . shape [ 0 ] // downsample_ratio , ), ) cv2 . imshow ( \"Output\" , frame ) return cv2 . waitKey ( 1 ) def get_output_file_path ( self ) -> str : \"\"\" Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set `output_path`, and want to know what the autogenerated output file path by Norfair will be. Returns ------- str The path to the file. \"\"\" if not os . path . isdir ( self . output_path ): return self . output_path if self . input_path is not None : file_name = self . input_path . split ( \"/\" )[ - 1 ] . split ( \".\" )[ 0 ] else : file_name = \"camera_ {self.camera} \" file_name = f \" { file_name } _out. { self . output_extension } \" return os . path . join ( self . output_path , file_name ) def get_codec_fourcc ( self , filename : str ) -> Optional [ str ]: if self . output_fourcc is not None : return self . output_fourcc # Default codecs for each extension extension = filename [ - 3 :] . lower () if \"avi\" == extension : return \"XVID\" elif \"mp4\" == extension : return \"mp4v\" # When available, \"avc1\" is better else : self . _fail ( f \"[bold red]Could not determine video codec for the provided output filename[/bold red]: \" f \"[yellow] { filename } [/yellow] \\n \" f \"Please use '.mp4', '.avi', or provide a custom OpenCV fourcc codec name.\" ) return ( None # Had to add this return to make mypya happy. I don't like this. ) def abbreviate_description ( self , description : str ) -> str : \"\"\"Conditionally abbreviate description so that progress bar fits in small terminals\"\"\" terminal_columns , _ = get_terminal_size () space_for_description = ( int ( terminal_columns ) - 25 ) # Leave 25 space for progressbar if len ( description ) < space_for_description : return description else : return \" {} ... {} \" . format ( description [: space_for_description // 2 - 3 ], description [ - space_for_description // 2 + 3 :], ) write ( frame ) # Write one frame to the output video. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to write to file. required Returns: Type Description int description Source code in norfair/video.py 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 def write ( self , frame : np . ndarray ) -> int : \"\"\" Write one frame to the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to write to file. Returns ------- int _description_ \"\"\" if self . output_video is None : # The user may need to access the output file path on their code output_file_path = self . get_output_file_path () fourcc = cv2 . VideoWriter_fourcc ( * self . get_codec_fourcc ( output_file_path )) # Set on first frame write in case the user resizes the frame in some way output_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) self . output_video = cv2 . VideoWriter ( output_file_path , fourcc , self . output_fps , output_size , ) self . output_video . write ( frame ) return cv2 . waitKey ( 1 ) show ( frame , downsample_ratio = 1.0 ) # Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to be displayed. required downsample_ratio float , optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. 1.0 Returns: Type Description int description Source code in norfair/video.py 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 def show ( self , frame : np . ndarray , downsample_ratio : float = 1.0 ) -> int : \"\"\" Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to be displayed. downsample_ratio : float, optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. Returns ------- int _description_ \"\"\" # Resize to lower resolution for faster streaming over slow connections if downsample_ratio != 1.0 : frame = cv2 . resize ( frame , ( frame . shape [ 1 ] // downsample_ratio , frame . shape [ 0 ] // downsample_ratio , ), ) cv2 . imshow ( \"Output\" , frame ) return cv2 . waitKey ( 1 ) get_output_file_path () # Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set output_path , and want to know what the autogenerated output file path by Norfair will be. Returns: Type Description str The path to the file. Source code in norfair/video.py 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 def get_output_file_path ( self ) -> str : \"\"\" Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set `output_path`, and want to know what the autogenerated output file path by Norfair will be. Returns ------- str The path to the file. \"\"\" if not os . path . isdir ( self . output_path ): return self . output_path if self . input_path is not None : file_name = self . input_path . split ( \"/\" )[ - 1 ] . split ( \".\" )[ 0 ] else : file_name = \"camera_ {self.camera} \" file_name = f \" { file_name } _out. { self . output_extension } \" return os . path . join ( self . output_path , file_name ) abbreviate_description ( description ) # Conditionally abbreviate description so that progress bar fits in small terminals Source code in norfair/video.py 287 288 289 290 291 292 293 294 295 296 297 298 299 def abbreviate_description ( self , description : str ) -> str : \"\"\"Conditionally abbreviate description so that progress bar fits in small terminals\"\"\" terminal_columns , _ = get_terminal_size () space_for_description = ( int ( terminal_columns ) - 25 ) # Leave 25 space for progressbar if len ( description ) < space_for_description : return description else : return \" {} ... {} \" . format ( description [: space_for_description // 2 - 3 ], description [ - space_for_description // 2 + 3 :], )","title":"Video"},{"location":"reference/video/#video","text":"","title":"Video"},{"location":"reference/video/#norfair.video.Video","text":"Class that provides a simple and pythonic way to interact with video. It returns regular OpenCV frames which enables the usage of the huge number of tools OpenCV provides to modify images. Parameters: Name Type Description Default camera Optional [ int ], optional An integer representing the device id of the camera to be used as the video source. Webcams tend to have an id of 0 . Arguments camera and input_path can't be used at the same time, one must be chosen. None input_path Optional [ str ], optional A string consisting of the path to the video file to be used as the video source. Arguments camera and input_path can't be used at the same time, one must be chosen. None output_path str , optional The path to the output video to be generated. Can be a folder were the file will be created or a full path with a file name. '.' output_fps Optional [ float ], optional The frames per second at which to encode the output video file. If not provided it is set to be equal to the input video source's fps. This argument is useful when using live video cameras as a video source, where the user may know the input fps, but where the frames are being fed to the output video at a rate that is lower than the video source's fps, due to the latency added by the detector. None label str , optional Label to add to the progress bar that appears when processing the current video. '' output_fourcc Optional [ str ], optional OpenCV encoding for output video file. By default we use mp4v for .mp4 and XVID for .avi . This is a combination that works on most systems but it results in larger files. To get smaller files use avc1 or H264 if available. Notice that some fourcc are not compatible with some extensions. None output_extension str , optional File extension used for the output video. Ignored if output_path is not a folder. 'mp4' Examples: >>> video = Video ( input_path = \"video.mp4\" ) >>> for frame in video : >>> # << Your modifications to the frame would go here >> >>> video . write ( frame ) Source code in norfair/video.py 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 class Video : \"\"\" Class that provides a simple and pythonic way to interact with video. It returns regular OpenCV frames which enables the usage of the huge number of tools OpenCV provides to modify images. Parameters ---------- camera : Optional[int], optional An integer representing the device id of the camera to be used as the video source. Webcams tend to have an id of `0`. Arguments `camera` and `input_path` can't be used at the same time, one must be chosen. input_path : Optional[str], optional A string consisting of the path to the video file to be used as the video source. Arguments `camera` and `input_path` can't be used at the same time, one must be chosen. output_path : str, optional The path to the output video to be generated. Can be a folder were the file will be created or a full path with a file name. output_fps : Optional[float], optional The frames per second at which to encode the output video file. If not provided it is set to be equal to the input video source's fps. This argument is useful when using live video cameras as a video source, where the user may know the input fps, but where the frames are being fed to the output video at a rate that is lower than the video source's fps, due to the latency added by the detector. label : str, optional Label to add to the progress bar that appears when processing the current video. output_fourcc : Optional[str], optional OpenCV encoding for output video file. By default we use `mp4v` for `.mp4` and `XVID` for `.avi`. This is a combination that works on most systems but it results in larger files. To get smaller files use `avc1` or `H264` if available. Notice that some fourcc are not compatible with some extensions. output_extension : str, optional File extension used for the output video. Ignored if `output_path` is not a folder. Examples -------- >>> video = Video(input_path=\"video.mp4\") >>> for frame in video: >>> # << Your modifications to the frame would go here >> >>> video.write(frame) \"\"\" def __init__ ( self , camera : Optional [ int ] = None , input_path : Optional [ str ] = None , output_path : str = \".\" , output_fps : Optional [ float ] = None , label : str = \"\" , output_fourcc : Optional [ str ] = None , output_extension : str = \"mp4\" , ): self . camera = camera self . input_path = input_path self . output_path = output_path self . label = label self . output_fourcc = output_fourcc self . output_extension = output_extension self . output_video : Optional [ cv2 . VideoWriter ] = None # Input validation if ( input_path is None and camera is None ) or ( input_path is not None and camera is not None ): raise ValueError ( \"You must set either 'camera' or 'input_path' arguments when setting 'Video' class\" ) if camera is not None and type ( camera ) is not int : raise ValueError ( \"Argument 'camera' refers to the device-id of your camera, and must be an int. Setting it to 0 usually works if you don't know the id.\" ) # Read Input Video if self . input_path is not None : if \"~\" in self . input_path : self . input_path = os . path . expanduser ( self . input_path ) if not os . path . isfile ( self . input_path ): self . _fail ( f \"[bold red]Error:[/bold red] File ' { self . input_path } ' does not exist.\" ) self . video_capture = cv2 . VideoCapture ( self . input_path ) total_frames = int ( self . video_capture . get ( cv2 . CAP_PROP_FRAME_COUNT )) if total_frames == 0 : self . _fail ( f \"[bold red]Error:[/bold red] ' { self . input_path } ' does not seem to be a video file supported by OpenCV. If the video file is not the problem, please check that your OpenCV installation is working correctly.\" ) description = os . path . basename ( self . input_path ) else : self . video_capture = cv2 . VideoCapture ( self . camera ) total_frames = 0 description = f \"Camera( { self . camera } )\" self . output_fps = ( output_fps if output_fps is not None else self . video_capture . get ( cv2 . CAP_PROP_FPS ) ) self . input_height = self . video_capture . get ( cv2 . CAP_PROP_FRAME_HEIGHT ) self . input_width = self . video_capture . get ( cv2 . CAP_PROP_FRAME_WIDTH ) self . frame_counter = 0 # Setup progressbar if self . label : description += f \" | { self . label } \" progress_bar_fields : List [ Union [ str , ProgressColumn ]] = [ \"[progress.description] {task.description} \" , BarColumn (), \"[yellow] {task.fields[process_fps]:.2f} fps[/yellow]\" , ] if self . input_path is not None : progress_bar_fields . insert ( 2 , \"[progress.percentage] {task.percentage:>3.0f} %\" ) progress_bar_fields . insert ( 3 , TimeRemainingColumn (), ) self . progress_bar = Progress ( * progress_bar_fields , auto_refresh = False , redirect_stdout = False , redirect_stderr = False , ) self . task = self . progress_bar . add_task ( self . abbreviate_description ( description ), total = total_frames , start = self . input_path is not None , process_fps = 0 , ) # This is a generator, note the yield keyword below. def __iter__ ( self ): with self . progress_bar as progress_bar : start = time . time () # Iterate over video while True : self . frame_counter += 1 ret , frame = self . video_capture . read () if ret is False or frame is None : break process_fps = self . frame_counter / ( time . time () - start ) progress_bar . update ( self . task , advance = 1 , refresh = True , process_fps = process_fps ) yield frame # Cleanup if self . output_video is not None : self . output_video . release () print ( f \"[white]Output video file saved to: { self . get_output_file_path () } [/white]\" ) self . video_capture . release () cv2 . destroyAllWindows () def _fail ( self , msg : str ): print ( msg ) exit () def write ( self , frame : np . ndarray ) -> int : \"\"\" Write one frame to the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to write to file. Returns ------- int _description_ \"\"\" if self . output_video is None : # The user may need to access the output file path on their code output_file_path = self . get_output_file_path () fourcc = cv2 . VideoWriter_fourcc ( * self . get_codec_fourcc ( output_file_path )) # Set on first frame write in case the user resizes the frame in some way output_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) self . output_video = cv2 . VideoWriter ( output_file_path , fourcc , self . output_fps , output_size , ) self . output_video . write ( frame ) return cv2 . waitKey ( 1 ) def show ( self , frame : np . ndarray , downsample_ratio : float = 1.0 ) -> int : \"\"\" Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to be displayed. downsample_ratio : float, optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. Returns ------- int _description_ \"\"\" # Resize to lower resolution for faster streaming over slow connections if downsample_ratio != 1.0 : frame = cv2 . resize ( frame , ( frame . shape [ 1 ] // downsample_ratio , frame . shape [ 0 ] // downsample_ratio , ), ) cv2 . imshow ( \"Output\" , frame ) return cv2 . waitKey ( 1 ) def get_output_file_path ( self ) -> str : \"\"\" Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set `output_path`, and want to know what the autogenerated output file path by Norfair will be. Returns ------- str The path to the file. \"\"\" if not os . path . isdir ( self . output_path ): return self . output_path if self . input_path is not None : file_name = self . input_path . split ( \"/\" )[ - 1 ] . split ( \".\" )[ 0 ] else : file_name = \"camera_ {self.camera} \" file_name = f \" { file_name } _out. { self . output_extension } \" return os . path . join ( self . output_path , file_name ) def get_codec_fourcc ( self , filename : str ) -> Optional [ str ]: if self . output_fourcc is not None : return self . output_fourcc # Default codecs for each extension extension = filename [ - 3 :] . lower () if \"avi\" == extension : return \"XVID\" elif \"mp4\" == extension : return \"mp4v\" # When available, \"avc1\" is better else : self . _fail ( f \"[bold red]Could not determine video codec for the provided output filename[/bold red]: \" f \"[yellow] { filename } [/yellow] \\n \" f \"Please use '.mp4', '.avi', or provide a custom OpenCV fourcc codec name.\" ) return ( None # Had to add this return to make mypya happy. I don't like this. ) def abbreviate_description ( self , description : str ) -> str : \"\"\"Conditionally abbreviate description so that progress bar fits in small terminals\"\"\" terminal_columns , _ = get_terminal_size () space_for_description = ( int ( terminal_columns ) - 25 ) # Leave 25 space for progressbar if len ( description ) < space_for_description : return description else : return \" {} ... {} \" . format ( description [: space_for_description // 2 - 3 ], description [ - space_for_description // 2 + 3 :], )","title":"Video"},{"location":"reference/video/#norfair.video.Video.write","text":"Write one frame to the output video. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to write to file. required Returns: Type Description int description Source code in norfair/video.py 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 def write ( self , frame : np . ndarray ) -> int : \"\"\" Write one frame to the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to write to file. Returns ------- int _description_ \"\"\" if self . output_video is None : # The user may need to access the output file path on their code output_file_path = self . get_output_file_path () fourcc = cv2 . VideoWriter_fourcc ( * self . get_codec_fourcc ( output_file_path )) # Set on first frame write in case the user resizes the frame in some way output_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) self . output_video = cv2 . VideoWriter ( output_file_path , fourcc , self . output_fps , output_size , ) self . output_video . write ( frame ) return cv2 . waitKey ( 1 )","title":"write()"},{"location":"reference/video/#norfair.video.Video.show","text":"Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to be displayed. required downsample_ratio float , optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. 1.0 Returns: Type Description int description Source code in norfair/video.py 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 def show ( self , frame : np . ndarray , downsample_ratio : float = 1.0 ) -> int : \"\"\" Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to be displayed. downsample_ratio : float, optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. Returns ------- int _description_ \"\"\" # Resize to lower resolution for faster streaming over slow connections if downsample_ratio != 1.0 : frame = cv2 . resize ( frame , ( frame . shape [ 1 ] // downsample_ratio , frame . shape [ 0 ] // downsample_ratio , ), ) cv2 . imshow ( \"Output\" , frame ) return cv2 . waitKey ( 1 )","title":"show()"},{"location":"reference/video/#norfair.video.Video.get_output_file_path","text":"Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set output_path , and want to know what the autogenerated output file path by Norfair will be. Returns: Type Description str The path to the file. Source code in norfair/video.py 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 def get_output_file_path ( self ) -> str : \"\"\" Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set `output_path`, and want to know what the autogenerated output file path by Norfair will be. Returns ------- str The path to the file. \"\"\" if not os . path . isdir ( self . output_path ): return self . output_path if self . input_path is not None : file_name = self . input_path . split ( \"/\" )[ - 1 ] . split ( \".\" )[ 0 ] else : file_name = \"camera_ {self.camera} \" file_name = f \" { file_name } _out. { self . output_extension } \" return os . path . join ( self . output_path , file_name )","title":"get_output_file_path()"},{"location":"reference/video/#norfair.video.Video.abbreviate_description","text":"Conditionally abbreviate description so that progress bar fits in small terminals Source code in norfair/video.py 287 288 289 290 291 292 293 294 295 296 297 298 299 def abbreviate_description ( self , description : str ) -> str : \"\"\"Conditionally abbreviate description so that progress bar fits in small terminals\"\"\" terminal_columns , _ = get_terminal_size () space_for_description = ( int ( terminal_columns ) - 25 ) # Leave 25 space for progressbar if len ( description ) < space_for_description : return description else : return \" {} ... {} \" . format ( description [: space_for_description // 2 - 3 ], description [ - space_for_description // 2 + 3 :], )","title":"abbreviate_description()"}]}
\ No newline at end of file
+{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Norfair is a customizable lightweight Python library for real-time multi-object tracking. Using Norfair, you can add tracking capabilities to any detector with just a few lines of code. Tracking players with moving camera Tracking 3D objects Features # Any detector expressing its detections as a series of (x, y) coordinates can be used with Norfair. This includes detectors performing tasks such as object or keypoint detection (see examples ). Modular. It can easily be inserted into complex video processing pipelines to add tracking to existing projects. At the same time, it is possible to build a video inference loop from scratch using just Norfair and a detector. Supports moving camera, re-identification with appearance embeddings, and n-dimensional object tracking (see Advanced features ). Norfair provides several predefined distance functions to compare tracked objects and detections. The distance functions can also be defined by the user, enabling the implementation of different tracking strategies. Fast. The only thing bounding inference speed will be the detection network feeding detections to Norfair. Norfair is built, used and maintained by Tryolabs . Installation # Norfair currently supports Python 3.7+. For the minimal version, install as: 1 pip install norfair To make Norfair install the dependencies to support more features, install as: 1 2 3 pip install norfair [ video ] # Adds several video helper features running on OpenCV pip install norfair [ metrics ] # Supports running MOT metrics evaluation pip install norfair [ metrics,video ] # Everything included If the needed dependencies are already present in the system, installing the minimal version of Norfair is enough for enabling the extra features. This is particularly useful for embedded devices, where installing compiled dependencies can be difficult, but they can sometimes come preinstalled with the system. Documentation # Getting started guide . Official reference . Examples & demos # We provide several examples of how Norfair can be used to add tracking capabilities to different detectors, and also showcase more advanced features. Note: for ease of reproducibility, we provide Dockerfiles for all the demos. Even though Norfair does not need a GPU, the default configuration of most demos requires a GPU to be able to run the detectors. For this, make sure you install NVIDIA Container Toolkit so that your GPU can be shared with Docker. It is possible to run several demos with a CPU, but you will have to modify the scripts or tinker with the installation of their dependencies. Adding tracking to different detectors # Most tracking demos are showcased with vehicles and pedestrians, but the detectors are generally trained with many more classes from the COCO dataset . YOLOv7 : tracking object centroids or bounding boxes. YOLOv5 : tracking object centroids or bounding boxes. YOLOv4 : tracking object centroids. Detectron2 : tracking object centroids. AlphaPose : tracking human keypoints (pose estimation) and inserting Norfair into a complex existing pipeline using. OpenPose : tracking human keypoints. Tracking objects with YOLOPv2 , a model for traffic object detection, drivable road area segmentation, and lane line detection. Advanced features # Speed up pose estimation by extrapolating detections using OpenPose . Track both bounding boxes and human keypoints (multi-class), unifying the detections from a YOLO model and OpenPose. Re-identification (ReID) of tracked objects using appearance embeddings. This is a good starting point for scenarios with a lot of occlusion, in which the Kalman filter alone would struggle. Accurately track objects even if the camera is moving , by estimating camera motion potentially accounting for pan, tilt, rotation, movement in any direction, and zoom. Track points in 3D , using MediaPipe Objectron . Tracking of small objects , using SAHI: Slicing Aided Hyper Inference . ROS integration # To make it even easier to use Norfair in robotics projects, we now offer a version that integrates with the Robotic Operating System (ROS). We present a ROS package and a fully functional environment running on Docker to do the first steps with this package and start your first application easier. Benchmarking and profiling # Kalman filter and distance function profiling using TRT pose estimator . Computation of MOT17 scores using motmetrics4norfair . How it works # 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. As an example we use 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. On the left you can see the points we get from Detectron2, and on the right how Norfair tracks them assigning a unique identifier through time. Even a straightforward distance function like this one can work when the tracking needed is simple. Norfair also provides several useful tools for creating a video inference loop. Here is what the full code for creating the previous example looks like, including the code needed to set up Detectron2: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import cv2 import numpy as np from detectron2.config import get_cfg from detectron2.engine import DefaultPredictor from norfair import Detection , Tracker , Video , draw_tracked_objects # Set up Detectron2 object detector cfg = get_cfg () cfg . merge_from_file ( \"demos/faster_rcnn_R_50_FPN_3x.yaml\" ) cfg . MODEL . ROI_HEADS . SCORE_THRESH_TEST = 0.5 cfg . MODEL . WEIGHTS = \"detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl\" detector = DefaultPredictor ( cfg ) # Norfair video = Video ( input_path = \"video.mp4\" ) tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 20 ) for frame in video : detections = detector ( cv2 . cvtColor ( frame , cv2 . COLOR_BGR2RGB )) detections = [ Detection ( p ) for p in detections [ 'instances' ] . pred_boxes . get_centers () . cpu () . numpy ()] tracked_objects = tracker . update ( detections = detections ) draw_tracked_objects ( frame , tracked_objects ) video . write ( frame ) The video and drawing tools use OpenCV frames, so they are compatible with most Python video code available online. The point tracking is based on SORT generalized to detections consisting of a dynamically changing number of points per detection. Motivation # Trying out the latest state-of-the-art detectors normally requires running repositories that weren't intended to be easy to use. These tend to be repositories associated with a research paper describing a novel new way of doing detection, and they are therefore intended to be run as a one-off evaluation script to get some result metric to publish on a particular research paper. This explains why they tend to not be easy to run as inference scripts, or why extracting the core model to use in another standalone script isn't always trivial. Norfair was born out of the need to quickly add a simple layer of tracking over a wide range of newly released SOTA detectors. It was designed to seamlessly be plugged into a complex, highly coupled code base, with minimum effort. Norfair provides a series of modular but compatible tools, which you can pick and choose to use in your project. Comparison to other trackers # Norfair's contribution to Python's object tracker library repertoire is its ability to work with any object detector by being able to work with a variable number of points per detection, and the ability for the user to heavily customize the tracker by creating their own distance function. If you are looking for a tracker, here are some other projects worth noting: OpenCV includes several tracking solutions like KCF Tracker and MedianFlow Tracker which are run by making the user select a part of the frame to track, and then letting the tracker follow that area. They tend not to be run on top of a detector and are not very robust. dlib includes a correlation single object tracker. You have to create your own multiple object tracker on top of it yourself if you want to track multiple objects with it. AlphaPose just released a new version of their human pose tracker. This tracker is tightly integrated into their code base, and to the task of tracking human poses. SORT and Deep SORT are similar to this repo in that they use Kalman filters (and a deep embedding for Deep SORT), but they are hardcoded to a fixed distance function and to tracking boxes. Norfair also adds some filtering when matching tracked objects with detections, and changes the Hungarian Algorithm for its own distance minimizer. Both these repos are also released under the GPL license, which might be an issue for some individuals or companies because the source code of derivative works needs to be published. Benchmarks # MOT17 and MOT20 results obtained using motmetrics4norfair demo script on the train split. We used detections obtained with ByteTrack's YOLOX object detection model. MOT17 Train IDF1 IDP IDR Rcll Prcn MOTA MOTP MOT17-02 61.3% 63.6% 59.0% 86.8% 93.5% 79.9% 14.8% MOT17-04 93.3% 93.6% 93.0% 98.6% 99.3% 97.9% 07.9% MOT17-05 77.8% 77.7% 77.8% 85.9% 85.8% 71.2% 14.7% MOT17-09 65.0% 67.4% 62.9% 90.3% 96.8% 86.8% 12.2% MOT17-10 70.2% 72.5% 68.1% 87.3% 93.0% 80.1% 18.7% MOT17-11 80.2% 80.5% 80.0% 93.0% 93.6% 86.4% 11.3% MOT17-13 79.0% 79.6% 78.4% 90.6% 92.0% 82.4% 16.6% OVERALL 80.6% 81.8% 79.6% 92.9% 95.5% 88.1% 11.9% MOT20 Train IDF1 IDP IDR Rcll Prcn MOTA MOTP MOT20-01 85.9% 88.1% 83.8% 93.4% 98.2% 91.5% 12.6% MOT20-02 72.8% 74.6% 71.0% 93.2% 97.9% 91.0% 12.7% MOT20-03 93.0% 94.1% 92.0% 96.1% 98.3% 94.4% 13.7% MOT20-05 87.9% 88.9% 87.0% 96.0% 98.1% 94.1% 13.0% OVERALL 87.3% 88.4% 86.2% 95.6% 98.1% 93.7% 13.2% Commercial support # Tryolabs can provide commercial support, implement new features in Norfair or build video analytics tools for solving your challenging problems. Norfair powers several video analytics applications, such as the face mask detection tool. If you are interested, please contact us . Citing Norfair # For citations in academic publications, please export your desired citation format (BibTeX or other) from Zenodo . License # Copyright \u00a9 2022, Tryolabs . Released under the BSD 3-Clause .","title":"Home"},{"location":"#features","text":"Any detector expressing its detections as a series of (x, y) coordinates can be used with Norfair. This includes detectors performing tasks such as object or keypoint detection (see examples ). Modular. It can easily be inserted into complex video processing pipelines to add tracking to existing projects. At the same time, it is possible to build a video inference loop from scratch using just Norfair and a detector. Supports moving camera, re-identification with appearance embeddings, and n-dimensional object tracking (see Advanced features ). Norfair provides several predefined distance functions to compare tracked objects and detections. The distance functions can also be defined by the user, enabling the implementation of different tracking strategies. Fast. The only thing bounding inference speed will be the detection network feeding detections to Norfair. Norfair is built, used and maintained by Tryolabs .","title":"Features"},{"location":"#installation","text":"Norfair currently supports Python 3.7+. For the minimal version, install as: 1 pip install norfair To make Norfair install the dependencies to support more features, install as: 1 2 3 pip install norfair [ video ] # Adds several video helper features running on OpenCV pip install norfair [ metrics ] # Supports running MOT metrics evaluation pip install norfair [ metrics,video ] # Everything included If the needed dependencies are already present in the system, installing the minimal version of Norfair is enough for enabling the extra features. This is particularly useful for embedded devices, where installing compiled dependencies can be difficult, but they can sometimes come preinstalled with the system.","title":"Installation"},{"location":"#documentation","text":"Getting started guide . Official reference .","title":"Documentation"},{"location":"#examples-demos","text":"We provide several examples of how Norfair can be used to add tracking capabilities to different detectors, and also showcase more advanced features. Note: for ease of reproducibility, we provide Dockerfiles for all the demos. Even though Norfair does not need a GPU, the default configuration of most demos requires a GPU to be able to run the detectors. For this, make sure you install NVIDIA Container Toolkit so that your GPU can be shared with Docker. It is possible to run several demos with a CPU, but you will have to modify the scripts or tinker with the installation of their dependencies.","title":"Examples & demos"},{"location":"#adding-tracking-to-different-detectors","text":"Most tracking demos are showcased with vehicles and pedestrians, but the detectors are generally trained with many more classes from the COCO dataset . YOLOv7 : tracking object centroids or bounding boxes. YOLOv5 : tracking object centroids or bounding boxes. YOLOv4 : tracking object centroids. Detectron2 : tracking object centroids. AlphaPose : tracking human keypoints (pose estimation) and inserting Norfair into a complex existing pipeline using. OpenPose : tracking human keypoints. Tracking objects with YOLOPv2 , a model for traffic object detection, drivable road area segmentation, and lane line detection.","title":"Adding tracking to different detectors"},{"location":"#advanced-features","text":"Speed up pose estimation by extrapolating detections using OpenPose . Track both bounding boxes and human keypoints (multi-class), unifying the detections from a YOLO model and OpenPose. Re-identification (ReID) of tracked objects using appearance embeddings. This is a good starting point for scenarios with a lot of occlusion, in which the Kalman filter alone would struggle. Accurately track objects even if the camera is moving , by estimating camera motion potentially accounting for pan, tilt, rotation, movement in any direction, and zoom. Track points in 3D , using MediaPipe Objectron . Tracking of small objects , using SAHI: Slicing Aided Hyper Inference .","title":"Advanced features"},{"location":"#ros-integration","text":"To make it even easier to use Norfair in robotics projects, we now offer a version that integrates with the Robotic Operating System (ROS). We present a ROS package and a fully functional environment running on Docker to do the first steps with this package and start your first application easier.","title":"ROS integration"},{"location":"#benchmarking-and-profiling","text":"Kalman filter and distance function profiling using TRT pose estimator . Computation of MOT17 scores using motmetrics4norfair .","title":"Benchmarking and profiling"},{"location":"#how-it-works","text":"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. As an example we use 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. On the left you can see the points we get from Detectron2, and on the right how Norfair tracks them assigning a unique identifier through time. Even a straightforward distance function like this one can work when the tracking needed is simple. Norfair also provides several useful tools for creating a video inference loop. Here is what the full code for creating the previous example looks like, including the code needed to set up Detectron2: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import cv2 import numpy as np from detectron2.config import get_cfg from detectron2.engine import DefaultPredictor from norfair import Detection , Tracker , Video , draw_tracked_objects # Set up Detectron2 object detector cfg = get_cfg () cfg . merge_from_file ( \"demos/faster_rcnn_R_50_FPN_3x.yaml\" ) cfg . MODEL . ROI_HEADS . SCORE_THRESH_TEST = 0.5 cfg . MODEL . WEIGHTS = \"detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl\" detector = DefaultPredictor ( cfg ) # Norfair video = Video ( input_path = \"video.mp4\" ) tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 20 ) for frame in video : detections = detector ( cv2 . cvtColor ( frame , cv2 . COLOR_BGR2RGB )) detections = [ Detection ( p ) for p in detections [ 'instances' ] . pred_boxes . get_centers () . cpu () . numpy ()] tracked_objects = tracker . update ( detections = detections ) draw_tracked_objects ( frame , tracked_objects ) video . write ( frame ) The video and drawing tools use OpenCV frames, so they are compatible with most Python video code available online. The point tracking is based on SORT generalized to detections consisting of a dynamically changing number of points per detection.","title":"How it works"},{"location":"#motivation","text":"Trying out the latest state-of-the-art detectors normally requires running repositories that weren't intended to be easy to use. These tend to be repositories associated with a research paper describing a novel new way of doing detection, and they are therefore intended to be run as a one-off evaluation script to get some result metric to publish on a particular research paper. This explains why they tend to not be easy to run as inference scripts, or why extracting the core model to use in another standalone script isn't always trivial. Norfair was born out of the need to quickly add a simple layer of tracking over a wide range of newly released SOTA detectors. It was designed to seamlessly be plugged into a complex, highly coupled code base, with minimum effort. Norfair provides a series of modular but compatible tools, which you can pick and choose to use in your project.","title":"Motivation"},{"location":"#comparison-to-other-trackers","text":"Norfair's contribution to Python's object tracker library repertoire is its ability to work with any object detector by being able to work with a variable number of points per detection, and the ability for the user to heavily customize the tracker by creating their own distance function. If you are looking for a tracker, here are some other projects worth noting: OpenCV includes several tracking solutions like KCF Tracker and MedianFlow Tracker which are run by making the user select a part of the frame to track, and then letting the tracker follow that area. They tend not to be run on top of a detector and are not very robust. dlib includes a correlation single object tracker. You have to create your own multiple object tracker on top of it yourself if you want to track multiple objects with it. AlphaPose just released a new version of their human pose tracker. This tracker is tightly integrated into their code base, and to the task of tracking human poses. SORT and Deep SORT are similar to this repo in that they use Kalman filters (and a deep embedding for Deep SORT), but they are hardcoded to a fixed distance function and to tracking boxes. Norfair also adds some filtering when matching tracked objects with detections, and changes the Hungarian Algorithm for its own distance minimizer. Both these repos are also released under the GPL license, which might be an issue for some individuals or companies because the source code of derivative works needs to be published.","title":"Comparison to other trackers"},{"location":"#benchmarks","text":"MOT17 and MOT20 results obtained using motmetrics4norfair demo script on the train split. We used detections obtained with ByteTrack's YOLOX object detection model. MOT17 Train IDF1 IDP IDR Rcll Prcn MOTA MOTP MOT17-02 61.3% 63.6% 59.0% 86.8% 93.5% 79.9% 14.8% MOT17-04 93.3% 93.6% 93.0% 98.6% 99.3% 97.9% 07.9% MOT17-05 77.8% 77.7% 77.8% 85.9% 85.8% 71.2% 14.7% MOT17-09 65.0% 67.4% 62.9% 90.3% 96.8% 86.8% 12.2% MOT17-10 70.2% 72.5% 68.1% 87.3% 93.0% 80.1% 18.7% MOT17-11 80.2% 80.5% 80.0% 93.0% 93.6% 86.4% 11.3% MOT17-13 79.0% 79.6% 78.4% 90.6% 92.0% 82.4% 16.6% OVERALL 80.6% 81.8% 79.6% 92.9% 95.5% 88.1% 11.9% MOT20 Train IDF1 IDP IDR Rcll Prcn MOTA MOTP MOT20-01 85.9% 88.1% 83.8% 93.4% 98.2% 91.5% 12.6% MOT20-02 72.8% 74.6% 71.0% 93.2% 97.9% 91.0% 12.7% MOT20-03 93.0% 94.1% 92.0% 96.1% 98.3% 94.4% 13.7% MOT20-05 87.9% 88.9% 87.0% 96.0% 98.1% 94.1% 13.0% OVERALL 87.3% 88.4% 86.2% 95.6% 98.1% 93.7% 13.2%","title":"Benchmarks"},{"location":"#commercial-support","text":"Tryolabs can provide commercial support, implement new features in Norfair or build video analytics tools for solving your challenging problems. Norfair powers several video analytics applications, such as the face mask detection tool. If you are interested, please contact us .","title":"Commercial support"},{"location":"#citing-norfair","text":"For citations in academic publications, please export your desired citation format (BibTeX or other) from Zenodo .","title":"Citing Norfair"},{"location":"#license","text":"Copyright \u00a9 2022, Tryolabs . Released under the BSD 3-Clause .","title":"License"},{"location":"getting_started/","text":"Getting Started # Norfair's goal is to easily track multiple objects in videos based on the frame-by-frame detections of a user-defined model. Model or Detector # We recommend first deciding and setting up the model and then adding Norfair on top of it. Models trained for any form of object detection or keypoint detection (including pose estimation ) are all supported. You can check some of the integrations we have as examples: Yolov7 , Yolov5 and Yolov4 Detectron2 Alphapose Openpose MMDetection Any other model trained on one of the supported tasks is also supported and should be easy to integrate with Norfair, regardless of whether it uses Pytorch, TensorFlow, or other. If you are unsure of which model to use, Yolov7 is a good starting point since it's easy to set up and offers models of different sizes pre-trained on object detection and pose estimation. Note Norfair is a Detection-Based-Tracker (DBT) and as such, its performance is highly dependent on the performance of the model of choice. The detections from the model will need to be wrapped in an instance of Detection before passing them to Norfair. Install # Installing Norfair is extremely easy, simply run pip install norfair to install the latest version from PyPI . You can also install the latest version from the master branch using pip install git+https://github.com/tryolabs/norfair.git@master#egg=norfair Video # Norfair offers optional functionality to process videos (mp4 and mov formats are supported) or capture a live feed from a camera. To use this functionality you need to install Norfair with the video extra using this command: pip install norfair[video] . Check the Video class for more info on how to use it. Tracking # Let's dive right into a simple example in the following snippet: 1 2 3 4 5 6 7 8 9 10 11 12 from norfair import Detection , Tracker , Video , draw_tracked_objects detector = MyDetector () # Set up a detector video = Video ( input_path = \"video.mp4\" ) tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 100 ) for frame in video : detections = detector ( frame ) norfair_detections = [ Detection ( points ) for points in detections ] tracked_objects = tracker . update ( detections = norfair_detections ) draw_tracked_objects ( frame , tracked_objects ) video . write ( frame ) The tracker is created and then the detections are fed to it one frame at a time in order. This method is called online tracking and allows Norfair to be used in live feeds and real-time scenarios where future frames are not available. Norfair includes functionality for creating an output video with drawings which is useful for evaluating and debugging. We usually start with this simple setup and move from there. Next Steps # The next steps depend a lot on your goal and the result of evaluating the output videos, nevertheless here are some pointers that might help you solve common problems Detection Issues # Most common problem is that the tracking has errors or is not precise enough. In this case, the first thing to check is whether this is a detection error or a tracking error. As mentioned above if the detector fails the tracking will suffer. To debug this use draw_points or draw_boxes to inspect the detections and analyze if they are precise enough. If you are filtering the detections based on scores, this is a good time to tweak the threshold. If you decide that the detections are not good enough you can try a different architecture, a bigger version of the model, or consider fine-tuning the model on your domain. Tracking Issues # After inspecting the detections you might find issues with the tracking, several things can go wrong with tracking but here is a list of common errors and things to try: Objects take too long to start , this can have multiple causes: initialization_delay is too big on the Tracker. Makes the TrackedObject stay on initializing for too long, 3 is usually a good value to start with. distance_threshold is too small on the Tracker. Prevents the Detections to be matched with the correct TrackedObject. The best value depends on the distance used. Incorrect distance_function on the Tracker. Some distances might not be valid in some cases, for instance, if using IoU but the objects in your video move so quickly that there is never an overlap between the detections of consecutive frames. Try different distances, euclidean or create_normalized_mean_euclidean_distance are good starting points. Objects take too long to disappear . Lower hit_counter_max on the Tracker. Points or bounding boxes jitter too much . Increase R (measurement error) or lower Q (estimate or process error) on the OptimizedKalmanFilterFactory or FilterPyKalmanFilterFactory . This makes the Kalman Filter put less weight on the measurements and trust more on the estimate, stabilizing the result. Camera motion confuses the Tracker. If the camera moves, the apparent movement of objects can become too erratic for the Tracker. Use MotionEstimator . Incorrect matches between Detections and TrackedObjects, a couple of scenarios can cause this: distance_threshold is too big so the Tracker matches Detections to TrackedObjects that are simply too far. Lower the threshold until you fix the error, the correct value will depend on the distance function that you're using. Mismatches when objects overlap. In this case, tracking becomes more challenging, usually, the quality of the detection degrades causing one of the objects to be missed or creating a single big detection that includes both objects. On top of the detection issues, the tracker needs to decide which detection should be matched to which TrackedObject which can be error-prone if only considering spatial information. The solution is not easy but incorporating the notion of the appearance similarity based on some kind of embedding to your distance_function can help. Can't recover an object after occlusions . Use ReID distance, see this demo for an example but for real-world use you will need a good ReID model that can provide good embeddings.","title":"Getting Started"},{"location":"getting_started/#getting-started","text":"Norfair's goal is to easily track multiple objects in videos based on the frame-by-frame detections of a user-defined model.","title":"Getting Started"},{"location":"getting_started/#model-or-detector","text":"We recommend first deciding and setting up the model and then adding Norfair on top of it. Models trained for any form of object detection or keypoint detection (including pose estimation ) are all supported. You can check some of the integrations we have as examples: Yolov7 , Yolov5 and Yolov4 Detectron2 Alphapose Openpose MMDetection Any other model trained on one of the supported tasks is also supported and should be easy to integrate with Norfair, regardless of whether it uses Pytorch, TensorFlow, or other. If you are unsure of which model to use, Yolov7 is a good starting point since it's easy to set up and offers models of different sizes pre-trained on object detection and pose estimation. Note Norfair is a Detection-Based-Tracker (DBT) and as such, its performance is highly dependent on the performance of the model of choice. The detections from the model will need to be wrapped in an instance of Detection before passing them to Norfair.","title":"Model or Detector"},{"location":"getting_started/#install","text":"Installing Norfair is extremely easy, simply run pip install norfair to install the latest version from PyPI . You can also install the latest version from the master branch using pip install git+https://github.com/tryolabs/norfair.git@master#egg=norfair","title":"Install"},{"location":"getting_started/#video","text":"Norfair offers optional functionality to process videos (mp4 and mov formats are supported) or capture a live feed from a camera. To use this functionality you need to install Norfair with the video extra using this command: pip install norfair[video] . Check the Video class for more info on how to use it.","title":"Video"},{"location":"getting_started/#tracking","text":"Let's dive right into a simple example in the following snippet: 1 2 3 4 5 6 7 8 9 10 11 12 from norfair import Detection , Tracker , Video , draw_tracked_objects detector = MyDetector () # Set up a detector video = Video ( input_path = \"video.mp4\" ) tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 100 ) for frame in video : detections = detector ( frame ) norfair_detections = [ Detection ( points ) for points in detections ] tracked_objects = tracker . update ( detections = norfair_detections ) draw_tracked_objects ( frame , tracked_objects ) video . write ( frame ) The tracker is created and then the detections are fed to it one frame at a time in order. This method is called online tracking and allows Norfair to be used in live feeds and real-time scenarios where future frames are not available. Norfair includes functionality for creating an output video with drawings which is useful for evaluating and debugging. We usually start with this simple setup and move from there.","title":"Tracking"},{"location":"getting_started/#next-steps","text":"The next steps depend a lot on your goal and the result of evaluating the output videos, nevertheless here are some pointers that might help you solve common problems","title":"Next Steps"},{"location":"getting_started/#detection-issues","text":"Most common problem is that the tracking has errors or is not precise enough. In this case, the first thing to check is whether this is a detection error or a tracking error. As mentioned above if the detector fails the tracking will suffer. To debug this use draw_points or draw_boxes to inspect the detections and analyze if they are precise enough. If you are filtering the detections based on scores, this is a good time to tweak the threshold. If you decide that the detections are not good enough you can try a different architecture, a bigger version of the model, or consider fine-tuning the model on your domain.","title":"Detection Issues"},{"location":"getting_started/#tracking-issues","text":"After inspecting the detections you might find issues with the tracking, several things can go wrong with tracking but here is a list of common errors and things to try: Objects take too long to start , this can have multiple causes: initialization_delay is too big on the Tracker. Makes the TrackedObject stay on initializing for too long, 3 is usually a good value to start with. distance_threshold is too small on the Tracker. Prevents the Detections to be matched with the correct TrackedObject. The best value depends on the distance used. Incorrect distance_function on the Tracker. Some distances might not be valid in some cases, for instance, if using IoU but the objects in your video move so quickly that there is never an overlap between the detections of consecutive frames. Try different distances, euclidean or create_normalized_mean_euclidean_distance are good starting points. Objects take too long to disappear . Lower hit_counter_max on the Tracker. Points or bounding boxes jitter too much . Increase R (measurement error) or lower Q (estimate or process error) on the OptimizedKalmanFilterFactory or FilterPyKalmanFilterFactory . This makes the Kalman Filter put less weight on the measurements and trust more on the estimate, stabilizing the result. Camera motion confuses the Tracker. If the camera moves, the apparent movement of objects can become too erratic for the Tracker. Use MotionEstimator . Incorrect matches between Detections and TrackedObjects, a couple of scenarios can cause this: distance_threshold is too big so the Tracker matches Detections to TrackedObjects that are simply too far. Lower the threshold until you fix the error, the correct value will depend on the distance function that you're using. Mismatches when objects overlap. In this case, tracking becomes more challenging, usually, the quality of the detection degrades causing one of the objects to be missed or creating a single big detection that includes both objects. On top of the detection issues, the tracker needs to decide which detection should be matched to which TrackedObject which can be error-prone if only considering spatial information. The solution is not easy but incorporating the notion of the appearance similarity based on some kind of embedding to your distance_function can help. Can't recover an object after occlusions . Use ReID distance, see this demo for an example but for real-world use you will need a good ReID model that can provide good embeddings.","title":"Tracking Issues"},{"location":"reference/","text":"Reference # A customizable lightweight Python library for real-time multi-object tracking. Examples: >>> from norfair import Detection , Tracker , Video , draw_tracked_objects >>> detector = MyDetector () # Set up a detector >>> video = Video ( input_path = \"video.mp4\" ) >>> tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 50 ) >>> for frame in video : >>> detections = detector ( frame ) >>> norfair_detections = [ Detection ( points ) for points in detections ] >>> tracked_objects = tracker . update ( detections = norfair_detections ) >>> draw_tracked_objects ( frame , tracked_objects ) >>> video . write ( frame )","title":"Reference"},{"location":"reference/#reference","text":"A customizable lightweight Python library for real-time multi-object tracking. Examples: >>> from norfair import Detection , Tracker , Video , draw_tracked_objects >>> detector = MyDetector () # Set up a detector >>> video = Video ( input_path = \"video.mp4\" ) >>> tracker = Tracker ( distance_function = \"euclidean\" , distance_threshold = 50 ) >>> for frame in video : >>> detections = detector ( frame ) >>> norfair_detections = [ Detection ( points ) for points in detections ] >>> tracked_objects = tracker . update ( detections = norfair_detections ) >>> draw_tracked_objects ( frame , tracked_objects ) >>> video . write ( frame )","title":"Reference"},{"location":"reference/camera_motion/","text":"Camera Motion # Camera motion stimation module. CoordinatesTransformation # Bases: ABC Abstract class representing a coordinate transformation. Detections' and tracked objects' coordinates can be interpreted in 2 reference: Relative : their position on the current frame, (0, 0) is top left Absolute : their position on an fixed space, (0, 0) is the top left of the first frame of the video. Therefore, coordinate transformation in this context is a class that can transform coordinates in one reference to another. Source code in norfair/camera_motion.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class CoordinatesTransformation ( ABC ): \"\"\" Abstract class representing a coordinate transformation. Detections' and tracked objects' coordinates can be interpreted in 2 reference: - _Relative_: their position on the current frame, (0, 0) is top left - _Absolute_: their position on an fixed space, (0, 0) is the top left of the first frame of the video. Therefore, coordinate transformation in this context is a class that can transform coordinates in one reference to another. \"\"\" @abstractmethod def abs_to_rel ( self , points : np . ndarray ) -> np . ndarray : pass @abstractmethod def rel_to_abs ( self , points : np . ndarray ) -> np . ndarray : pass TransformationGetter # Bases: ABC Abstract class representing a method for finding CoordinatesTransformation between 2 sets of points Source code in norfair/camera_motion.py 41 42 43 44 45 46 47 48 49 50 class TransformationGetter ( ABC ): \"\"\" Abstract class representing a method for finding CoordinatesTransformation between 2 sets of points \"\"\" @abstractmethod def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , CoordinatesTransformation ]: pass TranslationTransformation # Bases: CoordinatesTransformation Coordinate transformation between points using a simple translation Parameters: Name Type Description Default movement_vector np . ndarray The vector representing the translation. required Source code in norfair/camera_motion.py 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class TranslationTransformation ( CoordinatesTransformation ): \"\"\" Coordinate transformation between points using a simple translation Parameters ---------- movement_vector : np.ndarray The vector representing the translation. \"\"\" def __init__ ( self , movement_vector ): self . movement_vector = movement_vector def abs_to_rel ( self , points : np . ndarray ): return points + self . movement_vector def rel_to_abs ( self , points : np . ndarray ): return points - self . movement_vector TranslationTransformationGetter # Bases: TransformationGetter Calculates TranslationTransformation between points. The camera movement is calculated as the mode of optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the translation, for this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters: Name Type Description Default bin_size float Before calculatin the mode, optiocal flow is bucketized into bins of this size. 0.2 proportion_points_used_threshold float Proportion of points that must be matched, otherwise the reference frame must be updated. 0.9 Source code in norfair/camera_motion.py 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 class TranslationTransformationGetter ( TransformationGetter ): \"\"\" Calculates TranslationTransformation between points. The camera movement is calculated as the mode of optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the translation, for this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters ---------- bin_size : float Before calculatin the mode, optiocal flow is bucketized into bins of this size. proportion_points_used_threshold: float Proportion of points that must be matched, otherwise the reference frame must be updated. \"\"\" def __init__ ( self , bin_size : float = 0.2 , proportion_points_used_threshold : float = 0.9 ) -> None : self . bin_size = bin_size self . proportion_points_used_threshold = proportion_points_used_threshold self . data = None def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , TranslationTransformation ]: # get flow flow = curr_pts - prev_pts # get mode flow = np . around ( flow / self . bin_size ) * self . bin_size unique_flows , counts = np . unique ( flow , axis = 0 , return_counts = True ) max_index = counts . argmax () proportion_points_used = counts [ max_index ] / len ( prev_pts ) update_prvs = proportion_points_used < self . proportion_points_used_threshold flow_mode = unique_flows [ max_index ] try : flow_mode += self . data except TypeError : pass if update_prvs : self . data = flow_mode return update_prvs , TranslationTransformation ( flow_mode ) HomographyTransformation # Bases: CoordinatesTransformation Coordinate transformation beweent points using an homography Parameters: Name Type Description Default homography_matrix np . ndarray The matrix representing the homography required Source code in norfair/camera_motion.py 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 class HomographyTransformation ( CoordinatesTransformation ): \"\"\" Coordinate transformation beweent points using an homography Parameters ---------- homography_matrix : np.ndarray The matrix representing the homography \"\"\" def __init__ ( self , homography_matrix : np . ndarray ): self . homography_matrix = homography_matrix self . inverse_homography_matrix = np . linalg . inv ( homography_matrix ) def abs_to_rel ( self , points : np . ndarray ): ones = np . ones (( len ( points ), 1 )) points_with_ones = np . hstack (( points , ones )) points_transformed = points_with_ones @ self . homography_matrix . T points_transformed = points_transformed / points_transformed [:, - 1 ] . reshape ( - 1 , 1 ) return points_transformed [:, : 2 ] def rel_to_abs ( self , points : np . ndarray ): ones = np . ones (( len ( points ), 1 )) points_with_ones = np . hstack (( points , ones )) points_transformed = points_with_ones @ self . inverse_homography_matrix . T points_transformed = points_transformed / points_transformed [:, - 1 ] . reshape ( - 1 , 1 ) return points_transformed [:, : 2 ] HomographyTransformationGetter # Bases: TransformationGetter Calculates HomographyTransformation between points. The camera movement is represented as an homography that matches the optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the homography, often resulting in the identity. For this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters: Name Type Description Default method Optional [ int ], optional One of openCV's method for finding homographies. Valid options are: [0, cv.RANSAC, cv.LMEDS, cv.RHO] , by default cv.RANSAC None ransac_reproj_threshold int , optional Maximum allowed reprojection error to treat a point pair as an inlier. More info in links below. 3 max_iters int , optional The maximum number of RANSAC iterations. More info in links below. 2000 confidence float , optional Confidence level, must be between 0 and 1. More info in links below. 0.995 proportion_points_used_threshold float , optional Proportion of points that must be matched, otherwise the reference frame must be updated. 0.9 See Also # opencv.findHomography Source code in norfair/camera_motion.py 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 class HomographyTransformationGetter ( TransformationGetter ): \"\"\" Calculates HomographyTransformation between points. The camera movement is represented as an homography that matches the optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the homography, often resulting in the identity. For this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters ---------- method : Optional[int], optional One of openCV's method for finding homographies. Valid options are: `[0, cv.RANSAC, cv.LMEDS, cv.RHO]`, by default `cv.RANSAC` ransac_reproj_threshold : int, optional Maximum allowed reprojection error to treat a point pair as an inlier. More info in links below. max_iters : int, optional The maximum number of RANSAC iterations. More info in links below. confidence : float, optional Confidence level, must be between 0 and 1. More info in links below. proportion_points_used_threshold : float, optional Proportion of points that must be matched, otherwise the reference frame must be updated. See Also -------- [opencv.findHomography](https://docs.opencv.org/3.4/d9/d0c/group__calib3d.html#ga4abc2ece9fab9398f2e560d53c8c9780) \"\"\" def __init__ ( self , method : Optional [ int ] = None , ransac_reproj_threshold : int = 3 , max_iters : int = 2000 , confidence : float = 0.995 , proportion_points_used_threshold : float = 0.9 , ) -> None : self . data = None if method is None : method = cv2 . RANSAC self . method = method self . ransac_reproj_threshold = ransac_reproj_threshold self . max_iters = max_iters self . confidence = confidence self . proportion_points_used_threshold = proportion_points_used_threshold def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , HomographyTransformation ]: homography_matrix , points_used = cv2 . findHomography ( prev_pts , curr_pts , method = self . method , ransacReprojThreshold = self . ransac_reproj_threshold , maxIters = self . max_iters , confidence = self . confidence , ) proportion_points_used = np . sum ( points_used ) / len ( points_used ) update_prvs = proportion_points_used < self . proportion_points_used_threshold try : homography_matrix = homography_matrix @ self . data except ( TypeError , ValueError ): pass if update_prvs : self . data = homography_matrix return update_prvs , HomographyTransformation ( homography_matrix ) MotionEstimator # Estimator of the motion of the camera. Uses optical flow to estimate the motion of the camera from frame to frame. The optical flow is calculated on a sample of strong points (corners). Parameters: Name Type Description Default max_points int , optional Maximum amount of points sampled. More points make the estimation process slower but more precise 200 min_distance int , optional Minimum distance between the sample points. 15 block_size int , optional Size of an average block when finding the corners. More info in links below. 3 transformations_getter TransformationGetter , optional An instance of TransformationGetter. By default HomographyTransformationGetter None draw_flow bool , optional Draws the optical flow on the frame for debugging. False flow_color Optional [ Tuple [ int , int , int ]], optional Color of the drawing, by default blue. None quality_level float , optional Parameter characterizing the minimal accepted quality of image corners. 0.01 Examples: >>> from norfair import Tracker , Video >>> from norfair.camera_motion MotionEstimator >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> motion_estimator = MotionEstimator () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> coord_transformation = motion_estimator . update ( frame ) >>> tracked_objects = tracker . update ( detections , coord_transformations = coord_transformation ) See Also # For more infor on how the points are sampled: OpenCV.goodFeaturesToTrack Source code in norfair/camera_motion.py 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 class MotionEstimator : \"\"\" Estimator of the motion of the camera. Uses optical flow to estimate the motion of the camera from frame to frame. The optical flow is calculated on a sample of strong points (corners). Parameters ---------- max_points : int, optional Maximum amount of points sampled. More points make the estimation process slower but more precise min_distance : int, optional Minimum distance between the sample points. block_size : int, optional Size of an average block when finding the corners. More info in links below. transformations_getter : TransformationGetter, optional An instance of TransformationGetter. By default [`HomographyTransformationGetter`][norfair.camera_motion.HomographyTransformationGetter] draw_flow : bool, optional Draws the optical flow on the frame for debugging. flow_color : Optional[Tuple[int, int, int]], optional Color of the drawing, by default blue. quality_level : float, optional Parameter characterizing the minimal accepted quality of image corners. Examples -------- >>> from norfair import Tracker, Video >>> from norfair.camera_motion MotionEstimator >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> motion_estimator = MotionEstimator() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> coord_transformation = motion_estimator.update(frame) >>> tracked_objects = tracker.update(detections, coord_transformations=coord_transformation) See Also -------- For more infor on how the points are sampled: [OpenCV.goodFeaturesToTrack](https://docs.opencv.org/3.4/dd/d1a/group__imgproc__feature.html#ga1d6bb77486c8f92d79c8793ad995d541) \"\"\" def __init__ ( self , max_points : int = 200 , min_distance : int = 15 , block_size : int = 3 , transformations_getter : TransformationGetter = None , draw_flow : bool = False , flow_color : Optional [ Tuple [ int , int , int ]] = None , quality_level : float = 0.01 , ): self . max_points = max_points self . min_distance = min_distance self . block_size = block_size self . draw_flow = draw_flow if self . draw_flow and flow_color is None : flow_color = [ 0 , 0 , 100 ] self . flow_color = flow_color self . gray_prvs = None self . prev_pts = None if transformations_getter is None : transformations_getter = HomographyTransformationGetter () self . transformations_getter = transformations_getter self . prev_mask = None self . gray_next = None self . quality_level = quality_level def update ( self , frame : np . ndarray , mask : np . ndarray = None ) -> CoordinatesTransformation : \"\"\" Estimate camera motion for each frame Parameters ---------- frame : np.ndarray The frame. mask : np.ndarray, optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape `(frame.shape[0], frame.shape[1])`, dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. Returns ------- CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. \"\"\" self . gray_next = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2GRAY ) if self . gray_prvs is None : self . gray_prvs = self . gray_next self . prev_mask = mask curr_pts , self . prev_pts = _get_sparse_flow ( self . gray_next , self . gray_prvs , self . prev_pts , self . max_points , self . min_distance , self . block_size , self . prev_mask , quality_level = self . quality_level , ) if self . draw_flow : for ( curr , prev ) in zip ( curr_pts , self . prev_pts ): c = tuple ( curr . astype ( int ) . ravel ()) p = tuple ( prev . astype ( int ) . ravel ()) cv2 . line ( frame , c , p , self . flow_color , 2 ) cv2 . circle ( frame , c , 3 , self . flow_color , - 1 ) update_prvs , coord_transformations = self . transformations_getter ( curr_pts , self . prev_pts , ) if update_prvs : self . gray_prvs = self . gray_next self . prev_pts = None self . prev_mask = mask return coord_transformations update ( frame , mask = None ) # Estimate camera motion for each frame Parameters: Name Type Description Default frame np . ndarray The frame. required mask np . ndarray , optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape (frame.shape[0], frame.shape[1]) , dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. None Returns: Type Description CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. Source code in norfair/camera_motion.py 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 def update ( self , frame : np . ndarray , mask : np . ndarray = None ) -> CoordinatesTransformation : \"\"\" Estimate camera motion for each frame Parameters ---------- frame : np.ndarray The frame. mask : np.ndarray, optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape `(frame.shape[0], frame.shape[1])`, dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. Returns ------- CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. \"\"\" self . gray_next = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2GRAY ) if self . gray_prvs is None : self . gray_prvs = self . gray_next self . prev_mask = mask curr_pts , self . prev_pts = _get_sparse_flow ( self . gray_next , self . gray_prvs , self . prev_pts , self . max_points , self . min_distance , self . block_size , self . prev_mask , quality_level = self . quality_level , ) if self . draw_flow : for ( curr , prev ) in zip ( curr_pts , self . prev_pts ): c = tuple ( curr . astype ( int ) . ravel ()) p = tuple ( prev . astype ( int ) . ravel ()) cv2 . line ( frame , c , p , self . flow_color , 2 ) cv2 . circle ( frame , c , 3 , self . flow_color , - 1 ) update_prvs , coord_transformations = self . transformations_getter ( curr_pts , self . prev_pts , ) if update_prvs : self . gray_prvs = self . gray_next self . prev_pts = None self . prev_mask = mask return coord_transformations","title":"Camera Motion"},{"location":"reference/camera_motion/#camera-motion","text":"Camera motion stimation module.","title":"Camera Motion"},{"location":"reference/camera_motion/#norfair.camera_motion.CoordinatesTransformation","text":"Bases: ABC Abstract class representing a coordinate transformation. Detections' and tracked objects' coordinates can be interpreted in 2 reference: Relative : their position on the current frame, (0, 0) is top left Absolute : their position on an fixed space, (0, 0) is the top left of the first frame of the video. Therefore, coordinate transformation in this context is a class that can transform coordinates in one reference to another. Source code in norfair/camera_motion.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class CoordinatesTransformation ( ABC ): \"\"\" Abstract class representing a coordinate transformation. Detections' and tracked objects' coordinates can be interpreted in 2 reference: - _Relative_: their position on the current frame, (0, 0) is top left - _Absolute_: their position on an fixed space, (0, 0) is the top left of the first frame of the video. Therefore, coordinate transformation in this context is a class that can transform coordinates in one reference to another. \"\"\" @abstractmethod def abs_to_rel ( self , points : np . ndarray ) -> np . ndarray : pass @abstractmethod def rel_to_abs ( self , points : np . ndarray ) -> np . ndarray : pass","title":"CoordinatesTransformation"},{"location":"reference/camera_motion/#norfair.camera_motion.TransformationGetter","text":"Bases: ABC Abstract class representing a method for finding CoordinatesTransformation between 2 sets of points Source code in norfair/camera_motion.py 41 42 43 44 45 46 47 48 49 50 class TransformationGetter ( ABC ): \"\"\" Abstract class representing a method for finding CoordinatesTransformation between 2 sets of points \"\"\" @abstractmethod def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , CoordinatesTransformation ]: pass","title":"TransformationGetter"},{"location":"reference/camera_motion/#norfair.camera_motion.TranslationTransformation","text":"Bases: CoordinatesTransformation Coordinate transformation between points using a simple translation Parameters: Name Type Description Default movement_vector np . ndarray The vector representing the translation. required Source code in norfair/camera_motion.py 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class TranslationTransformation ( CoordinatesTransformation ): \"\"\" Coordinate transformation between points using a simple translation Parameters ---------- movement_vector : np.ndarray The vector representing the translation. \"\"\" def __init__ ( self , movement_vector ): self . movement_vector = movement_vector def abs_to_rel ( self , points : np . ndarray ): return points + self . movement_vector def rel_to_abs ( self , points : np . ndarray ): return points - self . movement_vector","title":"TranslationTransformation"},{"location":"reference/camera_motion/#norfair.camera_motion.TranslationTransformationGetter","text":"Bases: TransformationGetter Calculates TranslationTransformation between points. The camera movement is calculated as the mode of optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the translation, for this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters: Name Type Description Default bin_size float Before calculatin the mode, optiocal flow is bucketized into bins of this size. 0.2 proportion_points_used_threshold float Proportion of points that must be matched, otherwise the reference frame must be updated. 0.9 Source code in norfair/camera_motion.py 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 class TranslationTransformationGetter ( TransformationGetter ): \"\"\" Calculates TranslationTransformation between points. The camera movement is calculated as the mode of optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the translation, for this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters ---------- bin_size : float Before calculatin the mode, optiocal flow is bucketized into bins of this size. proportion_points_used_threshold: float Proportion of points that must be matched, otherwise the reference frame must be updated. \"\"\" def __init__ ( self , bin_size : float = 0.2 , proportion_points_used_threshold : float = 0.9 ) -> None : self . bin_size = bin_size self . proportion_points_used_threshold = proportion_points_used_threshold self . data = None def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , TranslationTransformation ]: # get flow flow = curr_pts - prev_pts # get mode flow = np . around ( flow / self . bin_size ) * self . bin_size unique_flows , counts = np . unique ( flow , axis = 0 , return_counts = True ) max_index = counts . argmax () proportion_points_used = counts [ max_index ] / len ( prev_pts ) update_prvs = proportion_points_used < self . proportion_points_used_threshold flow_mode = unique_flows [ max_index ] try : flow_mode += self . data except TypeError : pass if update_prvs : self . data = flow_mode return update_prvs , TranslationTransformation ( flow_mode )","title":"TranslationTransformationGetter"},{"location":"reference/camera_motion/#norfair.camera_motion.HomographyTransformation","text":"Bases: CoordinatesTransformation Coordinate transformation beweent points using an homography Parameters: Name Type Description Default homography_matrix np . ndarray The matrix representing the homography required Source code in norfair/camera_motion.py 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 class HomographyTransformation ( CoordinatesTransformation ): \"\"\" Coordinate transformation beweent points using an homography Parameters ---------- homography_matrix : np.ndarray The matrix representing the homography \"\"\" def __init__ ( self , homography_matrix : np . ndarray ): self . homography_matrix = homography_matrix self . inverse_homography_matrix = np . linalg . inv ( homography_matrix ) def abs_to_rel ( self , points : np . ndarray ): ones = np . ones (( len ( points ), 1 )) points_with_ones = np . hstack (( points , ones )) points_transformed = points_with_ones @ self . homography_matrix . T points_transformed = points_transformed / points_transformed [:, - 1 ] . reshape ( - 1 , 1 ) return points_transformed [:, : 2 ] def rel_to_abs ( self , points : np . ndarray ): ones = np . ones (( len ( points ), 1 )) points_with_ones = np . hstack (( points , ones )) points_transformed = points_with_ones @ self . inverse_homography_matrix . T points_transformed = points_transformed / points_transformed [:, - 1 ] . reshape ( - 1 , 1 ) return points_transformed [:, : 2 ]","title":"HomographyTransformation"},{"location":"reference/camera_motion/#norfair.camera_motion.HomographyTransformationGetter","text":"Bases: TransformationGetter Calculates HomographyTransformation between points. The camera movement is represented as an homography that matches the optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the homography, often resulting in the identity. For this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters: Name Type Description Default method Optional [ int ], optional One of openCV's method for finding homographies. Valid options are: [0, cv.RANSAC, cv.LMEDS, cv.RHO] , by default cv.RANSAC None ransac_reproj_threshold int , optional Maximum allowed reprojection error to treat a point pair as an inlier. More info in links below. 3 max_iters int , optional The maximum number of RANSAC iterations. More info in links below. 2000 confidence float , optional Confidence level, must be between 0 and 1. More info in links below. 0.995 proportion_points_used_threshold float , optional Proportion of points that must be matched, otherwise the reference frame must be updated. 0.9","title":"HomographyTransformationGetter"},{"location":"reference/camera_motion/#norfair.camera_motion.HomographyTransformationGetter--see-also","text":"opencv.findHomography Source code in norfair/camera_motion.py 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 class HomographyTransformationGetter ( TransformationGetter ): \"\"\" Calculates HomographyTransformation between points. The camera movement is represented as an homography that matches the optical flow between the previous reference frame and the current. Comparing consecutive frames can make differences too small to correctly estimate the homography, often resulting in the identity. For this reason the reference frame is kept fixed as we progress through the video. Eventually, if the transformation is no longer able to match enough points, the reference frame is updated. Parameters ---------- method : Optional[int], optional One of openCV's method for finding homographies. Valid options are: `[0, cv.RANSAC, cv.LMEDS, cv.RHO]`, by default `cv.RANSAC` ransac_reproj_threshold : int, optional Maximum allowed reprojection error to treat a point pair as an inlier. More info in links below. max_iters : int, optional The maximum number of RANSAC iterations. More info in links below. confidence : float, optional Confidence level, must be between 0 and 1. More info in links below. proportion_points_used_threshold : float, optional Proportion of points that must be matched, otherwise the reference frame must be updated. See Also -------- [opencv.findHomography](https://docs.opencv.org/3.4/d9/d0c/group__calib3d.html#ga4abc2ece9fab9398f2e560d53c8c9780) \"\"\" def __init__ ( self , method : Optional [ int ] = None , ransac_reproj_threshold : int = 3 , max_iters : int = 2000 , confidence : float = 0.995 , proportion_points_used_threshold : float = 0.9 , ) -> None : self . data = None if method is None : method = cv2 . RANSAC self . method = method self . ransac_reproj_threshold = ransac_reproj_threshold self . max_iters = max_iters self . confidence = confidence self . proportion_points_used_threshold = proportion_points_used_threshold def __call__ ( self , curr_pts : np . ndarray , prev_pts : np . ndarray ) -> Tuple [ bool , HomographyTransformation ]: homography_matrix , points_used = cv2 . findHomography ( prev_pts , curr_pts , method = self . method , ransacReprojThreshold = self . ransac_reproj_threshold , maxIters = self . max_iters , confidence = self . confidence , ) proportion_points_used = np . sum ( points_used ) / len ( points_used ) update_prvs = proportion_points_used < self . proportion_points_used_threshold try : homography_matrix = homography_matrix @ self . data except ( TypeError , ValueError ): pass if update_prvs : self . data = homography_matrix return update_prvs , HomographyTransformation ( homography_matrix )","title":"See Also"},{"location":"reference/camera_motion/#norfair.camera_motion.MotionEstimator","text":"Estimator of the motion of the camera. Uses optical flow to estimate the motion of the camera from frame to frame. The optical flow is calculated on a sample of strong points (corners). Parameters: Name Type Description Default max_points int , optional Maximum amount of points sampled. More points make the estimation process slower but more precise 200 min_distance int , optional Minimum distance between the sample points. 15 block_size int , optional Size of an average block when finding the corners. More info in links below. 3 transformations_getter TransformationGetter , optional An instance of TransformationGetter. By default HomographyTransformationGetter None draw_flow bool , optional Draws the optical flow on the frame for debugging. False flow_color Optional [ Tuple [ int , int , int ]], optional Color of the drawing, by default blue. None quality_level float , optional Parameter characterizing the minimal accepted quality of image corners. 0.01 Examples: >>> from norfair import Tracker , Video >>> from norfair.camera_motion MotionEstimator >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> motion_estimator = MotionEstimator () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> coord_transformation = motion_estimator . update ( frame ) >>> tracked_objects = tracker . update ( detections , coord_transformations = coord_transformation )","title":"MotionEstimator"},{"location":"reference/camera_motion/#norfair.camera_motion.MotionEstimator--see-also","text":"For more infor on how the points are sampled: OpenCV.goodFeaturesToTrack Source code in norfair/camera_motion.py 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 class MotionEstimator : \"\"\" Estimator of the motion of the camera. Uses optical flow to estimate the motion of the camera from frame to frame. The optical flow is calculated on a sample of strong points (corners). Parameters ---------- max_points : int, optional Maximum amount of points sampled. More points make the estimation process slower but more precise min_distance : int, optional Minimum distance between the sample points. block_size : int, optional Size of an average block when finding the corners. More info in links below. transformations_getter : TransformationGetter, optional An instance of TransformationGetter. By default [`HomographyTransformationGetter`][norfair.camera_motion.HomographyTransformationGetter] draw_flow : bool, optional Draws the optical flow on the frame for debugging. flow_color : Optional[Tuple[int, int, int]], optional Color of the drawing, by default blue. quality_level : float, optional Parameter characterizing the minimal accepted quality of image corners. Examples -------- >>> from norfair import Tracker, Video >>> from norfair.camera_motion MotionEstimator >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> motion_estimator = MotionEstimator() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> coord_transformation = motion_estimator.update(frame) >>> tracked_objects = tracker.update(detections, coord_transformations=coord_transformation) See Also -------- For more infor on how the points are sampled: [OpenCV.goodFeaturesToTrack](https://docs.opencv.org/3.4/dd/d1a/group__imgproc__feature.html#ga1d6bb77486c8f92d79c8793ad995d541) \"\"\" def __init__ ( self , max_points : int = 200 , min_distance : int = 15 , block_size : int = 3 , transformations_getter : TransformationGetter = None , draw_flow : bool = False , flow_color : Optional [ Tuple [ int , int , int ]] = None , quality_level : float = 0.01 , ): self . max_points = max_points self . min_distance = min_distance self . block_size = block_size self . draw_flow = draw_flow if self . draw_flow and flow_color is None : flow_color = [ 0 , 0 , 100 ] self . flow_color = flow_color self . gray_prvs = None self . prev_pts = None if transformations_getter is None : transformations_getter = HomographyTransformationGetter () self . transformations_getter = transformations_getter self . prev_mask = None self . gray_next = None self . quality_level = quality_level def update ( self , frame : np . ndarray , mask : np . ndarray = None ) -> CoordinatesTransformation : \"\"\" Estimate camera motion for each frame Parameters ---------- frame : np.ndarray The frame. mask : np.ndarray, optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape `(frame.shape[0], frame.shape[1])`, dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. Returns ------- CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. \"\"\" self . gray_next = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2GRAY ) if self . gray_prvs is None : self . gray_prvs = self . gray_next self . prev_mask = mask curr_pts , self . prev_pts = _get_sparse_flow ( self . gray_next , self . gray_prvs , self . prev_pts , self . max_points , self . min_distance , self . block_size , self . prev_mask , quality_level = self . quality_level , ) if self . draw_flow : for ( curr , prev ) in zip ( curr_pts , self . prev_pts ): c = tuple ( curr . astype ( int ) . ravel ()) p = tuple ( prev . astype ( int ) . ravel ()) cv2 . line ( frame , c , p , self . flow_color , 2 ) cv2 . circle ( frame , c , 3 , self . flow_color , - 1 ) update_prvs , coord_transformations = self . transformations_getter ( curr_pts , self . prev_pts , ) if update_prvs : self . gray_prvs = self . gray_next self . prev_pts = None self . prev_mask = mask return coord_transformations","title":"See Also"},{"location":"reference/camera_motion/#norfair.camera_motion.MotionEstimator.update","text":"Estimate camera motion for each frame Parameters: Name Type Description Default frame np . ndarray The frame. required mask np . ndarray , optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape (frame.shape[0], frame.shape[1]) , dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. None Returns: Type Description CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. Source code in norfair/camera_motion.py 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 def update ( self , frame : np . ndarray , mask : np . ndarray = None ) -> CoordinatesTransformation : \"\"\" Estimate camera motion for each frame Parameters ---------- frame : np.ndarray The frame. mask : np.ndarray, optional An optional mask to avoid areas of the frame when sampling the corner. Must be an array of shape `(frame.shape[0], frame.shape[1])`, dtype same as frame, and values in {0, 1}. In general, the estimation will work best when it samples many points from the background; with that intention, this parameters is usefull for masking out the detections/tracked objects, forcing the MotionEstimator ignore the moving objects. Can be used to mask static areas of the image, such as score overlays in sport transmisions or timestamps in security cameras. Returns ------- CoordinatesTransformation The CoordinatesTransformation that can transform coordinates on this frame to absolute coordinates or vice versa. \"\"\" self . gray_next = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2GRAY ) if self . gray_prvs is None : self . gray_prvs = self . gray_next self . prev_mask = mask curr_pts , self . prev_pts = _get_sparse_flow ( self . gray_next , self . gray_prvs , self . prev_pts , self . max_points , self . min_distance , self . block_size , self . prev_mask , quality_level = self . quality_level , ) if self . draw_flow : for ( curr , prev ) in zip ( curr_pts , self . prev_pts ): c = tuple ( curr . astype ( int ) . ravel ()) p = tuple ( prev . astype ( int ) . ravel ()) cv2 . line ( frame , c , p , self . flow_color , 2 ) cv2 . circle ( frame , c , 3 , self . flow_color , - 1 ) update_prvs , coord_transformations = self . transformations_getter ( curr_pts , self . prev_pts , ) if update_prvs : self . gray_prvs = self . gray_next self . prev_pts = None self . prev_mask = mask return coord_transformations","title":"update()"},{"location":"reference/distances/","text":"Distances # Predefined distances Distance # Bases: ABC Abstract class representing a distance. Subclasses must implement the method get_distances Source code in norfair/distances.py 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Distance ( ABC ): \"\"\" Abstract class representing a distance. Subclasses must implement the method `get_distances` \"\"\" @abstractmethod def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" get_distances ( objects , candidates ) abstractmethod # Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @abstractmethod def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" ScalarDistance # Bases: Distance ScalarDistance class represents a distance that is calculated pointwise. Parameters: Name Type Description Default 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 . It has to return a float with the distance it calculates. required Source code in norfair/distances.py 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 class ScalarDistance ( Distance ): \"\"\" ScalarDistance class represents a distance that is calculated pointwise. Parameters ---------- 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. \"\"\" def __init__ ( self , distance_function : Union [ Callable [[ \"Detection\" , \"TrackedObject\" ], float ], Callable [[ \"TrackedObject\" , \"TrackedObject\" ], float ], ], ): self . distance_function = distance_function def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix for c , candidate in enumerate ( candidates ): for o , obj in enumerate ( objects ): if candidate . label != obj . label : if ( candidate . label is None ) or ( obj . label is None ): print ( \" \\n There are detections with and without label!\" ) continue distance = self . distance_function ( candidate , obj ) distance_matrix [ c , o ] = distance return distance_matrix get_distances ( objects , candidates ) # Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix for c , candidate in enumerate ( candidates ): for o , obj in enumerate ( objects ): if candidate . label != obj . label : if ( candidate . label is None ) or ( obj . label is None ): print ( \" \\n There are detections with and without label!\" ) continue distance = self . distance_function ( candidate , obj ) distance_matrix [ c , o ] = distance return distance_matrix VectorizedDistance # Bases: Distance 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: Name Type Description Default distance_function Callable [[ np . ndarray , np . ndarray ], np . ndarray ] 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. required Source code in norfair/distances.py 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 class VectorizedDistance ( Distance ): \"\"\" 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 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. \"\"\" def __init__ ( self , distance_function : Callable [[ np . ndarray , np . ndarray ], np . ndarray ], ): self . distance_function = distance_function def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix object_labels = np . array ([ o . label for o in objects ]) . astype ( str ) candidate_labels = np . array ([ c . label for c in candidates ]) . astype ( str ) # iterate over labels that are present both in objects and detections for label in np . intersect1d ( np . unique ( object_labels ), np . unique ( candidate_labels ) ): # generate masks of the subset of object and detections for this label obj_mask = object_labels == label cand_mask = candidate_labels == label stacked_objects = [] for o in objects : if str ( o . label ) == label : stacked_objects . append ( o . estimate . ravel ()) stacked_objects = np . stack ( stacked_objects ) stacked_candidates = [] for c in candidates : if str ( c . label ) == label : if \"Detection\" in str ( type ( c )): stacked_candidates . append ( c . points . ravel ()) else : stacked_candidates . append ( c . estimate . ravel ()) stacked_candidates = np . stack ( stacked_candidates ) # calculate the pairwise distances between objects and candidates with this label # and assign the result to the correct positions inside distance_matrix distance_matrix [ np . ix_ ( cand_mask , obj_mask )] = self . _compute_distance ( stacked_candidates , stacked_objects ) return distance_matrix def _compute_distance ( self , stacked_candidates : np . ndarray , stacked_objects : np . ndarray ) -> 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 np.ndarray containing a stack of candidates to be compared with the stacked_objects. stacked_objects : np.ndarray np.ndarray containing a stack of objects to be compared with the stacked_objects. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" return self . distance_function ( stacked_candidates , stacked_objects ) get_distances ( objects , candidates ) # Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix object_labels = np . array ([ o . label for o in objects ]) . astype ( str ) candidate_labels = np . array ([ c . label for c in candidates ]) . astype ( str ) # iterate over labels that are present both in objects and detections for label in np . intersect1d ( np . unique ( object_labels ), np . unique ( candidate_labels ) ): # generate masks of the subset of object and detections for this label obj_mask = object_labels == label cand_mask = candidate_labels == label stacked_objects = [] for o in objects : if str ( o . label ) == label : stacked_objects . append ( o . estimate . ravel ()) stacked_objects = np . stack ( stacked_objects ) stacked_candidates = [] for c in candidates : if str ( c . label ) == label : if \"Detection\" in str ( type ( c )): stacked_candidates . append ( c . points . ravel ()) else : stacked_candidates . append ( c . estimate . ravel ()) stacked_candidates = np . stack ( stacked_candidates ) # calculate the pairwise distances between objects and candidates with this label # and assign the result to the correct positions inside distance_matrix distance_matrix [ np . ix_ ( cand_mask , obj_mask )] = self . _compute_distance ( stacked_candidates , stacked_objects ) return distance_matrix ScipyDistance # Bases: VectorizedDistance 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 . Parameters: Name Type Description Default metric str , optional Defines the specific Scipy metric to use to calculate the pairwise distances between new candidates and objects. 'euclidean' Other kwargs are passed through to cdist See Also # scipy.spatial.distance.cdist Source code in norfair/distances.py 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 class ScipyDistance ( VectorizedDistance ): \"\"\" 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`. Parameters ---------- metric : str, optional Defines the specific Scipy metric to use to calculate the pairwise distances between new candidates and objects. Other kwargs are passed through to cdist See Also -------- [`scipy.spatial.distance.cdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html) \"\"\" def __init__ ( self , metric : str = \"euclidean\" , ** kwargs ): self . metric = metric super () . __init__ ( distance_function = partial ( cdist , metric = self . metric , ** kwargs )) frobenius ( detection , tracked_object ) # Frobernius norm on the difference of the points in detection and the estimates in tracked_object. The Frobenius distance and norm are given by: \\[ d_f(a, b) = ||a - b||_F \\] \\[ ||A||_F = [\\sum_{i,j} abs(a_{i,j})^2]^{1/2} \\] Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject A tracked object. required Returns: Type Description float The distance. See Also # np.linalg.norm Source code in norfair/distances.py 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 def frobenius ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Frobernius norm on the difference of the points in detection and the estimates in tracked_object. The Frobenius distance and norm are given by: $$ d_f(a, b) = ||a - b||_F $$ $$ ||A||_F = [\\\\sum_{i,j} abs(a_{i,j})^2]^{1/2} $$ Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject A tracked object. Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate ) mean_euclidean ( detection , tracked_object ) # Average euclidean distance between the points in detection and estimates in tracked_object. \\[ d(a, b) = \\frac{\\sum_{i=0}^N ||a_i - b_i||_2}{N} \\] Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject A tracked object required Returns: Type Description float The distance. See Also # np.linalg.norm Source code in norfair/distances.py 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 def mean_euclidean ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Average euclidean distance between the points in detection and estimates in tracked_object. $$ d(a, b) = \\\\frac{\\\\sum_{i=0}^N ||a_i - b_i||_2}{N} $$ Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject A tracked object Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate , axis = 1 ) . mean () mean_manhattan ( detection , tracked_object ) # Average manhattan distance between the points in detection and the estimates in tracked_object Given by: \\[ d(a, b) = \\frac{\\sum_{i=0}^N ||a_i - b_i||_1}{N} \\] Where \\(||a||_1\\) is the manhattan norm. Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject a tracked object. required Returns: Type Description float The distance. See Also # np.linalg.norm Source code in norfair/distances.py 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 def mean_manhattan ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Average manhattan distance between the points in detection and the estimates in tracked_object Given by: $$ d(a, b) = \\\\frac{\\\\sum_{i=0}^N ||a_i - b_i||_1}{N} $$ Where $||a||_1$ is the manhattan norm. Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject a tracked object. Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate , ord = 1 , axis = 1 ) . mean () iou ( candidates , objects ) # Calculate IoU between two sets of bounding boxes. Both sets of boxes are expected to be in [x_min, y_min, x_max, y_max] format. Normal IoU is 1 when the boxes are the same and 0 when they don't overlap, to transform that into a distance that makes sense we return 1 - iou . Parameters: Name Type Description Default candidates numpy . ndarray (N, 4) numpy.ndarray containing candidates bounding boxes. required objects numpy . ndarray (K, 4) numpy.ndarray containing objects bounding boxes. required Returns: Type Description numpy . ndarray (N, K) numpy.ndarray of 1 - iou between candidates and objects. Source code in norfair/distances.py 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 def iou ( candidates : np . ndarray , objects : np . ndarray ) -> np . ndarray : \"\"\" Calculate IoU between two sets of bounding boxes. Both sets of boxes are expected to be in `[x_min, y_min, x_max, y_max]` format. Normal IoU is 1 when the boxes are the same and 0 when they don't overlap, to transform that into a distance that makes sense we return `1 - iou`. Parameters ---------- candidates : numpy.ndarray (N, 4) numpy.ndarray containing candidates bounding boxes. objects : numpy.ndarray (K, 4) numpy.ndarray containing objects bounding boxes. Returns ------- numpy.ndarray (N, K) numpy.ndarray of `1 - iou` between candidates and objects. \"\"\" _validate_bboxes ( candidates ) area_candidates = _boxes_area ( candidates . T ) area_objects = _boxes_area ( objects . T ) top_left = np . maximum ( candidates [:, None , : 2 ], objects [:, : 2 ]) bottom_right = np . minimum ( candidates [:, None , 2 :], objects [:, 2 :]) area_intersection = np . prod ( np . clip ( bottom_right - top_left , a_min = 0 , a_max = None ), 2 ) return 1 - area_intersection / ( area_candidates [:, None ] + area_objects - area_intersection ) get_distance_by_name ( name ) # Select a distance by name. Parameters: Name Type Description Default name str A string defining the metric to get. required Returns: Type Description Distance The distance object. Source code in norfair/distances.py 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 def get_distance_by_name ( name : str ) -> Distance : \"\"\" Select a distance by name. 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\" f \" such as { AVAILABLE_VECTORIZED_DISTANCES } .\" ) distance = _SCALAR_DISTANCE_FUNCTIONS [ name ] distance_function = ScalarDistance ( distance ) elif name in _SCIPY_DISTANCE_FUNCTIONS : distance_function = ScipyDistance ( name ) elif name in _VECTORIZED_DISTANCE_FUNCTIONS : if name == \"iou_opt\" : warning ( \"iou_opt is deprecated, use iou instead\" ) distance = _VECTORIZED_DISTANCE_FUNCTIONS [ name ] distance_function = VectorizedDistance ( distance ) else : raise ValueError ( f \"Invalid distance ' { name } ', expecting one of\" f \" { list ( _SCALAR_DISTANCE_FUNCTIONS . keys ()) + AVAILABLE_VECTORIZED_DISTANCES } \" ) return distance_function create_keypoints_voting_distance ( keypoint_distance_threshold , detection_threshold ) # Construct a keypoint voting distance function configured with the thresholds. Count how many points in a detection match the with a tracked_object. A match is considered when distance between the points is < keypoint_distance_threshold and the score of the last_detection of the tracked_object is > detection_threshold . Notice the if multiple points are tracked, the ith point in detection can only match the ith point in the tracked object. Distance is 1 if no point matches and approximates 0 as more points are matched. Parameters: Name Type Description Default keypoint_distance_threshold float Points closer than this threshold are considered a match. required detection_threshold float Detections and objects with score lower than this threshold are ignored. required Returns: Type Description Callable The distance funtion that must be passed to the Tracker. Source code in norfair/distances.py 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 def create_keypoints_voting_distance ( keypoint_distance_threshold : float , detection_threshold : float ) -> Callable [[ \"Detection\" , \"TrackedObject\" ], float ]: \"\"\" Construct a keypoint voting distance function configured with the thresholds. Count how many points in a detection match the with a tracked_object. A match is considered when distance between the points is < `keypoint_distance_threshold` and the score of the last_detection of the tracked_object is > `detection_threshold`. Notice the if multiple points are tracked, the ith point in detection can only match the ith point in the tracked object. Distance is 1 if no point matches and approximates 0 as more points are matched. Parameters ---------- keypoint_distance_threshold: float Points closer than this threshold are considered a match. detection_threshold: float Detections and objects with score lower than this threshold are ignored. Returns ------- Callable The distance funtion that must be passed to the Tracker. \"\"\" def keypoints_voting_distance ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : distances = np . linalg . norm ( detection . points - tracked_object . estimate , axis = 1 ) match_num = np . count_nonzero ( ( distances < keypoint_distance_threshold ) * ( detection . scores > detection_threshold ) * ( tracked_object . last_detection . scores > detection_threshold ) ) return 1 / ( 1 + match_num ) return keypoints_voting_distance create_normalized_mean_euclidean_distance ( height , width ) # Construct a normalized mean euclidean distance function configured with the max height and width. The result distance is bound to [0, 1] where 1 indicates oposite corners of the image. Parameters: Name Type Description Default height int Height of the image. required width int Width of the image. required Returns: Type Description Callable The distance funtion that must be passed to the Tracker. Source code in norfair/distances.py 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 def create_normalized_mean_euclidean_distance ( height : int , width : int ) -> Callable [[ \"Detection\" , \"TrackedObject\" ], float ]: \"\"\" Construct a normalized mean euclidean distance function configured with the max height and width. The result distance is bound to [0, 1] where 1 indicates oposite corners of the image. Parameters ---------- height: int Height of the image. width: int Width of the image. Returns ------- Callable The distance funtion that must be passed to the Tracker. \"\"\" def normalized__mean_euclidean_distance ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\"Normalized mean euclidean distance\"\"\" # calculate distances and normalized it by width and height difference = ( detection . points - tracked_object . estimate ) . astype ( float ) difference [:, 0 ] /= width difference [:, 1 ] /= height # calculate eucledean distance and average return np . linalg . norm ( difference , axis = 1 ) . mean () return normalized__mean_euclidean_distance","title":"Distances"},{"location":"reference/distances/#distances","text":"Predefined distances","title":"Distances"},{"location":"reference/distances/#norfair.distances.Distance","text":"Bases: ABC Abstract class representing a distance. Subclasses must implement the method get_distances Source code in norfair/distances.py 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Distance ( ABC ): \"\"\" Abstract class representing a distance. Subclasses must implement the method `get_distances` \"\"\" @abstractmethod def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\"","title":"Distance"},{"location":"reference/distances/#norfair.distances.Distance.get_distances","text":"Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @abstractmethod def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\"","title":"get_distances()"},{"location":"reference/distances/#norfair.distances.ScalarDistance","text":"Bases: Distance ScalarDistance class represents a distance that is calculated pointwise. Parameters: Name Type Description Default 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 . It has to return a float with the distance it calculates. required Source code in norfair/distances.py 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 class ScalarDistance ( Distance ): \"\"\" ScalarDistance class represents a distance that is calculated pointwise. Parameters ---------- 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. \"\"\" def __init__ ( self , distance_function : Union [ Callable [[ \"Detection\" , \"TrackedObject\" ], float ], Callable [[ \"TrackedObject\" , \"TrackedObject\" ], float ], ], ): self . distance_function = distance_function def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix for c , candidate in enumerate ( candidates ): for o , obj in enumerate ( objects ): if candidate . label != obj . label : if ( candidate . label is None ) or ( obj . label is None ): print ( \" \\n There are detections with and without label!\" ) continue distance = self . distance_function ( candidate , obj ) distance_matrix [ c , o ] = distance return distance_matrix","title":"ScalarDistance"},{"location":"reference/distances/#norfair.distances.ScalarDistance.get_distances","text":"Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix for c , candidate in enumerate ( candidates ): for o , obj in enumerate ( objects ): if candidate . label != obj . label : if ( candidate . label is None ) or ( obj . label is None ): print ( \" \\n There are detections with and without label!\" ) continue distance = self . distance_function ( candidate , obj ) distance_matrix [ c , o ] = distance return distance_matrix","title":"get_distances()"},{"location":"reference/distances/#norfair.distances.VectorizedDistance","text":"Bases: Distance 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: Name Type Description Default distance_function Callable [[ np . ndarray , np . ndarray ], np . ndarray ] 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. required Source code in norfair/distances.py 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 class VectorizedDistance ( Distance ): \"\"\" 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 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. \"\"\" def __init__ ( self , distance_function : Callable [[ np . ndarray , np . ndarray ], np . ndarray ], ): self . distance_function = distance_function def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix object_labels = np . array ([ o . label for o in objects ]) . astype ( str ) candidate_labels = np . array ([ c . label for c in candidates ]) . astype ( str ) # iterate over labels that are present both in objects and detections for label in np . intersect1d ( np . unique ( object_labels ), np . unique ( candidate_labels ) ): # generate masks of the subset of object and detections for this label obj_mask = object_labels == label cand_mask = candidate_labels == label stacked_objects = [] for o in objects : if str ( o . label ) == label : stacked_objects . append ( o . estimate . ravel ()) stacked_objects = np . stack ( stacked_objects ) stacked_candidates = [] for c in candidates : if str ( c . label ) == label : if \"Detection\" in str ( type ( c )): stacked_candidates . append ( c . points . ravel ()) else : stacked_candidates . append ( c . estimate . ravel ()) stacked_candidates = np . stack ( stacked_candidates ) # calculate the pairwise distances between objects and candidates with this label # and assign the result to the correct positions inside distance_matrix distance_matrix [ np . ix_ ( cand_mask , obj_mask )] = self . _compute_distance ( stacked_candidates , stacked_objects ) return distance_matrix def _compute_distance ( self , stacked_candidates : np . ndarray , stacked_objects : np . ndarray ) -> 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 np.ndarray containing a stack of candidates to be compared with the stacked_objects. stacked_objects : np.ndarray np.ndarray containing a stack of objects to be compared with the stacked_objects. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" return self . distance_function ( stacked_candidates , stacked_objects )","title":"VectorizedDistance"},{"location":"reference/distances/#norfair.distances.VectorizedDistance.get_distances","text":"Method that calculates the distances between new candidates and objects. Parameters: Name Type Description Default objects Sequence [ TrackedObject ] Sequence of TrackedObject to be compared with potential Detection or TrackedObject candidates. required candidates Union [ List [ Detection ], List [ TrackedObject ]], optional List of candidates ( Detection or TrackedObject ) to be compared to TrackedObject . required Returns: Type Description np . ndarray A matrix containing the distances between objects and candidates. Source code in norfair/distances.py 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 def get_distances ( self , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], ) -> np . ndarray : \"\"\" Method that calculates the distances between new candidates and objects. Parameters ---------- 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 List of candidates ([Detection][norfair.tracker.Detection] or [TrackedObject][norfair.tracker.TrackedObject]) to be compared to [TrackedObject][norfair.tracker.TrackedObject]. Returns ------- np.ndarray A matrix containing the distances between objects and candidates. \"\"\" distance_matrix = np . full ( ( len ( candidates ), len ( objects )), fill_value = np . inf , dtype = np . float32 , ) if not objects or not candidates : return distance_matrix object_labels = np . array ([ o . label for o in objects ]) . astype ( str ) candidate_labels = np . array ([ c . label for c in candidates ]) . astype ( str ) # iterate over labels that are present both in objects and detections for label in np . intersect1d ( np . unique ( object_labels ), np . unique ( candidate_labels ) ): # generate masks of the subset of object and detections for this label obj_mask = object_labels == label cand_mask = candidate_labels == label stacked_objects = [] for o in objects : if str ( o . label ) == label : stacked_objects . append ( o . estimate . ravel ()) stacked_objects = np . stack ( stacked_objects ) stacked_candidates = [] for c in candidates : if str ( c . label ) == label : if \"Detection\" in str ( type ( c )): stacked_candidates . append ( c . points . ravel ()) else : stacked_candidates . append ( c . estimate . ravel ()) stacked_candidates = np . stack ( stacked_candidates ) # calculate the pairwise distances between objects and candidates with this label # and assign the result to the correct positions inside distance_matrix distance_matrix [ np . ix_ ( cand_mask , obj_mask )] = self . _compute_distance ( stacked_candidates , stacked_objects ) return distance_matrix","title":"get_distances()"},{"location":"reference/distances/#norfair.distances.ScipyDistance","text":"Bases: VectorizedDistance 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 . Parameters: Name Type Description Default metric str , optional Defines the specific Scipy metric to use to calculate the pairwise distances between new candidates and objects. 'euclidean' Other kwargs are passed through to cdist","title":"ScipyDistance"},{"location":"reference/distances/#norfair.distances.ScipyDistance--see-also","text":"scipy.spatial.distance.cdist Source code in norfair/distances.py 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 class ScipyDistance ( VectorizedDistance ): \"\"\" 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`. Parameters ---------- metric : str, optional Defines the specific Scipy metric to use to calculate the pairwise distances between new candidates and objects. Other kwargs are passed through to cdist See Also -------- [`scipy.spatial.distance.cdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html) \"\"\" def __init__ ( self , metric : str = \"euclidean\" , ** kwargs ): self . metric = metric super () . __init__ ( distance_function = partial ( cdist , metric = self . metric , ** kwargs ))","title":"See Also"},{"location":"reference/distances/#norfair.distances.frobenius","text":"Frobernius norm on the difference of the points in detection and the estimates in tracked_object. The Frobenius distance and norm are given by: \\[ d_f(a, b) = ||a - b||_F \\] \\[ ||A||_F = [\\sum_{i,j} abs(a_{i,j})^2]^{1/2} \\] Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject A tracked object. required Returns: Type Description float The distance.","title":"frobenius()"},{"location":"reference/distances/#norfair.distances.frobenius--see-also","text":"np.linalg.norm Source code in norfair/distances.py 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 def frobenius ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Frobernius norm on the difference of the points in detection and the estimates in tracked_object. The Frobenius distance and norm are given by: $$ d_f(a, b) = ||a - b||_F $$ $$ ||A||_F = [\\\\sum_{i,j} abs(a_{i,j})^2]^{1/2} $$ Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject A tracked object. Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate )","title":"See Also"},{"location":"reference/distances/#norfair.distances.mean_euclidean","text":"Average euclidean distance between the points in detection and estimates in tracked_object. \\[ d(a, b) = \\frac{\\sum_{i=0}^N ||a_i - b_i||_2}{N} \\] Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject A tracked object required Returns: Type Description float The distance.","title":"mean_euclidean()"},{"location":"reference/distances/#norfair.distances.mean_euclidean--see-also","text":"np.linalg.norm Source code in norfair/distances.py 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 def mean_euclidean ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Average euclidean distance between the points in detection and estimates in tracked_object. $$ d(a, b) = \\\\frac{\\\\sum_{i=0}^N ||a_i - b_i||_2}{N} $$ Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject A tracked object Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate , axis = 1 ) . mean ()","title":"See Also"},{"location":"reference/distances/#norfair.distances.mean_manhattan","text":"Average manhattan distance between the points in detection and the estimates in tracked_object Given by: \\[ d(a, b) = \\frac{\\sum_{i=0}^N ||a_i - b_i||_1}{N} \\] Where \\(||a||_1\\) is the manhattan norm. Parameters: Name Type Description Default detection Detection A detection. required tracked_object TrackedObject a tracked object. required Returns: Type Description float The distance.","title":"mean_manhattan()"},{"location":"reference/distances/#norfair.distances.mean_manhattan--see-also","text":"np.linalg.norm Source code in norfair/distances.py 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 def mean_manhattan ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\" Average manhattan distance between the points in detection and the estimates in tracked_object Given by: $$ d(a, b) = \\\\frac{\\\\sum_{i=0}^N ||a_i - b_i||_1}{N} $$ Where $||a||_1$ is the manhattan norm. Parameters ---------- detection : Detection A detection. tracked_object : TrackedObject a tracked object. Returns ------- float The distance. See Also -------- [`np.linalg.norm`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html) \"\"\" return np . linalg . norm ( detection . points - tracked_object . estimate , ord = 1 , axis = 1 ) . mean ()","title":"See Also"},{"location":"reference/distances/#norfair.distances.iou","text":"Calculate IoU between two sets of bounding boxes. Both sets of boxes are expected to be in [x_min, y_min, x_max, y_max] format. Normal IoU is 1 when the boxes are the same and 0 when they don't overlap, to transform that into a distance that makes sense we return 1 - iou . Parameters: Name Type Description Default candidates numpy . ndarray (N, 4) numpy.ndarray containing candidates bounding boxes. required objects numpy . ndarray (K, 4) numpy.ndarray containing objects bounding boxes. required Returns: Type Description numpy . ndarray (N, K) numpy.ndarray of 1 - iou between candidates and objects. Source code in norfair/distances.py 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 def iou ( candidates : np . ndarray , objects : np . ndarray ) -> np . ndarray : \"\"\" Calculate IoU between two sets of bounding boxes. Both sets of boxes are expected to be in `[x_min, y_min, x_max, y_max]` format. Normal IoU is 1 when the boxes are the same and 0 when they don't overlap, to transform that into a distance that makes sense we return `1 - iou`. Parameters ---------- candidates : numpy.ndarray (N, 4) numpy.ndarray containing candidates bounding boxes. objects : numpy.ndarray (K, 4) numpy.ndarray containing objects bounding boxes. Returns ------- numpy.ndarray (N, K) numpy.ndarray of `1 - iou` between candidates and objects. \"\"\" _validate_bboxes ( candidates ) area_candidates = _boxes_area ( candidates . T ) area_objects = _boxes_area ( objects . T ) top_left = np . maximum ( candidates [:, None , : 2 ], objects [:, : 2 ]) bottom_right = np . minimum ( candidates [:, None , 2 :], objects [:, 2 :]) area_intersection = np . prod ( np . clip ( bottom_right - top_left , a_min = 0 , a_max = None ), 2 ) return 1 - area_intersection / ( area_candidates [:, None ] + area_objects - area_intersection )","title":"iou()"},{"location":"reference/distances/#norfair.distances.get_distance_by_name","text":"Select a distance by name. Parameters: Name Type Description Default name str A string defining the metric to get. required Returns: Type Description Distance The distance object. Source code in norfair/distances.py 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 def get_distance_by_name ( name : str ) -> Distance : \"\"\" Select a distance by name. 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\" f \" such as { AVAILABLE_VECTORIZED_DISTANCES } .\" ) distance = _SCALAR_DISTANCE_FUNCTIONS [ name ] distance_function = ScalarDistance ( distance ) elif name in _SCIPY_DISTANCE_FUNCTIONS : distance_function = ScipyDistance ( name ) elif name in _VECTORIZED_DISTANCE_FUNCTIONS : if name == \"iou_opt\" : warning ( \"iou_opt is deprecated, use iou instead\" ) distance = _VECTORIZED_DISTANCE_FUNCTIONS [ name ] distance_function = VectorizedDistance ( distance ) else : raise ValueError ( f \"Invalid distance ' { name } ', expecting one of\" f \" { list ( _SCALAR_DISTANCE_FUNCTIONS . keys ()) + AVAILABLE_VECTORIZED_DISTANCES } \" ) return distance_function","title":"get_distance_by_name()"},{"location":"reference/distances/#norfair.distances.create_keypoints_voting_distance","text":"Construct a keypoint voting distance function configured with the thresholds. Count how many points in a detection match the with a tracked_object. A match is considered when distance between the points is < keypoint_distance_threshold and the score of the last_detection of the tracked_object is > detection_threshold . Notice the if multiple points are tracked, the ith point in detection can only match the ith point in the tracked object. Distance is 1 if no point matches and approximates 0 as more points are matched. Parameters: Name Type Description Default keypoint_distance_threshold float Points closer than this threshold are considered a match. required detection_threshold float Detections and objects with score lower than this threshold are ignored. required Returns: Type Description Callable The distance funtion that must be passed to the Tracker. Source code in norfair/distances.py 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 def create_keypoints_voting_distance ( keypoint_distance_threshold : float , detection_threshold : float ) -> Callable [[ \"Detection\" , \"TrackedObject\" ], float ]: \"\"\" Construct a keypoint voting distance function configured with the thresholds. Count how many points in a detection match the with a tracked_object. A match is considered when distance between the points is < `keypoint_distance_threshold` and the score of the last_detection of the tracked_object is > `detection_threshold`. Notice the if multiple points are tracked, the ith point in detection can only match the ith point in the tracked object. Distance is 1 if no point matches and approximates 0 as more points are matched. Parameters ---------- keypoint_distance_threshold: float Points closer than this threshold are considered a match. detection_threshold: float Detections and objects with score lower than this threshold are ignored. Returns ------- Callable The distance funtion that must be passed to the Tracker. \"\"\" def keypoints_voting_distance ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : distances = np . linalg . norm ( detection . points - tracked_object . estimate , axis = 1 ) match_num = np . count_nonzero ( ( distances < keypoint_distance_threshold ) * ( detection . scores > detection_threshold ) * ( tracked_object . last_detection . scores > detection_threshold ) ) return 1 / ( 1 + match_num ) return keypoints_voting_distance","title":"create_keypoints_voting_distance()"},{"location":"reference/distances/#norfair.distances.create_normalized_mean_euclidean_distance","text":"Construct a normalized mean euclidean distance function configured with the max height and width. The result distance is bound to [0, 1] where 1 indicates oposite corners of the image. Parameters: Name Type Description Default height int Height of the image. required width int Width of the image. required Returns: Type Description Callable The distance funtion that must be passed to the Tracker. Source code in norfair/distances.py 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 def create_normalized_mean_euclidean_distance ( height : int , width : int ) -> Callable [[ \"Detection\" , \"TrackedObject\" ], float ]: \"\"\" Construct a normalized mean euclidean distance function configured with the max height and width. The result distance is bound to [0, 1] where 1 indicates oposite corners of the image. Parameters ---------- height: int Height of the image. width: int Width of the image. Returns ------- Callable The distance funtion that must be passed to the Tracker. \"\"\" def normalized__mean_euclidean_distance ( detection : \"Detection\" , tracked_object : \"TrackedObject\" ) -> float : \"\"\"Normalized mean euclidean distance\"\"\" # calculate distances and normalized it by width and height difference = ( detection . points - tracked_object . estimate ) . astype ( float ) difference [:, 0 ] /= width difference [:, 1 ] /= height # calculate eucledean distance and average return np . linalg . norm ( difference , axis = 1 ) . mean () return normalized__mean_euclidean_distance","title":"create_normalized_mean_euclidean_distance()"},{"location":"reference/drawing/","text":"Drawing # Collection of drawing functions draw_points # draw_points ( frame , drawables = None , radius = None , thickness = None , color = 'by_id' , color_by_label = None , draw_labels = True , text_size = None , draw_ids = True , draw_points = True , text_thickness = None , text_color = None , hide_dead_points = True , detections = None , label_size = None , draw_scores = False ) # Draw the points included in a list of Detections or TrackedObjects. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. Modified in place. required drawables Union [ Sequence [ Detection ], Sequence [ TrackedObject ]], optional List of objects to draw, Detections and TrackedObjects are accepted. None radius Optional [ int ], optional Radius of the circles representing each point. By default a sensible value is picked considering the frame size. None thickness Optional [ int ], optional Thickness or width of the line. None color ColorLike , optional This parameter can take: A color as a tuple of ints describing the BGR (0, 0, 255) A 6-digit hex string \"#FF0000\" One of the defined color names \"red\" A string defining the strategy to choose colors from the Palette: based on the id of the objects \"by_id\" based on the label of the objects \"by_label\" random choice \"random\" If using by_id or by_label strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). 'by_id' color_by_label bool , optional Deprecated . set color=\"by_label\" . None draw_labels bool , optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. True draw_scores bool , optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False text_size Optional [ int ], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. None draw_ids bool , optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. True draw_points bool , optional Set to False to hide the points and just draw the text. True text_thickness Optional [ int ], optional Thickness of the font. By default it's scaled with the text_size . None text_color Optional [ ColorLike ], optional Color of the text. By default the same color as the box is used. None hide_dead_points bool , optional Set this param to False to always draw all points, even the ones considered \"dead\". A point is \"dead\" when the corresponding value of TrackedObject.live_points is set to False. If all objects are dead the object is not drawn. All points of a detection are considered to be alive. True detections Sequence [ Detection ], optional Deprecated . use drawables. None label_size Optional [ int ], optional Deprecated . text_size. None Returns: Type Description np . ndarray The resulting frame. Source code in norfair/drawing/draw_points.py 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 def draw_points ( frame : np . ndarray , drawables : Union [ Sequence [ Detection ], Sequence [ TrackedObject ]] = None , radius : Optional [ int ] = None , thickness : Optional [ int ] = None , color : ColorLike = \"by_id\" , color_by_label : bool = None , # deprecated draw_labels : bool = True , text_size : Optional [ int ] = None , draw_ids : bool = True , draw_points : bool = True , # pylint: disable=redefined-outer-name text_thickness : Optional [ int ] = None , text_color : Optional [ ColorLike ] = None , hide_dead_points : bool = True , detections : Sequence [ \"Detection\" ] = None , # deprecated label_size : Optional [ int ] = None , # deprecated draw_scores : bool = False , ) -> np . ndarray : \"\"\" Draw the points included in a list of Detections or TrackedObjects. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. Modified in place. drawables : Union[Sequence[Detection], Sequence[TrackedObject]], optional List of objects to draw, Detections and TrackedObjects are accepted. radius : Optional[int], optional Radius of the circles representing each point. By default a sensible value is picked considering the frame size. thickness : Optional[int], optional Thickness or width of the line. color : ColorLike, optional This parameter can take: 1. A color as a tuple of ints describing the BGR `(0, 0, 255)` 2. A 6-digit hex string `\"#FF0000\"` 3. One of the defined color names `\"red\"` 4. A string defining the strategy to choose colors from the Palette: 1. based on the id of the objects `\"by_id\"` 2. based on the label of the objects `\"by_label\"` 3. random choice `\"random\"` If using `by_id` or `by_label` strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). color_by_label : bool, optional **Deprecated**. set `color=\"by_label\"`. draw_labels : bool, optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. draw_scores : bool, optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. text_size : Optional[int], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. draw_ids : bool, optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. draw_points : bool, optional Set to False to hide the points and just draw the text. text_thickness : Optional[int], optional Thickness of the font. By default it's scaled with the `text_size`. text_color : Optional[ColorLike], optional Color of the text. By default the same color as the box is used. hide_dead_points : bool, optional Set this param to False to always draw all points, even the ones considered \"dead\". A point is \"dead\" when the corresponding value of `TrackedObject.live_points` is set to False. If all objects are dead the object is not drawn. All points of a detection are considered to be alive. detections : Sequence[Detection], optional **Deprecated**. use drawables. label_size : Optional[int], optional **Deprecated**. text_size. Returns ------- np.ndarray The resulting frame. \"\"\" # # handle deprecated parameters # if color_by_label is not None : warn_once ( 'Parameter \"color_by_label\" on function draw_points is deprecated, set `color=\"by_label\"` instead' ) color = \"by_label\" if detections is not None : warn_once ( \"Parameter 'detections' on function draw_points is deprecated, use 'drawables' instead\" ) drawables = detections if label_size is not None : warn_once ( \"Parameter 'label_size' on function draw_points is deprecated, use 'text_size' instead\" ) text_size = label_size # end if drawables is None : return if text_color is not None : text_color = parse_color ( text_color ) if color is None : color = \"by_id\" if thickness is None : thickness = - 1 if radius is None : radius = int ( round ( max ( max ( frame . shape ) * 0.002 , 1 ))) for o in drawables : if not isinstance ( o , Drawable ): d = Drawable ( o ) else : d = o if hide_dead_points and not d . live_points . any (): continue if color == \"by_id\" : obj_color = Palette . choose_color ( d . id ) elif color == \"by_label\" : obj_color = Palette . choose_color ( d . label ) elif color == \"random\" : obj_color = Palette . choose_color ( np . random . rand ()) else : obj_color = parse_color ( color ) if text_color is None : obj_text_color = obj_color else : obj_text_color = text_color if draw_points : for point , live in zip ( d . points , d . live_points ): if live or not hide_dead_points : Drawer . circle ( frame , tuple ( point . astype ( int )), radius = radius , color = obj_color , thickness = thickness , ) if draw_labels or draw_ids or draw_scores : position = d . points [ d . live_points ] . mean ( axis = 0 ) position -= radius text = _build_text ( d , draw_labels = draw_labels , draw_ids = draw_ids , draw_scores = draw_scores ) Drawer . text ( frame , text , tuple ( position . astype ( int )), size = text_size , color = obj_text_color , thickness = text_thickness , ) return frame draw_tracked_objects ( frame , objects , radius = None , color = None , id_size = None , id_thickness = None , draw_points = True , color_by_label = False , draw_labels = False , label_size = None ) # Deprecated use draw_points Source code in norfair/drawing/draw_points.py 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 def draw_tracked_objects ( frame : np . ndarray , objects : Sequence [ \"TrackedObject\" ], radius : Optional [ int ] = None , color : Optional [ ColorLike ] = None , id_size : Optional [ float ] = None , id_thickness : Optional [ int ] = None , draw_points : bool = True , # pylint: disable=redefined-outer-name color_by_label : bool = False , draw_labels : bool = False , label_size : Optional [ int ] = None , ): \"\"\" **Deprecated** use [`draw_points`][norfair.drawing.draw_points.draw_points] \"\"\" warn_once ( \"draw_tracked_objects is deprecated, use draw_points instead\" ) frame_scale = frame . shape [ 0 ] / 100 if radius is None : radius = int ( frame_scale * 0.5 ) if id_size is None : id_size = frame_scale / 10 if id_thickness is None : id_thickness = int ( frame_scale / 5 ) if label_size is None : label_size = int ( max ( frame_scale / 100 , 1 )) _draw_points_alias ( frame = frame , drawables = objects , color = \"by_label\" if color_by_label else color , radius = radius , thickness = None , draw_labels = draw_labels , draw_ids = id_size is not None and id_size > 0 , draw_points = draw_points , text_size = label_size or id_size , text_thickness = id_thickness , text_color = None , hide_dead_points = True , ) # TODO: We used to have this function to debug # migrate it to use Drawer and clean it up # if possible maybe merge this functionality to the function above # def draw_debug_metrics( # frame: np.ndarray, # objects: Sequence[\"TrackedObject\"], # text_size: Optional[float] = None, # text_thickness: Optional[int] = None, # color: Optional[Tuple[int, int, int]] = None, # only_ids=None, # only_initializing_ids=None, # draw_score_threshold: float = 0, # color_by_label: bool = False, # draw_labels: bool = False, # ): # \"\"\"Draw objects with their debug information # It is recommended to set the input variable `objects` to `your_tracker_object.objects` # so you can also debug objects wich haven't finished initializing, and you get a more # complete view of what your tracker is doing on each step. # \"\"\" # frame_scale = frame.shape[0] / 100 # if text_size is None: # text_size = frame_scale / 10 # if text_thickness is None: # text_thickness = int(frame_scale / 5) # radius = int(frame_scale * 0.5) # for obj in objects: # if ( # not (obj.last_detection.scores is None) # and not (obj.last_detection.scores > draw_score_threshold).any() # ): # continue # if only_ids is not None: # if obj.id not in only_ids: # continue # if only_initializing_ids is not None: # if obj.initializing_id not in only_initializing_ids: # continue # if color_by_label: # text_color = Color.random(abs(hash(obj.label))) # elif color is None: # text_color = Color.random(obj.initializing_id) # else: # text_color = color # draw_position = _centroid( # obj.estimate[obj.last_detection.scores > draw_score_threshold] # if obj.last_detection.scores is not None # else obj.estimate # ) # for point in obj.estimate: # cv2.circle( # frame, # tuple(point.astype(int)), # radius=radius, # color=text_color, # thickness=-1, # ) # # Distance to last matched detection # if obj.last_distance is None: # last_dist = \"-\" # elif obj.last_distance > 999: # last_dist = \">\" # else: # last_dist = \"{:.2f}\".format(obj.last_distance) # # Distance to currently closest detection # if obj.current_min_distance is None: # current_min_dist = \"-\" # else: # current_min_dist = \"{:.2f}\".format(obj.current_min_distance) # # No support for multiline text in opencv :facepalm: # lines_to_draw = [ # \"{}|{}\".format(obj.id, obj.initializing_id), # \"a:{}\".format(obj.age), # \"h:{}\".format(obj.hit_counter), # \"ld:{}\".format(last_dist), # \"cd:{}\".format(current_min_dist), # ] # if draw_labels: # lines_to_draw.append(\"l:{}\".format(obj.label)) # for i, line in enumerate(lines_to_draw): # draw_position = ( # int(draw_position[0]), # int(draw_position[1] + i * text_size * 7 + 15), # ) # cv2.putText( # frame, # line, # draw_position, # cv2.FONT_HERSHEY_SIMPLEX, # text_size, # text_color, # text_thickness, # cv2.LINE_AA, # ) draw_boxes # draw_boxes ( frame , drawables = None , color = 'by_id' , thickness = None , random_color = None , color_by_label = None , draw_labels = False , text_size = None , draw_ids = True , text_color = None , text_thickness = None , draw_box = True , detections = None , line_color = None , line_width = None , label_size = None , draw_scores = False ) # Draw bounding boxes corresponding to Detections or TrackedObjects. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. Modified in place. required drawables Union [ Sequence [ Detection ], Sequence [ TrackedObject ]], optional List of objects to draw, Detections and TrackedObjects are accepted. This objects are assumed to contain 2 bi-dimensional points defining the bounding box as [[x0, y0], [x1, y1]] . None color ColorLike , optional This parameter can take: A color as a tuple of ints describing the BGR (0, 0, 255) A 6-digit hex string \"#FF0000\" One of the defined color names \"red\" A string defining the strategy to choose colors from the Palette: based on the id of the objects \"by_id\" based on the label of the objects \"by_label\" random choice \"random\" If using by_id or by_label strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). 'by_id' thickness Optional [ int ], optional Thickness or width of the line. None random_color bool , optional Deprecated . Set color=\"random\". None color_by_label bool , optional Deprecated . Set color=\"by_label\". None draw_labels bool , optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False draw_scores bool , optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False text_size Optional [ float ], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. None draw_ids bool , optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. True text_color Optional [ ColorLike ], optional Color of the text. By default the same color as the box is used. None text_thickness Optional [ int ], optional Thickness of the font. By default it's scaled with the text_size . None draw_box bool , optional Set to False to hide the box and just draw the text. True detections Sequence [ Detection ], optional Deprecated . Use drawables. None line_color Optional [ ColorLike ] Deprecated . Use color. None line_width Optional [ int ] Deprecated . Use thickness. None label_size Optional [ int ] Deprecated . Use text_size. None Returns: Type Description np . ndarray The resulting frame. Source code in norfair/drawing/draw_boxes.py 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 def draw_boxes ( frame : np . ndarray , drawables : Union [ Sequence [ Detection ], Sequence [ TrackedObject ]] = None , color : ColorLike = \"by_id\" , thickness : Optional [ int ] = None , random_color : bool = None , # Deprecated color_by_label : bool = None , # Deprecated draw_labels : bool = False , text_size : Optional [ float ] = None , draw_ids : bool = True , text_color : Optional [ ColorLike ] = None , text_thickness : Optional [ int ] = None , draw_box : bool = True , detections : Sequence [ \"Detection\" ] = None , # Deprecated line_color : Optional [ ColorLike ] = None , # Deprecated line_width : Optional [ int ] = None , # Deprecated label_size : Optional [ int ] = None , # Deprecated\u00b4 draw_scores : bool = False , ) -> np . ndarray : \"\"\" Draw bounding boxes corresponding to Detections or TrackedObjects. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. Modified in place. drawables : Union[Sequence[Detection], Sequence[TrackedObject]], optional List of objects to draw, Detections and TrackedObjects are accepted. This objects are assumed to contain 2 bi-dimensional points defining the bounding box as `[[x0, y0], [x1, y1]]`. color : ColorLike, optional This parameter can take: 1. A color as a tuple of ints describing the BGR `(0, 0, 255)` 2. A 6-digit hex string `\"#FF0000\"` 3. One of the defined color names `\"red\"` 4. A string defining the strategy to choose colors from the Palette: 1. based on the id of the objects `\"by_id\"` 2. based on the label of the objects `\"by_label\"` 3. random choice `\"random\"` If using `by_id` or `by_label` strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). thickness : Optional[int], optional Thickness or width of the line. random_color : bool, optional **Deprecated**. Set color=\"random\". color_by_label : bool, optional **Deprecated**. Set color=\"by_label\". draw_labels : bool, optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. draw_scores : bool, optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. text_size : Optional[float], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. draw_ids : bool, optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. text_color : Optional[ColorLike], optional Color of the text. By default the same color as the box is used. text_thickness : Optional[int], optional Thickness of the font. By default it's scaled with the `text_size`. draw_box : bool, optional Set to False to hide the box and just draw the text. detections : Sequence[Detection], optional **Deprecated**. Use drawables. line_color: Optional[ColorLike], optional **Deprecated**. Use color. line_width: Optional[int], optional **Deprecated**. Use thickness. label_size: Optional[int], optional **Deprecated**. Use text_size. Returns ------- np.ndarray The resulting frame. \"\"\" # # handle deprecated parameters # if random_color is not None : warn_once ( 'Parameter \"random_color\" is deprecated, set `color=\"random\"` instead' ) color = \"random\" if color_by_label is not None : warn_once ( 'Parameter \"color_by_label\" is deprecated, set `color=\"by_label\"` instead' ) color = \"by_label\" if detections is not None : warn_once ( 'Parameter \"detections\" is deprecated, use \"drawables\" instead' ) drawables = detections if line_color is not None : warn_once ( 'Parameter \"line_color\" is deprecated, use \"color\" instead' ) color = line_color if line_width is not None : warn_once ( 'Parameter \"line_width\" is deprecated, use \"thickness\" instead' ) thickness = line_width if label_size is not None : warn_once ( 'Parameter \"label_size\" is deprecated, use \"text_size\" instead' ) text_size = label_size # end if color is None : color = \"by_id\" if thickness is None : thickness = int ( max ( frame . shape ) / 500 ) if drawables is None : return frame if text_color is not None : text_color = parse_color ( text_color ) for obj in drawables : if not isinstance ( obj , Drawable ): d = Drawable ( obj ) else : d = obj if color == \"by_id\" : obj_color = Palette . choose_color ( d . id ) elif color == \"by_label\" : obj_color = Palette . choose_color ( d . label ) elif color == \"random\" : obj_color = Palette . choose_color ( np . random . rand ()) else : obj_color = parse_color ( color ) points = d . points . astype ( int ) if draw_box : Drawer . rectangle ( frame , tuple ( points ), color = obj_color , thickness = thickness , ) text = _build_text ( d , draw_labels = draw_labels , draw_ids = draw_ids , draw_scores = draw_scores ) if text : if text_color is None : obj_text_color = obj_color else : obj_text_color = text_color # the anchor will become the bottom-left of the text, # we select-top left of the bbox compensating for the thickness of the box text_anchor = ( points [ 0 , 0 ] - thickness // 2 , points [ 0 , 1 ] - thickness // 2 - 1 , ) frame = Drawer . text ( frame , text , position = text_anchor , size = text_size , color = obj_text_color , thickness = text_thickness , ) return frame draw_tracked_boxes ( frame , objects , border_colors = None , border_width = None , id_size = None , id_thickness = None , draw_box = True , color_by_label = False , draw_labels = False , label_size = None , label_width = None ) # Deprecated . Use draw_box Source code in norfair/drawing/draw_boxes.py 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 def draw_tracked_boxes ( frame : np . ndarray , objects : Sequence [ \"TrackedObject\" ], border_colors : Optional [ Tuple [ int , int , int ]] = None , border_width : Optional [ int ] = None , id_size : Optional [ int ] = None , id_thickness : Optional [ int ] = None , draw_box : bool = True , color_by_label : bool = False , draw_labels : bool = False , label_size : Optional [ int ] = None , label_width : Optional [ int ] = None , ) -> np . array : \"**Deprecated**. Use [`draw_box`][norfair.drawing.draw_boxes.draw_boxes]\" warn_once ( \"draw_tracked_boxes is deprecated, use draw_box instead\" ) return draw_boxes ( frame = frame , drawables = objects , color = \"by_label\" if color_by_label else border_colors , thickness = border_width , text_size = label_size or id_size , text_thickness = id_thickness or label_width , draw_labels = draw_labels , draw_ids = id_size is not None and id_size > 0 , draw_box = draw_box , ) color # Color # Contains predefined colors. Colors are defined as a Tuple of integers between 0 and 255 expressing the values in BGR This is the format opencv uses. Source code in norfair/drawing/color.py 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 class Color : \"\"\" Contains predefined colors. Colors are defined as a Tuple of integers between 0 and 255 expressing the values in BGR This is the format opencv uses. \"\"\" # from PIL.ImageColors.colormap aliceblue = hex_to_bgr ( \"#f0f8ff\" ) antiquewhite = hex_to_bgr ( \"#faebd7\" ) aqua = hex_to_bgr ( \"#00ffff\" ) aquamarine = hex_to_bgr ( \"#7fffd4\" ) azure = hex_to_bgr ( \"#f0ffff\" ) beige = hex_to_bgr ( \"#f5f5dc\" ) bisque = hex_to_bgr ( \"#ffe4c4\" ) black = hex_to_bgr ( \"#000000\" ) blanchedalmond = hex_to_bgr ( \"#ffebcd\" ) blue = hex_to_bgr ( \"#0000ff\" ) blueviolet = hex_to_bgr ( \"#8a2be2\" ) brown = hex_to_bgr ( \"#a52a2a\" ) burlywood = hex_to_bgr ( \"#deb887\" ) cadetblue = hex_to_bgr ( \"#5f9ea0\" ) chartreuse = hex_to_bgr ( \"#7fff00\" ) chocolate = hex_to_bgr ( \"#d2691e\" ) coral = hex_to_bgr ( \"#ff7f50\" ) cornflowerblue = hex_to_bgr ( \"#6495ed\" ) cornsilk = hex_to_bgr ( \"#fff8dc\" ) crimson = hex_to_bgr ( \"#dc143c\" ) cyan = hex_to_bgr ( \"#00ffff\" ) darkblue = hex_to_bgr ( \"#00008b\" ) darkcyan = hex_to_bgr ( \"#008b8b\" ) darkgoldenrod = hex_to_bgr ( \"#b8860b\" ) darkgray = hex_to_bgr ( \"#a9a9a9\" ) darkgrey = hex_to_bgr ( \"#a9a9a9\" ) darkgreen = hex_to_bgr ( \"#006400\" ) darkkhaki = hex_to_bgr ( \"#bdb76b\" ) darkmagenta = hex_to_bgr ( \"#8b008b\" ) darkolivegreen = hex_to_bgr ( \"#556b2f\" ) darkorange = hex_to_bgr ( \"#ff8c00\" ) darkorchid = hex_to_bgr ( \"#9932cc\" ) darkred = hex_to_bgr ( \"#8b0000\" ) darksalmon = hex_to_bgr ( \"#e9967a\" ) darkseagreen = hex_to_bgr ( \"#8fbc8f\" ) darkslateblue = hex_to_bgr ( \"#483d8b\" ) darkslategray = hex_to_bgr ( \"#2f4f4f\" ) darkslategrey = hex_to_bgr ( \"#2f4f4f\" ) darkturquoise = hex_to_bgr ( \"#00ced1\" ) darkviolet = hex_to_bgr ( \"#9400d3\" ) deeppink = hex_to_bgr ( \"#ff1493\" ) deepskyblue = hex_to_bgr ( \"#00bfff\" ) dimgray = hex_to_bgr ( \"#696969\" ) dimgrey = hex_to_bgr ( \"#696969\" ) dodgerblue = hex_to_bgr ( \"#1e90ff\" ) firebrick = hex_to_bgr ( \"#b22222\" ) floralwhite = hex_to_bgr ( \"#fffaf0\" ) forestgreen = hex_to_bgr ( \"#228b22\" ) fuchsia = hex_to_bgr ( \"#ff00ff\" ) gainsboro = hex_to_bgr ( \"#dcdcdc\" ) ghostwhite = hex_to_bgr ( \"#f8f8ff\" ) gold = hex_to_bgr ( \"#ffd700\" ) goldenrod = hex_to_bgr ( \"#daa520\" ) gray = hex_to_bgr ( \"#808080\" ) grey = hex_to_bgr ( \"#808080\" ) green = ( 0 , 128 , 0 ) greenyellow = hex_to_bgr ( \"#adff2f\" ) honeydew = hex_to_bgr ( \"#f0fff0\" ) hotpink = hex_to_bgr ( \"#ff69b4\" ) indianred = hex_to_bgr ( \"#cd5c5c\" ) indigo = hex_to_bgr ( \"#4b0082\" ) ivory = hex_to_bgr ( \"#fffff0\" ) khaki = hex_to_bgr ( \"#f0e68c\" ) lavender = hex_to_bgr ( \"#e6e6fa\" ) lavenderblush = hex_to_bgr ( \"#fff0f5\" ) lawngreen = hex_to_bgr ( \"#7cfc00\" ) lemonchiffon = hex_to_bgr ( \"#fffacd\" ) lightblue = hex_to_bgr ( \"#add8e6\" ) lightcoral = hex_to_bgr ( \"#f08080\" ) lightcyan = hex_to_bgr ( \"#e0ffff\" ) lightgoldenrodyellow = hex_to_bgr ( \"#fafad2\" ) lightgreen = hex_to_bgr ( \"#90ee90\" ) lightgray = hex_to_bgr ( \"#d3d3d3\" ) lightgrey = hex_to_bgr ( \"#d3d3d3\" ) lightpink = hex_to_bgr ( \"#ffb6c1\" ) lightsalmon = hex_to_bgr ( \"#ffa07a\" ) lightseagreen = hex_to_bgr ( \"#20b2aa\" ) lightskyblue = hex_to_bgr ( \"#87cefa\" ) lightslategray = hex_to_bgr ( \"#778899\" ) lightslategrey = hex_to_bgr ( \"#778899\" ) lightsteelblue = hex_to_bgr ( \"#b0c4de\" ) lightyellow = hex_to_bgr ( \"#ffffe0\" ) lime = hex_to_bgr ( \"#00ff00\" ) limegreen = hex_to_bgr ( \"#32cd32\" ) linen = hex_to_bgr ( \"#faf0e6\" ) magenta = hex_to_bgr ( \"#ff00ff\" ) maroon = hex_to_bgr ( \"#800000\" ) mediumaquamarine = hex_to_bgr ( \"#66cdaa\" ) mediumblue = hex_to_bgr ( \"#0000cd\" ) mediumorchid = hex_to_bgr ( \"#ba55d3\" ) mediumpurple = hex_to_bgr ( \"#9370db\" ) mediumseagreen = hex_to_bgr ( \"#3cb371\" ) mediumslateblue = hex_to_bgr ( \"#7b68ee\" ) mediumspringgreen = hex_to_bgr ( \"#00fa9a\" ) mediumturquoise = hex_to_bgr ( \"#48d1cc\" ) mediumvioletred = hex_to_bgr ( \"#c71585\" ) midnightblue = hex_to_bgr ( \"#191970\" ) mintcream = hex_to_bgr ( \"#f5fffa\" ) mistyrose = hex_to_bgr ( \"#ffe4e1\" ) moccasin = hex_to_bgr ( \"#ffe4b5\" ) navajowhite = hex_to_bgr ( \"#ffdead\" ) navy = hex_to_bgr ( \"#000080\" ) oldlace = hex_to_bgr ( \"#fdf5e6\" ) olive = hex_to_bgr ( \"#808000\" ) olivedrab = hex_to_bgr ( \"#6b8e23\" ) orange = hex_to_bgr ( \"#ffa500\" ) orangered = hex_to_bgr ( \"#ff4500\" ) orchid = hex_to_bgr ( \"#da70d6\" ) palegoldenrod = hex_to_bgr ( \"#eee8aa\" ) palegreen = hex_to_bgr ( \"#98fb98\" ) paleturquoise = hex_to_bgr ( \"#afeeee\" ) palevioletred = hex_to_bgr ( \"#db7093\" ) papayawhip = hex_to_bgr ( \"#ffefd5\" ) peachpuff = hex_to_bgr ( \"#ffdab9\" ) peru = hex_to_bgr ( \"#cd853f\" ) pink = hex_to_bgr ( \"#ffc0cb\" ) plum = hex_to_bgr ( \"#dda0dd\" ) powderblue = hex_to_bgr ( \"#b0e0e6\" ) purple = hex_to_bgr ( \"#800080\" ) rebeccapurple = hex_to_bgr ( \"#663399\" ) red = hex_to_bgr ( \"#ff0000\" ) rosybrown = hex_to_bgr ( \"#bc8f8f\" ) royalblue = hex_to_bgr ( \"#4169e1\" ) saddlebrown = hex_to_bgr ( \"#8b4513\" ) salmon = hex_to_bgr ( \"#fa8072\" ) sandybrown = hex_to_bgr ( \"#f4a460\" ) seagreen = hex_to_bgr ( \"#2e8b57\" ) seashell = hex_to_bgr ( \"#fff5ee\" ) sienna = hex_to_bgr ( \"#a0522d\" ) silver = hex_to_bgr ( \"#c0c0c0\" ) skyblue = hex_to_bgr ( \"#87ceeb\" ) slateblue = hex_to_bgr ( \"#6a5acd\" ) slategray = hex_to_bgr ( \"#708090\" ) slategrey = hex_to_bgr ( \"#708090\" ) snow = hex_to_bgr ( \"#fffafa\" ) springgreen = hex_to_bgr ( \"#00ff7f\" ) steelblue = hex_to_bgr ( \"#4682b4\" ) tan = hex_to_bgr ( \"#d2b48c\" ) teal = hex_to_bgr ( \"#008080\" ) thistle = hex_to_bgr ( \"#d8bfd8\" ) tomato = hex_to_bgr ( \"#ff6347\" ) turquoise = hex_to_bgr ( \"#40e0d0\" ) violet = hex_to_bgr ( \"#ee82ee\" ) wheat = hex_to_bgr ( \"#f5deb3\" ) white = hex_to_bgr ( \"#ffffff\" ) whitesmoke = hex_to_bgr ( \"#f5f5f5\" ) yellow = hex_to_bgr ( \"#ffff00\" ) yellowgreen = hex_to_bgr ( \"#9acd32\" ) # seaborn tab20 colors tab1 = hex_to_bgr ( \"#1f77b4\" ) tab2 = hex_to_bgr ( \"#aec7e8\" ) tab3 = hex_to_bgr ( \"#ff7f0e\" ) tab4 = hex_to_bgr ( \"#ffbb78\" ) tab5 = hex_to_bgr ( \"#2ca02c\" ) tab6 = hex_to_bgr ( \"#98df8a\" ) tab7 = hex_to_bgr ( \"#d62728\" ) tab8 = hex_to_bgr ( \"#ff9896\" ) tab9 = hex_to_bgr ( \"#9467bd\" ) tab10 = hex_to_bgr ( \"#c5b0d5\" ) tab11 = hex_to_bgr ( \"#8c564b\" ) tab12 = hex_to_bgr ( \"#c49c94\" ) tab13 = hex_to_bgr ( \"#e377c2\" ) tab14 = hex_to_bgr ( \"#f7b6d2\" ) tab15 = hex_to_bgr ( \"#7f7f7f\" ) tab16 = hex_to_bgr ( \"#c7c7c7\" ) tab17 = hex_to_bgr ( \"#bcbd22\" ) tab18 = hex_to_bgr ( \"#dbdb8d\" ) tab19 = hex_to_bgr ( \"#17becf\" ) tab20 = hex_to_bgr ( \"#9edae5\" ) # seaborn colorblind cb1 = hex_to_bgr ( \"#0173b2\" ) cb2 = hex_to_bgr ( \"#de8f05\" ) cb3 = hex_to_bgr ( \"#029e73\" ) cb4 = hex_to_bgr ( \"#d55e00\" ) cb5 = hex_to_bgr ( \"#cc78bc\" ) cb6 = hex_to_bgr ( \"#ca9161\" ) cb7 = hex_to_bgr ( \"#fbafe4\" ) cb8 = hex_to_bgr ( \"#949494\" ) cb9 = hex_to_bgr ( \"#ece133\" ) cb10 = hex_to_bgr ( \"#56b4e9\" ) Palette # Class to control the color pallete for drawing. Examples: Change palette: >>> from norfair import Palette >>> Palette . set ( \"colorblind\" ) >>> # or a custom palette >>> from norfair import Color >>> Palette . set ([ Color . red , Color . blue , \"#ffeeff\" ]) Source code in norfair/drawing/color.py 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 class Palette : \"\"\" Class to control the color pallete for drawing. Examples -------- Change palette: >>> from norfair import Palette >>> Palette.set(\"colorblind\") >>> # or a custom palette >>> from norfair import Color >>> Palette.set([Color.red, Color.blue, \"#ffeeff\"]) \"\"\" _colors = PALETTES [ \"tab10\" ] _default_color = Color . black @classmethod def set ( cls , palette : Union [ str , Iterable [ ColorLike ]]): \"\"\" Selects a color palette. Parameters ---------- palette : Union[str, Iterable[ColorLike]] can be either - the name of one of the predefined palettes `tab10`, `tab20`, or `colorblind` - a list of ColorLike objects that can be parsed by [`parse_color`][norfair.drawing.color.parse_color] \"\"\" if isinstance ( palette , str ): try : cls . _colors = PALETTES [ palette ] except KeyError as e : raise ValueError ( f \"Invalid palette name ' { palette } ', valid values are { PALETTES . keys () } \" ) from e else : colors = [] for c in palette : colors . append ( parse_color ( c )) cls . _colors = colors @classmethod def set_default_color ( cls , color : ColorLike ): \"\"\" Selects the default color of `choose_color` when hashable is None. Parameters ---------- color : ColorLike The new default color. \"\"\" cls . _default_color = parse_color ( color ) @classmethod def choose_color ( cls , hashable : Hashable ) -> ColorType : if hashable is None : return cls . _default_color return cls . _colors [ abs ( hash ( hashable )) % len ( cls . _colors )] set ( palette ) classmethod # Selects a color palette. Parameters: Name Type Description Default palette Union [ str , Iterable [ ColorLike ]] can be either - the name of one of the predefined palettes tab10 , tab20 , or colorblind - a list of ColorLike objects that can be parsed by parse_color required Source code in norfair/drawing/color.py 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 @classmethod def set ( cls , palette : Union [ str , Iterable [ ColorLike ]]): \"\"\" Selects a color palette. Parameters ---------- palette : Union[str, Iterable[ColorLike]] can be either - the name of one of the predefined palettes `tab10`, `tab20`, or `colorblind` - a list of ColorLike objects that can be parsed by [`parse_color`][norfair.drawing.color.parse_color] \"\"\" if isinstance ( palette , str ): try : cls . _colors = PALETTES [ palette ] except KeyError as e : raise ValueError ( f \"Invalid palette name ' { palette } ', valid values are { PALETTES . keys () } \" ) from e else : colors = [] for c in palette : colors . append ( parse_color ( c )) cls . _colors = colors set_default_color ( color ) classmethod # Selects the default color of choose_color when hashable is None. Parameters: Name Type Description Default color ColorLike The new default color. required Source code in norfair/drawing/color.py 355 356 357 358 359 360 361 362 363 364 365 @classmethod def set_default_color ( cls , color : ColorLike ): \"\"\" Selects the default color of `choose_color` when hashable is None. Parameters ---------- color : ColorLike The new default color. \"\"\" cls . _default_color = parse_color ( color ) hex_to_bgr ( hex_value ) # Converts conventional 6 digits hex colors to BGR tuples Parameters: Name Type Description Default hex_value str hex value with leading # for instance \"#ff0000\" required Returns: Type Description Tuple [ int , int , int ] BGR values Raises: Type Description ValueError if the string is invalid Source code in norfair/drawing/color.py 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 def hex_to_bgr ( hex_value : str ) -> ColorType : \"\"\"Converts conventional 6 digits hex colors to BGR tuples Parameters ---------- hex_value : str hex value with leading `#` for instance `\"#ff0000\"` Returns ------- Tuple[int, int, int] BGR values Raises ------ ValueError if the string is invalid \"\"\" if re . match ( \"#[a-f0-9] {6} $\" , hex_value ): return ( int ( hex_value [ 5 : 7 ], 16 ), int ( hex_value [ 3 : 5 ], 16 ), int ( hex_value [ 1 : 3 ], 16 ), ) if re . match ( \"#[a-f0-9] {3} $\" , hex_value ): return ( int ( hex_value [ 3 ] * 2 , 16 ), int ( hex_value [ 2 ] * 2 , 16 ), int ( hex_value [ 1 ] * 2 , 16 ), ) raise ValueError ( f \"' { hex_value } ' is not a valid color\" ) parse_color ( color_like ) # Makes best effort to parse the given value to a Color Parameters: Name Type Description Default color_like ColorLike Can be one of: a string with the 6 digits hex value ( \"#ff0000\" ) a string with one of the names defined in Colors ( \"red\" ) a BGR tuple ( (0, 0, 255) ) required Returns: Type Description Color The BGR tuple. Source code in norfair/drawing/color.py 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 def parse_color ( color_like : ColorLike ) -> ColorType : \"\"\"Makes best effort to parse the given value to a Color Parameters ---------- color_like : ColorLike Can be one of: 1. a string with the 6 digits hex value (`\"#ff0000\"`) 2. a string with one of the names defined in Colors (`\"red\"`) 3. a BGR tuple (`(0, 0, 255)`) Returns ------- Color The BGR tuple. \"\"\" if isinstance ( color_like , str ): if color_like . startswith ( \"#\" ): return hex_to_bgr ( color_like ) else : return getattr ( Color , color_like ) # TODO: validate? return tuple ([ int ( v ) for v in color_like ]) path # Paths # Class that draws the paths taken by a set of points of interest defined from the coordinates of each tracker estimation. Parameters: Name Type Description Default get_points_to_draw Optional [ Callable [[ np . array ], np . array ]], optional Function that takes a list of points (the .estimate attribute of a TrackedObject ) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. None thickness Optional [ int ], optional Thickness of the circles representing the paths of interest. None color Optional [ Tuple [ int , int , int ]], optional Color of the circles representing the paths of interest. None radius Optional [ int ], optional Radius of the circles representing the paths of interest. None attenuation float , optional A float number in [0, 1] that dictates the speed at which the path is erased. if it is 0 then the path is never erased. 0.01 Examples: >>> from norfair import Tracker , Video , Path >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> path_drawer = Path () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> tracked_objects = tracker . update ( detections ) >>> frame = path_drawer . draw ( frame , tracked_objects ) >>> video . write ( frame ) Source code in norfair/drawing/path.py 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 class Paths : \"\"\" Class that draws the paths taken by a set of points of interest defined from the coordinates of each tracker estimation. Parameters ---------- get_points_to_draw : Optional[Callable[[np.array], np.array]], optional Function that takes a list of points (the `.estimate` attribute of a [`TrackedObject`][norfair.tracker.TrackedObject]) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. thickness : Optional[int], optional Thickness of the circles representing the paths of interest. color : Optional[Tuple[int, int, int]], optional [Color][norfair.drawing.Color] of the circles representing the paths of interest. radius : Optional[int], optional Radius of the circles representing the paths of interest. attenuation : float, optional A float number in [0, 1] that dictates the speed at which the path is erased. if it is `0` then the path is never erased. Examples -------- >>> from norfair import Tracker, Video, Path >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> path_drawer = Path() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> tracked_objects = tracker.update(detections) >>> frame = path_drawer.draw(frame, tracked_objects) >>> video.write(frame) \"\"\" def __init__ ( self , get_points_to_draw : Optional [ Callable [[ np . array ], np . array ]] = None , thickness : Optional [ int ] = None , color : Optional [ Tuple [ int , int , int ]] = None , radius : Optional [ int ] = None , attenuation : float = 0.01 , ): if get_points_to_draw is None : def get_points_to_draw ( points ): return [ np . mean ( np . array ( points ), axis = 0 )] self . get_points_to_draw = get_points_to_draw self . radius = radius self . thickness = thickness self . color = color self . mask = None self . attenuation_factor = 1 - attenuation def draw ( self , frame : np . ndarray , tracked_objects : Sequence [ TrackedObject ] ) -> np . array : \"\"\" Draw the paths of the points interest on a frame. !!! warning This method does **not** draw frames in place as other drawers do, the resulting frame is returned. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. tracked_objects : Sequence[TrackedObject] List of [`TrackedObject`][norfair.tracker.TrackedObject] to get the points of interest in order to update the paths. Returns ------- np.array The resulting frame. \"\"\" if self . mask is None : frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) self . mask = np . zeros ( frame . shape , np . uint8 ) self . mask = ( self . mask * self . attenuation_factor ) . astype ( \"uint8\" ) for obj in tracked_objects : if obj . abs_to_rel is not None : warn_once ( \"It seems that your using the Path drawer together with MotionEstimator. This is not fully supported and the results will not be what's expected\" ) if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . estimate ) for point in points_to_draw : self . mask = Drawer . circle ( self . mask , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) return Drawer . alpha_blend ( self . mask , frame , alpha = 1 , beta = 1 ) draw ( frame , tracked_objects ) # Draw the paths of the points interest on a frame. Warning This method does not draw frames in place as other drawers do, the resulting frame is returned. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. required tracked_objects Sequence [ TrackedObject ] List of TrackedObject to get the points of interest in order to update the paths. required Returns: Type Description np . array The resulting frame. Source code in norfair/drawing/path.py 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 def draw ( self , frame : np . ndarray , tracked_objects : Sequence [ TrackedObject ] ) -> np . array : \"\"\" Draw the paths of the points interest on a frame. !!! warning This method does **not** draw frames in place as other drawers do, the resulting frame is returned. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. tracked_objects : Sequence[TrackedObject] List of [`TrackedObject`][norfair.tracker.TrackedObject] to get the points of interest in order to update the paths. Returns ------- np.array The resulting frame. \"\"\" if self . mask is None : frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) self . mask = np . zeros ( frame . shape , np . uint8 ) self . mask = ( self . mask * self . attenuation_factor ) . astype ( \"uint8\" ) for obj in tracked_objects : if obj . abs_to_rel is not None : warn_once ( \"It seems that your using the Path drawer together with MotionEstimator. This is not fully supported and the results will not be what's expected\" ) if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . estimate ) for point in points_to_draw : self . mask = Drawer . circle ( self . mask , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) return Drawer . alpha_blend ( self . mask , frame , alpha = 1 , beta = 1 ) AbsolutePaths # Class that draws the absolute paths taken by a set of points. Works just like Paths but supports camera motion. Warning This drawer is not optimized so it can be stremely slow. Performance degrades linearly with max_history * number_of_tracked_objects . Parameters: Name Type Description Default get_points_to_draw Optional [ Callable [[ np . array ], np . array ]], optional Function that takes a list of points (the .estimate attribute of a TrackedObject ) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. None thickness Optional [ int ], optional Thickness of the circles representing the paths of interest. None color Optional [ Tuple [ int , int , int ]], optional Color of the circles representing the paths of interest. None radius Optional [ int ], optional Radius of the circles representing the paths of interest. None max_history int , optional Number of past points to include in the path. High values make the drawing slower 20 Examples: >>> from norfair import Tracker , Video , Path >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> path_drawer = Path () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> tracked_objects = tracker . update ( detections ) >>> frame = path_drawer . draw ( frame , tracked_objects ) >>> video . write ( frame ) Source code in norfair/drawing/path.py 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 class AbsolutePaths : \"\"\" Class that draws the absolute paths taken by a set of points. Works just like [`Paths`][norfair.drawing.Paths] but supports camera motion. !!! warning This drawer is not optimized so it can be stremely slow. Performance degrades linearly with `max_history * number_of_tracked_objects`. Parameters ---------- get_points_to_draw : Optional[Callable[[np.array], np.array]], optional Function that takes a list of points (the `.estimate` attribute of a [`TrackedObject`][norfair.tracker.TrackedObject]) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. thickness : Optional[int], optional Thickness of the circles representing the paths of interest. color : Optional[Tuple[int, int, int]], optional [Color][norfair.drawing.Color] of the circles representing the paths of interest. radius : Optional[int], optional Radius of the circles representing the paths of interest. max_history : int, optional Number of past points to include in the path. High values make the drawing slower Examples -------- >>> from norfair import Tracker, Video, Path >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> path_drawer = Path() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> tracked_objects = tracker.update(detections) >>> frame = path_drawer.draw(frame, tracked_objects) >>> video.write(frame) \"\"\" def __init__ ( self , get_points_to_draw : Optional [ Callable [[ np . array ], np . array ]] = None , thickness : Optional [ int ] = None , color : Optional [ Tuple [ int , int , int ]] = None , radius : Optional [ int ] = None , max_history = 20 , ): if get_points_to_draw is None : def get_points_to_draw ( points ): return [ np . mean ( np . array ( points ), axis = 0 )] self . get_points_to_draw = get_points_to_draw self . radius = radius self . thickness = thickness self . color = color self . past_points = defaultdict ( lambda : []) self . max_history = max_history self . alphas = np . linspace ( 0.99 , 0.01 , max_history ) def draw ( self , frame , tracked_objects , coord_transform = None ): frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) for obj in tracked_objects : if not obj . live_points . any (): continue if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . get_estimate ( absolute = True )) for point in coord_transform . abs_to_rel ( points_to_draw ): Drawer . circle ( frame , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) last = points_to_draw for i , past_points in enumerate ( self . past_points [ obj . id ]): overlay = frame . copy () last = coord_transform . abs_to_rel ( last ) for j , point in enumerate ( coord_transform . abs_to_rel ( past_points )): Drawer . line ( overlay , tuple ( last [ j ] . astype ( int )), tuple ( point . astype ( int )), color = color , thickness = self . thickness , ) last = past_points alpha = self . alphas [ i ] frame = Drawer . alpha_blend ( overlay , frame , alpha = alpha ) self . past_points [ obj . id ] . insert ( 0 , points_to_draw ) self . past_points [ obj . id ] = self . past_points [ obj . id ][: self . max_history ] return frame fixed_camera # FixedCamera # Class used to stabilize video based on the camera motion. Starts with a larger frame, where the original frame is drawn on top of a black background. As the camera moves, the smaller frame moves in the opposite direction, stabilizing the objects in it. Useful for debugging or demoing the camera motion. Warning This only works with TranslationTransformation , using HomographyTransformation will result in unexpected behaviour. Warning If using other drawers, always apply this one last. Using other drawers on the scaled up frame will not work as expected. Note Sometimes the camera moves so far from the original point that the result won't fit in the scaled-up frame. In this case, a warning will be logged and the frames will be cropped to avoid errors. Parameters: Name Type Description Default scale float , optional The resulting video will have a resolution of scale * (H, W) where HxW is the resolution of the original video. Use a bigger scale if the camera is moving too much. 2 attenuation float , optional Controls how fast the older frames fade to black. 0.05 Examples: >>> # setup >>> tracker = Tracker ( \"frobenious\" , 100 ) >>> motion_estimator = MotionEstimator () >>> video = Video ( input_path = \"video.mp4\" ) >>> fixed_camera = FixedCamera () >>> # process video >>> for frame in video : >>> coord_transformations = motion_estimator . update ( frame ) >>> detections = get_detections ( frame ) >>> tracked_objects = tracker . update ( detections , coord_transformations ) >>> draw_tracked_objects ( frame , tracked_objects ) # fixed_camera should always be the last drawer >>> bigger_frame = fixed_camera . adjust_frame ( frame , coord_transformations ) >>> video . write ( bigger_frame ) Source code in norfair/drawing/fixed_camera.py 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 class FixedCamera : \"\"\" Class used to stabilize video based on the camera motion. Starts with a larger frame, where the original frame is drawn on top of a black background. As the camera moves, the smaller frame moves in the opposite direction, stabilizing the objects in it. Useful for debugging or demoing the camera motion. ![Example GIF](../../videos/camera_stabilization.gif) !!! Warning This only works with [`TranslationTransformation`][norfair.camera_motion.TranslationTransformation], using [`HomographyTransformation`][norfair.camera_motion.HomographyTransformation] will result in unexpected behaviour. !!! Warning If using other drawers, always apply this one last. Using other drawers on the scaled up frame will not work as expected. !!! Note Sometimes the camera moves so far from the original point that the result won't fit in the scaled-up frame. In this case, a warning will be logged and the frames will be cropped to avoid errors. Parameters ---------- scale : float, optional The resulting video will have a resolution of `scale * (H, W)` where HxW is the resolution of the original video. Use a bigger scale if the camera is moving too much. attenuation : float, optional Controls how fast the older frames fade to black. Examples -------- >>> # setup >>> tracker = Tracker(\"frobenious\", 100) >>> motion_estimator = MotionEstimator() >>> video = Video(input_path=\"video.mp4\") >>> fixed_camera = FixedCamera() >>> # process video >>> for frame in video: >>> coord_transformations = motion_estimator.update(frame) >>> detections = get_detections(frame) >>> tracked_objects = tracker.update(detections, coord_transformations) >>> draw_tracked_objects(frame, tracked_objects) # fixed_camera should always be the last drawer >>> bigger_frame = fixed_camera.adjust_frame(frame, coord_transformations) >>> video.write(bigger_frame) \"\"\" def __init__ ( self , scale : float = 2 , attenuation : float = 0.05 ): self . scale = scale self . _background = None self . _attenuation_factor = 1 - attenuation def adjust_frame ( self , frame : np . ndarray , coord_transformation : TranslationTransformation ) -> np . ndarray : \"\"\" Render scaled up frame. Parameters ---------- frame : np.ndarray The OpenCV frame. coord_transformation : TranslationTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] Returns ------- np.ndarray The new bigger frame with the original frame drawn on it. \"\"\" # initialize background if necessary if self . _background is None : original_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) scaled_size = tuple ( ( np . array ( original_size ) * np . array ( self . scale )) . round () . astype ( int ) ) self . _background = np . zeros ( [ scaled_size [ 1 ], scaled_size [ 0 ], frame . shape [ - 1 ]], frame . dtype , ) else : self . _background = ( self . _background * self . _attenuation_factor ) . astype ( frame . dtype ) # top_left is the anchor coordinate from where we start drawing the fame on top of the background # aim to draw it in the center of the background but transformations will move this point top_left = ( np . array ( self . _background . shape [: 2 ]) // 2 - np . array ( frame . shape [: 2 ]) // 2 ) top_left = ( coord_transformation . rel_to_abs ( top_left [:: - 1 ]) . round () . astype ( int )[:: - 1 ] ) # box of the background that will be updated and the limits of it background_y0 , background_y1 = ( top_left [ 0 ], top_left [ 0 ] + frame . shape [ 0 ]) background_x0 , background_x1 = ( top_left [ 1 ], top_left [ 1 ] + frame . shape [ 1 ]) background_size_y , background_size_x = self . _background . shape [: 2 ] # define box of the frame that will be used # if the scale is not enough to support the movement, warn the user but keep drawing # cropping the frame so that the operation doesn't fail frame_y0 , frame_y1 , frame_x0 , frame_x1 = ( 0 , frame . shape [ 0 ], 0 , frame . shape [ 1 ]) if ( background_y0 < 0 or background_x0 < 0 or background_y1 > background_size_y or background_x1 > background_size_x ): warn_once ( \"moving_camera_scale is not enough to cover the range of camera movement, frame will be cropped\" ) # crop left or top of the frame if necessary frame_y0 = max ( - background_y0 , 0 ) frame_x0 = max ( - background_x0 , 0 ) # crop right or bottom of the frame if necessary frame_y1 = max ( min ( background_size_y - background_y0 , background_y1 - background_y0 ), 0 ) frame_x1 = max ( min ( background_size_x - background_x0 , background_x1 - background_x0 ), 0 ) # handle cases where the limits of the background become negative which numpy will interpret incorrectly background_y0 = max ( background_y0 , 0 ) background_x0 = max ( background_x0 , 0 ) background_y1 = max ( background_y1 , 0 ) background_x1 = max ( background_x1 , 0 ) self . _background [ background_y0 : background_y1 , background_x0 : background_x1 , : ] = frame [ frame_y0 : frame_y1 , frame_x0 : frame_x1 , :] return self . _background adjust_frame ( frame , coord_transformation ) # Render scaled up frame. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame. required coord_transformation TranslationTransformation The coordinate transformation as returned by the MotionEstimator required Returns: Type Description np . ndarray The new bigger frame with the original frame drawn on it. Source code in norfair/drawing/fixed_camera.py 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 def adjust_frame ( self , frame : np . ndarray , coord_transformation : TranslationTransformation ) -> np . ndarray : \"\"\" Render scaled up frame. Parameters ---------- frame : np.ndarray The OpenCV frame. coord_transformation : TranslationTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] Returns ------- np.ndarray The new bigger frame with the original frame drawn on it. \"\"\" # initialize background if necessary if self . _background is None : original_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) scaled_size = tuple ( ( np . array ( original_size ) * np . array ( self . scale )) . round () . astype ( int ) ) self . _background = np . zeros ( [ scaled_size [ 1 ], scaled_size [ 0 ], frame . shape [ - 1 ]], frame . dtype , ) else : self . _background = ( self . _background * self . _attenuation_factor ) . astype ( frame . dtype ) # top_left is the anchor coordinate from where we start drawing the fame on top of the background # aim to draw it in the center of the background but transformations will move this point top_left = ( np . array ( self . _background . shape [: 2 ]) // 2 - np . array ( frame . shape [: 2 ]) // 2 ) top_left = ( coord_transformation . rel_to_abs ( top_left [:: - 1 ]) . round () . astype ( int )[:: - 1 ] ) # box of the background that will be updated and the limits of it background_y0 , background_y1 = ( top_left [ 0 ], top_left [ 0 ] + frame . shape [ 0 ]) background_x0 , background_x1 = ( top_left [ 1 ], top_left [ 1 ] + frame . shape [ 1 ]) background_size_y , background_size_x = self . _background . shape [: 2 ] # define box of the frame that will be used # if the scale is not enough to support the movement, warn the user but keep drawing # cropping the frame so that the operation doesn't fail frame_y0 , frame_y1 , frame_x0 , frame_x1 = ( 0 , frame . shape [ 0 ], 0 , frame . shape [ 1 ]) if ( background_y0 < 0 or background_x0 < 0 or background_y1 > background_size_y or background_x1 > background_size_x ): warn_once ( \"moving_camera_scale is not enough to cover the range of camera movement, frame will be cropped\" ) # crop left or top of the frame if necessary frame_y0 = max ( - background_y0 , 0 ) frame_x0 = max ( - background_x0 , 0 ) # crop right or bottom of the frame if necessary frame_y1 = max ( min ( background_size_y - background_y0 , background_y1 - background_y0 ), 0 ) frame_x1 = max ( min ( background_size_x - background_x0 , background_x1 - background_x0 ), 0 ) # handle cases where the limits of the background become negative which numpy will interpret incorrectly background_y0 = max ( background_y0 , 0 ) background_x0 = max ( background_x0 , 0 ) background_y1 = max ( background_y1 , 0 ) background_x1 = max ( background_x1 , 0 ) self . _background [ background_y0 : background_y1 , background_x0 : background_x1 , : ] = frame [ frame_y0 : frame_y1 , frame_x0 : frame_x1 , :] return self . _background absolute_grid # draw_absolute_grid ( frame , coord_transformations , grid_size = 20 , radius = 2 , thickness = 1 , color = Color . black , polar = False ) # Draw a grid of points in absolute coordinates. Useful for debugging camera motion. The points are drawn as if the camera were in the center of a sphere and points are drawn in the intersection of latitude and longitude lines over the surface of the sphere. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. required coord_transformations CoordinatesTransformation The coordinate transformation as returned by the MotionEstimator required grid_size int , optional How many points to draw. 20 radius int , optional Size of each point. 2 thickness int , optional Thickness of each point 1 color ColorType , optional Color of the points. Color.black polar Bool , optional If True, the points on the first frame are drawn as if the camera were pointing to a pole (viewed from the center of the earth). By default, False is used which means the points are drawn as if the camera were pointing to the Equator. False Source code in norfair/drawing/absolute_grid.py 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 def draw_absolute_grid ( frame : np . ndarray , coord_transformations : CoordinatesTransformation , grid_size : int = 20 , radius : int = 2 , thickness : int = 1 , color : ColorType = Color . black , polar : bool = False , ): \"\"\" Draw a grid of points in absolute coordinates. Useful for debugging camera motion. The points are drawn as if the camera were in the center of a sphere and points are drawn in the intersection of latitude and longitude lines over the surface of the sphere. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. coord_transformations : CoordinatesTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] grid_size : int, optional How many points to draw. radius : int, optional Size of each point. thickness : int, optional Thickness of each point color : ColorType, optional Color of the points. polar : Bool, optional If True, the points on the first frame are drawn as if the camera were pointing to a pole (viewed from the center of the earth). By default, False is used which means the points are drawn as if the camera were pointing to the Equator. \"\"\" h , w , _ = frame . shape # get absolute points grid points = _get_grid ( grid_size , w , h , polar = polar ) # transform the points to relative coordinates if coord_transformations is None : points_transformed = points else : points_transformed = coord_transformations . abs_to_rel ( points ) # filter points that are not visible visible_points = points_transformed [ ( points_transformed <= np . array ([ w , h ])) . all ( axis = 1 ) & ( points_transformed >= 0 ) . all ( axis = 1 ) ] for point in visible_points : Drawer . cross ( frame , point . astype ( int ), radius = radius , thickness = thickness , color = color )","title":"Drawing"},{"location":"reference/drawing/#drawing","text":"Collection of drawing functions","title":"Drawing"},{"location":"reference/drawing/#norfair.drawing.draw_points","text":"","title":"draw_points"},{"location":"reference/drawing/#norfair.drawing.draw_points.draw_points","text":"Draw the points included in a list of Detections or TrackedObjects. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. Modified in place. required drawables Union [ Sequence [ Detection ], Sequence [ TrackedObject ]], optional List of objects to draw, Detections and TrackedObjects are accepted. None radius Optional [ int ], optional Radius of the circles representing each point. By default a sensible value is picked considering the frame size. None thickness Optional [ int ], optional Thickness or width of the line. None color ColorLike , optional This parameter can take: A color as a tuple of ints describing the BGR (0, 0, 255) A 6-digit hex string \"#FF0000\" One of the defined color names \"red\" A string defining the strategy to choose colors from the Palette: based on the id of the objects \"by_id\" based on the label of the objects \"by_label\" random choice \"random\" If using by_id or by_label strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). 'by_id' color_by_label bool , optional Deprecated . set color=\"by_label\" . None draw_labels bool , optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. True draw_scores bool , optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False text_size Optional [ int ], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. None draw_ids bool , optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. True draw_points bool , optional Set to False to hide the points and just draw the text. True text_thickness Optional [ int ], optional Thickness of the font. By default it's scaled with the text_size . None text_color Optional [ ColorLike ], optional Color of the text. By default the same color as the box is used. None hide_dead_points bool , optional Set this param to False to always draw all points, even the ones considered \"dead\". A point is \"dead\" when the corresponding value of TrackedObject.live_points is set to False. If all objects are dead the object is not drawn. All points of a detection are considered to be alive. True detections Sequence [ Detection ], optional Deprecated . use drawables. None label_size Optional [ int ], optional Deprecated . text_size. None Returns: Type Description np . ndarray The resulting frame. Source code in norfair/drawing/draw_points.py 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 def draw_points ( frame : np . ndarray , drawables : Union [ Sequence [ Detection ], Sequence [ TrackedObject ]] = None , radius : Optional [ int ] = None , thickness : Optional [ int ] = None , color : ColorLike = \"by_id\" , color_by_label : bool = None , # deprecated draw_labels : bool = True , text_size : Optional [ int ] = None , draw_ids : bool = True , draw_points : bool = True , # pylint: disable=redefined-outer-name text_thickness : Optional [ int ] = None , text_color : Optional [ ColorLike ] = None , hide_dead_points : bool = True , detections : Sequence [ \"Detection\" ] = None , # deprecated label_size : Optional [ int ] = None , # deprecated draw_scores : bool = False , ) -> np . ndarray : \"\"\" Draw the points included in a list of Detections or TrackedObjects. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. Modified in place. drawables : Union[Sequence[Detection], Sequence[TrackedObject]], optional List of objects to draw, Detections and TrackedObjects are accepted. radius : Optional[int], optional Radius of the circles representing each point. By default a sensible value is picked considering the frame size. thickness : Optional[int], optional Thickness or width of the line. color : ColorLike, optional This parameter can take: 1. A color as a tuple of ints describing the BGR `(0, 0, 255)` 2. A 6-digit hex string `\"#FF0000\"` 3. One of the defined color names `\"red\"` 4. A string defining the strategy to choose colors from the Palette: 1. based on the id of the objects `\"by_id\"` 2. based on the label of the objects `\"by_label\"` 3. random choice `\"random\"` If using `by_id` or `by_label` strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). color_by_label : bool, optional **Deprecated**. set `color=\"by_label\"`. draw_labels : bool, optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. draw_scores : bool, optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. text_size : Optional[int], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. draw_ids : bool, optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. draw_points : bool, optional Set to False to hide the points and just draw the text. text_thickness : Optional[int], optional Thickness of the font. By default it's scaled with the `text_size`. text_color : Optional[ColorLike], optional Color of the text. By default the same color as the box is used. hide_dead_points : bool, optional Set this param to False to always draw all points, even the ones considered \"dead\". A point is \"dead\" when the corresponding value of `TrackedObject.live_points` is set to False. If all objects are dead the object is not drawn. All points of a detection are considered to be alive. detections : Sequence[Detection], optional **Deprecated**. use drawables. label_size : Optional[int], optional **Deprecated**. text_size. Returns ------- np.ndarray The resulting frame. \"\"\" # # handle deprecated parameters # if color_by_label is not None : warn_once ( 'Parameter \"color_by_label\" on function draw_points is deprecated, set `color=\"by_label\"` instead' ) color = \"by_label\" if detections is not None : warn_once ( \"Parameter 'detections' on function draw_points is deprecated, use 'drawables' instead\" ) drawables = detections if label_size is not None : warn_once ( \"Parameter 'label_size' on function draw_points is deprecated, use 'text_size' instead\" ) text_size = label_size # end if drawables is None : return if text_color is not None : text_color = parse_color ( text_color ) if color is None : color = \"by_id\" if thickness is None : thickness = - 1 if radius is None : radius = int ( round ( max ( max ( frame . shape ) * 0.002 , 1 ))) for o in drawables : if not isinstance ( o , Drawable ): d = Drawable ( o ) else : d = o if hide_dead_points and not d . live_points . any (): continue if color == \"by_id\" : obj_color = Palette . choose_color ( d . id ) elif color == \"by_label\" : obj_color = Palette . choose_color ( d . label ) elif color == \"random\" : obj_color = Palette . choose_color ( np . random . rand ()) else : obj_color = parse_color ( color ) if text_color is None : obj_text_color = obj_color else : obj_text_color = text_color if draw_points : for point , live in zip ( d . points , d . live_points ): if live or not hide_dead_points : Drawer . circle ( frame , tuple ( point . astype ( int )), radius = radius , color = obj_color , thickness = thickness , ) if draw_labels or draw_ids or draw_scores : position = d . points [ d . live_points ] . mean ( axis = 0 ) position -= radius text = _build_text ( d , draw_labels = draw_labels , draw_ids = draw_ids , draw_scores = draw_scores ) Drawer . text ( frame , text , tuple ( position . astype ( int )), size = text_size , color = obj_text_color , thickness = text_thickness , ) return frame","title":"draw_points()"},{"location":"reference/drawing/#norfair.drawing.draw_points.draw_tracked_objects","text":"Deprecated use draw_points Source code in norfair/drawing/draw_points.py 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 def draw_tracked_objects ( frame : np . ndarray , objects : Sequence [ \"TrackedObject\" ], radius : Optional [ int ] = None , color : Optional [ ColorLike ] = None , id_size : Optional [ float ] = None , id_thickness : Optional [ int ] = None , draw_points : bool = True , # pylint: disable=redefined-outer-name color_by_label : bool = False , draw_labels : bool = False , label_size : Optional [ int ] = None , ): \"\"\" **Deprecated** use [`draw_points`][norfair.drawing.draw_points.draw_points] \"\"\" warn_once ( \"draw_tracked_objects is deprecated, use draw_points instead\" ) frame_scale = frame . shape [ 0 ] / 100 if radius is None : radius = int ( frame_scale * 0.5 ) if id_size is None : id_size = frame_scale / 10 if id_thickness is None : id_thickness = int ( frame_scale / 5 ) if label_size is None : label_size = int ( max ( frame_scale / 100 , 1 )) _draw_points_alias ( frame = frame , drawables = objects , color = \"by_label\" if color_by_label else color , radius = radius , thickness = None , draw_labels = draw_labels , draw_ids = id_size is not None and id_size > 0 , draw_points = draw_points , text_size = label_size or id_size , text_thickness = id_thickness , text_color = None , hide_dead_points = True , ) # TODO: We used to have this function to debug # migrate it to use Drawer and clean it up # if possible maybe merge this functionality to the function above # def draw_debug_metrics( # frame: np.ndarray, # objects: Sequence[\"TrackedObject\"], # text_size: Optional[float] = None, # text_thickness: Optional[int] = None, # color: Optional[Tuple[int, int, int]] = None, # only_ids=None, # only_initializing_ids=None, # draw_score_threshold: float = 0, # color_by_label: bool = False, # draw_labels: bool = False, # ): # \"\"\"Draw objects with their debug information # It is recommended to set the input variable `objects` to `your_tracker_object.objects` # so you can also debug objects wich haven't finished initializing, and you get a more # complete view of what your tracker is doing on each step. # \"\"\" # frame_scale = frame.shape[0] / 100 # if text_size is None: # text_size = frame_scale / 10 # if text_thickness is None: # text_thickness = int(frame_scale / 5) # radius = int(frame_scale * 0.5) # for obj in objects: # if ( # not (obj.last_detection.scores is None) # and not (obj.last_detection.scores > draw_score_threshold).any() # ): # continue # if only_ids is not None: # if obj.id not in only_ids: # continue # if only_initializing_ids is not None: # if obj.initializing_id not in only_initializing_ids: # continue # if color_by_label: # text_color = Color.random(abs(hash(obj.label))) # elif color is None: # text_color = Color.random(obj.initializing_id) # else: # text_color = color # draw_position = _centroid( # obj.estimate[obj.last_detection.scores > draw_score_threshold] # if obj.last_detection.scores is not None # else obj.estimate # ) # for point in obj.estimate: # cv2.circle( # frame, # tuple(point.astype(int)), # radius=radius, # color=text_color, # thickness=-1, # ) # # Distance to last matched detection # if obj.last_distance is None: # last_dist = \"-\" # elif obj.last_distance > 999: # last_dist = \">\" # else: # last_dist = \"{:.2f}\".format(obj.last_distance) # # Distance to currently closest detection # if obj.current_min_distance is None: # current_min_dist = \"-\" # else: # current_min_dist = \"{:.2f}\".format(obj.current_min_distance) # # No support for multiline text in opencv :facepalm: # lines_to_draw = [ # \"{}|{}\".format(obj.id, obj.initializing_id), # \"a:{}\".format(obj.age), # \"h:{}\".format(obj.hit_counter), # \"ld:{}\".format(last_dist), # \"cd:{}\".format(current_min_dist), # ] # if draw_labels: # lines_to_draw.append(\"l:{}\".format(obj.label)) # for i, line in enumerate(lines_to_draw): # draw_position = ( # int(draw_position[0]), # int(draw_position[1] + i * text_size * 7 + 15), # ) # cv2.putText( # frame, # line, # draw_position, # cv2.FONT_HERSHEY_SIMPLEX, # text_size, # text_color, # text_thickness, # cv2.LINE_AA, # )","title":"draw_tracked_objects()"},{"location":"reference/drawing/#norfair.drawing.draw_boxes","text":"","title":"draw_boxes"},{"location":"reference/drawing/#norfair.drawing.draw_boxes.draw_boxes","text":"Draw bounding boxes corresponding to Detections or TrackedObjects. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. Modified in place. required drawables Union [ Sequence [ Detection ], Sequence [ TrackedObject ]], optional List of objects to draw, Detections and TrackedObjects are accepted. This objects are assumed to contain 2 bi-dimensional points defining the bounding box as [[x0, y0], [x1, y1]] . None color ColorLike , optional This parameter can take: A color as a tuple of ints describing the BGR (0, 0, 255) A 6-digit hex string \"#FF0000\" One of the defined color names \"red\" A string defining the strategy to choose colors from the Palette: based on the id of the objects \"by_id\" based on the label of the objects \"by_label\" random choice \"random\" If using by_id or by_label strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). 'by_id' thickness Optional [ int ], optional Thickness or width of the line. None random_color bool , optional Deprecated . Set color=\"random\". None color_by_label bool , optional Deprecated . Set color=\"by_label\". None draw_labels bool , optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False draw_scores bool , optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. False text_size Optional [ float ], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. None draw_ids bool , optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. True text_color Optional [ ColorLike ], optional Color of the text. By default the same color as the box is used. None text_thickness Optional [ int ], optional Thickness of the font. By default it's scaled with the text_size . None draw_box bool , optional Set to False to hide the box and just draw the text. True detections Sequence [ Detection ], optional Deprecated . Use drawables. None line_color Optional [ ColorLike ] Deprecated . Use color. None line_width Optional [ int ] Deprecated . Use thickness. None label_size Optional [ int ] Deprecated . Use text_size. None Returns: Type Description np . ndarray The resulting frame. Source code in norfair/drawing/draw_boxes.py 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 def draw_boxes ( frame : np . ndarray , drawables : Union [ Sequence [ Detection ], Sequence [ TrackedObject ]] = None , color : ColorLike = \"by_id\" , thickness : Optional [ int ] = None , random_color : bool = None , # Deprecated color_by_label : bool = None , # Deprecated draw_labels : bool = False , text_size : Optional [ float ] = None , draw_ids : bool = True , text_color : Optional [ ColorLike ] = None , text_thickness : Optional [ int ] = None , draw_box : bool = True , detections : Sequence [ \"Detection\" ] = None , # Deprecated line_color : Optional [ ColorLike ] = None , # Deprecated line_width : Optional [ int ] = None , # Deprecated label_size : Optional [ int ] = None , # Deprecated\u00b4 draw_scores : bool = False , ) -> np . ndarray : \"\"\" Draw bounding boxes corresponding to Detections or TrackedObjects. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. Modified in place. drawables : Union[Sequence[Detection], Sequence[TrackedObject]], optional List of objects to draw, Detections and TrackedObjects are accepted. This objects are assumed to contain 2 bi-dimensional points defining the bounding box as `[[x0, y0], [x1, y1]]`. color : ColorLike, optional This parameter can take: 1. A color as a tuple of ints describing the BGR `(0, 0, 255)` 2. A 6-digit hex string `\"#FF0000\"` 3. One of the defined color names `\"red\"` 4. A string defining the strategy to choose colors from the Palette: 1. based on the id of the objects `\"by_id\"` 2. based on the label of the objects `\"by_label\"` 3. random choice `\"random\"` If using `by_id` or `by_label` strategy but your objects don't have that field defined (Detections never have ids) the selected color will be the same for all objects (Palette's default Color). thickness : Optional[int], optional Thickness or width of the line. random_color : bool, optional **Deprecated**. Set color=\"random\". color_by_label : bool, optional **Deprecated**. Set color=\"by_label\". draw_labels : bool, optional If set to True, the label is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. draw_scores : bool, optional If set to True, the score is added to a title that is drawn on top of the box. If an object doesn't have a label this parameter is ignored. text_size : Optional[float], optional Size of the title, the value is used as a multiplier of the base size of the font. By default the size is scaled automatically based on the frame size. draw_ids : bool, optional If set to True, the id is added to a title that is drawn on top of the box. If an object doesn't have an id this parameter is ignored. text_color : Optional[ColorLike], optional Color of the text. By default the same color as the box is used. text_thickness : Optional[int], optional Thickness of the font. By default it's scaled with the `text_size`. draw_box : bool, optional Set to False to hide the box and just draw the text. detections : Sequence[Detection], optional **Deprecated**. Use drawables. line_color: Optional[ColorLike], optional **Deprecated**. Use color. line_width: Optional[int], optional **Deprecated**. Use thickness. label_size: Optional[int], optional **Deprecated**. Use text_size. Returns ------- np.ndarray The resulting frame. \"\"\" # # handle deprecated parameters # if random_color is not None : warn_once ( 'Parameter \"random_color\" is deprecated, set `color=\"random\"` instead' ) color = \"random\" if color_by_label is not None : warn_once ( 'Parameter \"color_by_label\" is deprecated, set `color=\"by_label\"` instead' ) color = \"by_label\" if detections is not None : warn_once ( 'Parameter \"detections\" is deprecated, use \"drawables\" instead' ) drawables = detections if line_color is not None : warn_once ( 'Parameter \"line_color\" is deprecated, use \"color\" instead' ) color = line_color if line_width is not None : warn_once ( 'Parameter \"line_width\" is deprecated, use \"thickness\" instead' ) thickness = line_width if label_size is not None : warn_once ( 'Parameter \"label_size\" is deprecated, use \"text_size\" instead' ) text_size = label_size # end if color is None : color = \"by_id\" if thickness is None : thickness = int ( max ( frame . shape ) / 500 ) if drawables is None : return frame if text_color is not None : text_color = parse_color ( text_color ) for obj in drawables : if not isinstance ( obj , Drawable ): d = Drawable ( obj ) else : d = obj if color == \"by_id\" : obj_color = Palette . choose_color ( d . id ) elif color == \"by_label\" : obj_color = Palette . choose_color ( d . label ) elif color == \"random\" : obj_color = Palette . choose_color ( np . random . rand ()) else : obj_color = parse_color ( color ) points = d . points . astype ( int ) if draw_box : Drawer . rectangle ( frame , tuple ( points ), color = obj_color , thickness = thickness , ) text = _build_text ( d , draw_labels = draw_labels , draw_ids = draw_ids , draw_scores = draw_scores ) if text : if text_color is None : obj_text_color = obj_color else : obj_text_color = text_color # the anchor will become the bottom-left of the text, # we select-top left of the bbox compensating for the thickness of the box text_anchor = ( points [ 0 , 0 ] - thickness // 2 , points [ 0 , 1 ] - thickness // 2 - 1 , ) frame = Drawer . text ( frame , text , position = text_anchor , size = text_size , color = obj_text_color , thickness = text_thickness , ) return frame","title":"draw_boxes()"},{"location":"reference/drawing/#norfair.drawing.draw_boxes.draw_tracked_boxes","text":"Deprecated . Use draw_box Source code in norfair/drawing/draw_boxes.py 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 def draw_tracked_boxes ( frame : np . ndarray , objects : Sequence [ \"TrackedObject\" ], border_colors : Optional [ Tuple [ int , int , int ]] = None , border_width : Optional [ int ] = None , id_size : Optional [ int ] = None , id_thickness : Optional [ int ] = None , draw_box : bool = True , color_by_label : bool = False , draw_labels : bool = False , label_size : Optional [ int ] = None , label_width : Optional [ int ] = None , ) -> np . array : \"**Deprecated**. Use [`draw_box`][norfair.drawing.draw_boxes.draw_boxes]\" warn_once ( \"draw_tracked_boxes is deprecated, use draw_box instead\" ) return draw_boxes ( frame = frame , drawables = objects , color = \"by_label\" if color_by_label else border_colors , thickness = border_width , text_size = label_size or id_size , text_thickness = id_thickness or label_width , draw_labels = draw_labels , draw_ids = id_size is not None and id_size > 0 , draw_box = draw_box , )","title":"draw_tracked_boxes()"},{"location":"reference/drawing/#norfair.drawing.color","text":"","title":"color"},{"location":"reference/drawing/#norfair.drawing.color.Color","text":"Contains predefined colors. Colors are defined as a Tuple of integers between 0 and 255 expressing the values in BGR This is the format opencv uses. Source code in norfair/drawing/color.py 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 class Color : \"\"\" Contains predefined colors. Colors are defined as a Tuple of integers between 0 and 255 expressing the values in BGR This is the format opencv uses. \"\"\" # from PIL.ImageColors.colormap aliceblue = hex_to_bgr ( \"#f0f8ff\" ) antiquewhite = hex_to_bgr ( \"#faebd7\" ) aqua = hex_to_bgr ( \"#00ffff\" ) aquamarine = hex_to_bgr ( \"#7fffd4\" ) azure = hex_to_bgr ( \"#f0ffff\" ) beige = hex_to_bgr ( \"#f5f5dc\" ) bisque = hex_to_bgr ( \"#ffe4c4\" ) black = hex_to_bgr ( \"#000000\" ) blanchedalmond = hex_to_bgr ( \"#ffebcd\" ) blue = hex_to_bgr ( \"#0000ff\" ) blueviolet = hex_to_bgr ( \"#8a2be2\" ) brown = hex_to_bgr ( \"#a52a2a\" ) burlywood = hex_to_bgr ( \"#deb887\" ) cadetblue = hex_to_bgr ( \"#5f9ea0\" ) chartreuse = hex_to_bgr ( \"#7fff00\" ) chocolate = hex_to_bgr ( \"#d2691e\" ) coral = hex_to_bgr ( \"#ff7f50\" ) cornflowerblue = hex_to_bgr ( \"#6495ed\" ) cornsilk = hex_to_bgr ( \"#fff8dc\" ) crimson = hex_to_bgr ( \"#dc143c\" ) cyan = hex_to_bgr ( \"#00ffff\" ) darkblue = hex_to_bgr ( \"#00008b\" ) darkcyan = hex_to_bgr ( \"#008b8b\" ) darkgoldenrod = hex_to_bgr ( \"#b8860b\" ) darkgray = hex_to_bgr ( \"#a9a9a9\" ) darkgrey = hex_to_bgr ( \"#a9a9a9\" ) darkgreen = hex_to_bgr ( \"#006400\" ) darkkhaki = hex_to_bgr ( \"#bdb76b\" ) darkmagenta = hex_to_bgr ( \"#8b008b\" ) darkolivegreen = hex_to_bgr ( \"#556b2f\" ) darkorange = hex_to_bgr ( \"#ff8c00\" ) darkorchid = hex_to_bgr ( \"#9932cc\" ) darkred = hex_to_bgr ( \"#8b0000\" ) darksalmon = hex_to_bgr ( \"#e9967a\" ) darkseagreen = hex_to_bgr ( \"#8fbc8f\" ) darkslateblue = hex_to_bgr ( \"#483d8b\" ) darkslategray = hex_to_bgr ( \"#2f4f4f\" ) darkslategrey = hex_to_bgr ( \"#2f4f4f\" ) darkturquoise = hex_to_bgr ( \"#00ced1\" ) darkviolet = hex_to_bgr ( \"#9400d3\" ) deeppink = hex_to_bgr ( \"#ff1493\" ) deepskyblue = hex_to_bgr ( \"#00bfff\" ) dimgray = hex_to_bgr ( \"#696969\" ) dimgrey = hex_to_bgr ( \"#696969\" ) dodgerblue = hex_to_bgr ( \"#1e90ff\" ) firebrick = hex_to_bgr ( \"#b22222\" ) floralwhite = hex_to_bgr ( \"#fffaf0\" ) forestgreen = hex_to_bgr ( \"#228b22\" ) fuchsia = hex_to_bgr ( \"#ff00ff\" ) gainsboro = hex_to_bgr ( \"#dcdcdc\" ) ghostwhite = hex_to_bgr ( \"#f8f8ff\" ) gold = hex_to_bgr ( \"#ffd700\" ) goldenrod = hex_to_bgr ( \"#daa520\" ) gray = hex_to_bgr ( \"#808080\" ) grey = hex_to_bgr ( \"#808080\" ) green = ( 0 , 128 , 0 ) greenyellow = hex_to_bgr ( \"#adff2f\" ) honeydew = hex_to_bgr ( \"#f0fff0\" ) hotpink = hex_to_bgr ( \"#ff69b4\" ) indianred = hex_to_bgr ( \"#cd5c5c\" ) indigo = hex_to_bgr ( \"#4b0082\" ) ivory = hex_to_bgr ( \"#fffff0\" ) khaki = hex_to_bgr ( \"#f0e68c\" ) lavender = hex_to_bgr ( \"#e6e6fa\" ) lavenderblush = hex_to_bgr ( \"#fff0f5\" ) lawngreen = hex_to_bgr ( \"#7cfc00\" ) lemonchiffon = hex_to_bgr ( \"#fffacd\" ) lightblue = hex_to_bgr ( \"#add8e6\" ) lightcoral = hex_to_bgr ( \"#f08080\" ) lightcyan = hex_to_bgr ( \"#e0ffff\" ) lightgoldenrodyellow = hex_to_bgr ( \"#fafad2\" ) lightgreen = hex_to_bgr ( \"#90ee90\" ) lightgray = hex_to_bgr ( \"#d3d3d3\" ) lightgrey = hex_to_bgr ( \"#d3d3d3\" ) lightpink = hex_to_bgr ( \"#ffb6c1\" ) lightsalmon = hex_to_bgr ( \"#ffa07a\" ) lightseagreen = hex_to_bgr ( \"#20b2aa\" ) lightskyblue = hex_to_bgr ( \"#87cefa\" ) lightslategray = hex_to_bgr ( \"#778899\" ) lightslategrey = hex_to_bgr ( \"#778899\" ) lightsteelblue = hex_to_bgr ( \"#b0c4de\" ) lightyellow = hex_to_bgr ( \"#ffffe0\" ) lime = hex_to_bgr ( \"#00ff00\" ) limegreen = hex_to_bgr ( \"#32cd32\" ) linen = hex_to_bgr ( \"#faf0e6\" ) magenta = hex_to_bgr ( \"#ff00ff\" ) maroon = hex_to_bgr ( \"#800000\" ) mediumaquamarine = hex_to_bgr ( \"#66cdaa\" ) mediumblue = hex_to_bgr ( \"#0000cd\" ) mediumorchid = hex_to_bgr ( \"#ba55d3\" ) mediumpurple = hex_to_bgr ( \"#9370db\" ) mediumseagreen = hex_to_bgr ( \"#3cb371\" ) mediumslateblue = hex_to_bgr ( \"#7b68ee\" ) mediumspringgreen = hex_to_bgr ( \"#00fa9a\" ) mediumturquoise = hex_to_bgr ( \"#48d1cc\" ) mediumvioletred = hex_to_bgr ( \"#c71585\" ) midnightblue = hex_to_bgr ( \"#191970\" ) mintcream = hex_to_bgr ( \"#f5fffa\" ) mistyrose = hex_to_bgr ( \"#ffe4e1\" ) moccasin = hex_to_bgr ( \"#ffe4b5\" ) navajowhite = hex_to_bgr ( \"#ffdead\" ) navy = hex_to_bgr ( \"#000080\" ) oldlace = hex_to_bgr ( \"#fdf5e6\" ) olive = hex_to_bgr ( \"#808000\" ) olivedrab = hex_to_bgr ( \"#6b8e23\" ) orange = hex_to_bgr ( \"#ffa500\" ) orangered = hex_to_bgr ( \"#ff4500\" ) orchid = hex_to_bgr ( \"#da70d6\" ) palegoldenrod = hex_to_bgr ( \"#eee8aa\" ) palegreen = hex_to_bgr ( \"#98fb98\" ) paleturquoise = hex_to_bgr ( \"#afeeee\" ) palevioletred = hex_to_bgr ( \"#db7093\" ) papayawhip = hex_to_bgr ( \"#ffefd5\" ) peachpuff = hex_to_bgr ( \"#ffdab9\" ) peru = hex_to_bgr ( \"#cd853f\" ) pink = hex_to_bgr ( \"#ffc0cb\" ) plum = hex_to_bgr ( \"#dda0dd\" ) powderblue = hex_to_bgr ( \"#b0e0e6\" ) purple = hex_to_bgr ( \"#800080\" ) rebeccapurple = hex_to_bgr ( \"#663399\" ) red = hex_to_bgr ( \"#ff0000\" ) rosybrown = hex_to_bgr ( \"#bc8f8f\" ) royalblue = hex_to_bgr ( \"#4169e1\" ) saddlebrown = hex_to_bgr ( \"#8b4513\" ) salmon = hex_to_bgr ( \"#fa8072\" ) sandybrown = hex_to_bgr ( \"#f4a460\" ) seagreen = hex_to_bgr ( \"#2e8b57\" ) seashell = hex_to_bgr ( \"#fff5ee\" ) sienna = hex_to_bgr ( \"#a0522d\" ) silver = hex_to_bgr ( \"#c0c0c0\" ) skyblue = hex_to_bgr ( \"#87ceeb\" ) slateblue = hex_to_bgr ( \"#6a5acd\" ) slategray = hex_to_bgr ( \"#708090\" ) slategrey = hex_to_bgr ( \"#708090\" ) snow = hex_to_bgr ( \"#fffafa\" ) springgreen = hex_to_bgr ( \"#00ff7f\" ) steelblue = hex_to_bgr ( \"#4682b4\" ) tan = hex_to_bgr ( \"#d2b48c\" ) teal = hex_to_bgr ( \"#008080\" ) thistle = hex_to_bgr ( \"#d8bfd8\" ) tomato = hex_to_bgr ( \"#ff6347\" ) turquoise = hex_to_bgr ( \"#40e0d0\" ) violet = hex_to_bgr ( \"#ee82ee\" ) wheat = hex_to_bgr ( \"#f5deb3\" ) white = hex_to_bgr ( \"#ffffff\" ) whitesmoke = hex_to_bgr ( \"#f5f5f5\" ) yellow = hex_to_bgr ( \"#ffff00\" ) yellowgreen = hex_to_bgr ( \"#9acd32\" ) # seaborn tab20 colors tab1 = hex_to_bgr ( \"#1f77b4\" ) tab2 = hex_to_bgr ( \"#aec7e8\" ) tab3 = hex_to_bgr ( \"#ff7f0e\" ) tab4 = hex_to_bgr ( \"#ffbb78\" ) tab5 = hex_to_bgr ( \"#2ca02c\" ) tab6 = hex_to_bgr ( \"#98df8a\" ) tab7 = hex_to_bgr ( \"#d62728\" ) tab8 = hex_to_bgr ( \"#ff9896\" ) tab9 = hex_to_bgr ( \"#9467bd\" ) tab10 = hex_to_bgr ( \"#c5b0d5\" ) tab11 = hex_to_bgr ( \"#8c564b\" ) tab12 = hex_to_bgr ( \"#c49c94\" ) tab13 = hex_to_bgr ( \"#e377c2\" ) tab14 = hex_to_bgr ( \"#f7b6d2\" ) tab15 = hex_to_bgr ( \"#7f7f7f\" ) tab16 = hex_to_bgr ( \"#c7c7c7\" ) tab17 = hex_to_bgr ( \"#bcbd22\" ) tab18 = hex_to_bgr ( \"#dbdb8d\" ) tab19 = hex_to_bgr ( \"#17becf\" ) tab20 = hex_to_bgr ( \"#9edae5\" ) # seaborn colorblind cb1 = hex_to_bgr ( \"#0173b2\" ) cb2 = hex_to_bgr ( \"#de8f05\" ) cb3 = hex_to_bgr ( \"#029e73\" ) cb4 = hex_to_bgr ( \"#d55e00\" ) cb5 = hex_to_bgr ( \"#cc78bc\" ) cb6 = hex_to_bgr ( \"#ca9161\" ) cb7 = hex_to_bgr ( \"#fbafe4\" ) cb8 = hex_to_bgr ( \"#949494\" ) cb9 = hex_to_bgr ( \"#ece133\" ) cb10 = hex_to_bgr ( \"#56b4e9\" )","title":"Color"},{"location":"reference/drawing/#norfair.drawing.color.Palette","text":"Class to control the color pallete for drawing. Examples: Change palette: >>> from norfair import Palette >>> Palette . set ( \"colorblind\" ) >>> # or a custom palette >>> from norfair import Color >>> Palette . set ([ Color . red , Color . blue , \"#ffeeff\" ]) Source code in norfair/drawing/color.py 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 class Palette : \"\"\" Class to control the color pallete for drawing. Examples -------- Change palette: >>> from norfair import Palette >>> Palette.set(\"colorblind\") >>> # or a custom palette >>> from norfair import Color >>> Palette.set([Color.red, Color.blue, \"#ffeeff\"]) \"\"\" _colors = PALETTES [ \"tab10\" ] _default_color = Color . black @classmethod def set ( cls , palette : Union [ str , Iterable [ ColorLike ]]): \"\"\" Selects a color palette. Parameters ---------- palette : Union[str, Iterable[ColorLike]] can be either - the name of one of the predefined palettes `tab10`, `tab20`, or `colorblind` - a list of ColorLike objects that can be parsed by [`parse_color`][norfair.drawing.color.parse_color] \"\"\" if isinstance ( palette , str ): try : cls . _colors = PALETTES [ palette ] except KeyError as e : raise ValueError ( f \"Invalid palette name ' { palette } ', valid values are { PALETTES . keys () } \" ) from e else : colors = [] for c in palette : colors . append ( parse_color ( c )) cls . _colors = colors @classmethod def set_default_color ( cls , color : ColorLike ): \"\"\" Selects the default color of `choose_color` when hashable is None. Parameters ---------- color : ColorLike The new default color. \"\"\" cls . _default_color = parse_color ( color ) @classmethod def choose_color ( cls , hashable : Hashable ) -> ColorType : if hashable is None : return cls . _default_color return cls . _colors [ abs ( hash ( hashable )) % len ( cls . _colors )]","title":"Palette"},{"location":"reference/drawing/#norfair.drawing.color.Palette.set","text":"Selects a color palette. Parameters: Name Type Description Default palette Union [ str , Iterable [ ColorLike ]] can be either - the name of one of the predefined palettes tab10 , tab20 , or colorblind - a list of ColorLike objects that can be parsed by parse_color required Source code in norfair/drawing/color.py 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 @classmethod def set ( cls , palette : Union [ str , Iterable [ ColorLike ]]): \"\"\" Selects a color palette. Parameters ---------- palette : Union[str, Iterable[ColorLike]] can be either - the name of one of the predefined palettes `tab10`, `tab20`, or `colorblind` - a list of ColorLike objects that can be parsed by [`parse_color`][norfair.drawing.color.parse_color] \"\"\" if isinstance ( palette , str ): try : cls . _colors = PALETTES [ palette ] except KeyError as e : raise ValueError ( f \"Invalid palette name ' { palette } ', valid values are { PALETTES . keys () } \" ) from e else : colors = [] for c in palette : colors . append ( parse_color ( c )) cls . _colors = colors","title":"set()"},{"location":"reference/drawing/#norfair.drawing.color.Palette.set_default_color","text":"Selects the default color of choose_color when hashable is None. Parameters: Name Type Description Default color ColorLike The new default color. required Source code in norfair/drawing/color.py 355 356 357 358 359 360 361 362 363 364 365 @classmethod def set_default_color ( cls , color : ColorLike ): \"\"\" Selects the default color of `choose_color` when hashable is None. Parameters ---------- color : ColorLike The new default color. \"\"\" cls . _default_color = parse_color ( color )","title":"set_default_color()"},{"location":"reference/drawing/#norfair.drawing.color.hex_to_bgr","text":"Converts conventional 6 digits hex colors to BGR tuples Parameters: Name Type Description Default hex_value str hex value with leading # for instance \"#ff0000\" required Returns: Type Description Tuple [ int , int , int ] BGR values Raises: Type Description ValueError if the string is invalid Source code in norfair/drawing/color.py 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 def hex_to_bgr ( hex_value : str ) -> ColorType : \"\"\"Converts conventional 6 digits hex colors to BGR tuples Parameters ---------- hex_value : str hex value with leading `#` for instance `\"#ff0000\"` Returns ------- Tuple[int, int, int] BGR values Raises ------ ValueError if the string is invalid \"\"\" if re . match ( \"#[a-f0-9] {6} $\" , hex_value ): return ( int ( hex_value [ 5 : 7 ], 16 ), int ( hex_value [ 3 : 5 ], 16 ), int ( hex_value [ 1 : 3 ], 16 ), ) if re . match ( \"#[a-f0-9] {3} $\" , hex_value ): return ( int ( hex_value [ 3 ] * 2 , 16 ), int ( hex_value [ 2 ] * 2 , 16 ), int ( hex_value [ 1 ] * 2 , 16 ), ) raise ValueError ( f \"' { hex_value } ' is not a valid color\" )","title":"hex_to_bgr()"},{"location":"reference/drawing/#norfair.drawing.color.parse_color","text":"Makes best effort to parse the given value to a Color Parameters: Name Type Description Default color_like ColorLike Can be one of: a string with the 6 digits hex value ( \"#ff0000\" ) a string with one of the names defined in Colors ( \"red\" ) a BGR tuple ( (0, 0, 255) ) required Returns: Type Description Color The BGR tuple. Source code in norfair/drawing/color.py 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 def parse_color ( color_like : ColorLike ) -> ColorType : \"\"\"Makes best effort to parse the given value to a Color Parameters ---------- color_like : ColorLike Can be one of: 1. a string with the 6 digits hex value (`\"#ff0000\"`) 2. a string with one of the names defined in Colors (`\"red\"`) 3. a BGR tuple (`(0, 0, 255)`) Returns ------- Color The BGR tuple. \"\"\" if isinstance ( color_like , str ): if color_like . startswith ( \"#\" ): return hex_to_bgr ( color_like ) else : return getattr ( Color , color_like ) # TODO: validate? return tuple ([ int ( v ) for v in color_like ])","title":"parse_color()"},{"location":"reference/drawing/#norfair.drawing.path","text":"","title":"path"},{"location":"reference/drawing/#norfair.drawing.path.Paths","text":"Class that draws the paths taken by a set of points of interest defined from the coordinates of each tracker estimation. Parameters: Name Type Description Default get_points_to_draw Optional [ Callable [[ np . array ], np . array ]], optional Function that takes a list of points (the .estimate attribute of a TrackedObject ) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. None thickness Optional [ int ], optional Thickness of the circles representing the paths of interest. None color Optional [ Tuple [ int , int , int ]], optional Color of the circles representing the paths of interest. None radius Optional [ int ], optional Radius of the circles representing the paths of interest. None attenuation float , optional A float number in [0, 1] that dictates the speed at which the path is erased. if it is 0 then the path is never erased. 0.01 Examples: >>> from norfair import Tracker , Video , Path >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> path_drawer = Path () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> tracked_objects = tracker . update ( detections ) >>> frame = path_drawer . draw ( frame , tracked_objects ) >>> video . write ( frame ) Source code in norfair/drawing/path.py 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 class Paths : \"\"\" Class that draws the paths taken by a set of points of interest defined from the coordinates of each tracker estimation. Parameters ---------- get_points_to_draw : Optional[Callable[[np.array], np.array]], optional Function that takes a list of points (the `.estimate` attribute of a [`TrackedObject`][norfair.tracker.TrackedObject]) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. thickness : Optional[int], optional Thickness of the circles representing the paths of interest. color : Optional[Tuple[int, int, int]], optional [Color][norfair.drawing.Color] of the circles representing the paths of interest. radius : Optional[int], optional Radius of the circles representing the paths of interest. attenuation : float, optional A float number in [0, 1] that dictates the speed at which the path is erased. if it is `0` then the path is never erased. Examples -------- >>> from norfair import Tracker, Video, Path >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> path_drawer = Path() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> tracked_objects = tracker.update(detections) >>> frame = path_drawer.draw(frame, tracked_objects) >>> video.write(frame) \"\"\" def __init__ ( self , get_points_to_draw : Optional [ Callable [[ np . array ], np . array ]] = None , thickness : Optional [ int ] = None , color : Optional [ Tuple [ int , int , int ]] = None , radius : Optional [ int ] = None , attenuation : float = 0.01 , ): if get_points_to_draw is None : def get_points_to_draw ( points ): return [ np . mean ( np . array ( points ), axis = 0 )] self . get_points_to_draw = get_points_to_draw self . radius = radius self . thickness = thickness self . color = color self . mask = None self . attenuation_factor = 1 - attenuation def draw ( self , frame : np . ndarray , tracked_objects : Sequence [ TrackedObject ] ) -> np . array : \"\"\" Draw the paths of the points interest on a frame. !!! warning This method does **not** draw frames in place as other drawers do, the resulting frame is returned. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. tracked_objects : Sequence[TrackedObject] List of [`TrackedObject`][norfair.tracker.TrackedObject] to get the points of interest in order to update the paths. Returns ------- np.array The resulting frame. \"\"\" if self . mask is None : frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) self . mask = np . zeros ( frame . shape , np . uint8 ) self . mask = ( self . mask * self . attenuation_factor ) . astype ( \"uint8\" ) for obj in tracked_objects : if obj . abs_to_rel is not None : warn_once ( \"It seems that your using the Path drawer together with MotionEstimator. This is not fully supported and the results will not be what's expected\" ) if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . estimate ) for point in points_to_draw : self . mask = Drawer . circle ( self . mask , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) return Drawer . alpha_blend ( self . mask , frame , alpha = 1 , beta = 1 )","title":"Paths"},{"location":"reference/drawing/#norfair.drawing.path.Paths.draw","text":"Draw the paths of the points interest on a frame. Warning This method does not draw frames in place as other drawers do, the resulting frame is returned. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. required tracked_objects Sequence [ TrackedObject ] List of TrackedObject to get the points of interest in order to update the paths. required Returns: Type Description np . array The resulting frame. Source code in norfair/drawing/path.py 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 def draw ( self , frame : np . ndarray , tracked_objects : Sequence [ TrackedObject ] ) -> np . array : \"\"\" Draw the paths of the points interest on a frame. !!! warning This method does **not** draw frames in place as other drawers do, the resulting frame is returned. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. tracked_objects : Sequence[TrackedObject] List of [`TrackedObject`][norfair.tracker.TrackedObject] to get the points of interest in order to update the paths. Returns ------- np.array The resulting frame. \"\"\" if self . mask is None : frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) self . mask = np . zeros ( frame . shape , np . uint8 ) self . mask = ( self . mask * self . attenuation_factor ) . astype ( \"uint8\" ) for obj in tracked_objects : if obj . abs_to_rel is not None : warn_once ( \"It seems that your using the Path drawer together with MotionEstimator. This is not fully supported and the results will not be what's expected\" ) if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . estimate ) for point in points_to_draw : self . mask = Drawer . circle ( self . mask , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) return Drawer . alpha_blend ( self . mask , frame , alpha = 1 , beta = 1 )","title":"draw()"},{"location":"reference/drawing/#norfair.drawing.path.AbsolutePaths","text":"Class that draws the absolute paths taken by a set of points. Works just like Paths but supports camera motion. Warning This drawer is not optimized so it can be stremely slow. Performance degrades linearly with max_history * number_of_tracked_objects . Parameters: Name Type Description Default get_points_to_draw Optional [ Callable [[ np . array ], np . array ]], optional Function that takes a list of points (the .estimate attribute of a TrackedObject ) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. None thickness Optional [ int ], optional Thickness of the circles representing the paths of interest. None color Optional [ Tuple [ int , int , int ]], optional Color of the circles representing the paths of interest. None radius Optional [ int ], optional Radius of the circles representing the paths of interest. None max_history int , optional Number of past points to include in the path. High values make the drawing slower 20 Examples: >>> from norfair import Tracker , Video , Path >>> video = Video ( \"video.mp4\" ) >>> tracker = Tracker ( ... ) >>> path_drawer = Path () >>> for frame in video : >>> detections = get_detections ( frame ) # runs detector and returns Detections >>> tracked_objects = tracker . update ( detections ) >>> frame = path_drawer . draw ( frame , tracked_objects ) >>> video . write ( frame ) Source code in norfair/drawing/path.py 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 class AbsolutePaths : \"\"\" Class that draws the absolute paths taken by a set of points. Works just like [`Paths`][norfair.drawing.Paths] but supports camera motion. !!! warning This drawer is not optimized so it can be stremely slow. Performance degrades linearly with `max_history * number_of_tracked_objects`. Parameters ---------- get_points_to_draw : Optional[Callable[[np.array], np.array]], optional Function that takes a list of points (the `.estimate` attribute of a [`TrackedObject`][norfair.tracker.TrackedObject]) and returns a list of points for which we want to draw their paths. By default it is the mean point of all the points in the tracker. thickness : Optional[int], optional Thickness of the circles representing the paths of interest. color : Optional[Tuple[int, int, int]], optional [Color][norfair.drawing.Color] of the circles representing the paths of interest. radius : Optional[int], optional Radius of the circles representing the paths of interest. max_history : int, optional Number of past points to include in the path. High values make the drawing slower Examples -------- >>> from norfair import Tracker, Video, Path >>> video = Video(\"video.mp4\") >>> tracker = Tracker(...) >>> path_drawer = Path() >>> for frame in video: >>> detections = get_detections(frame) # runs detector and returns Detections >>> tracked_objects = tracker.update(detections) >>> frame = path_drawer.draw(frame, tracked_objects) >>> video.write(frame) \"\"\" def __init__ ( self , get_points_to_draw : Optional [ Callable [[ np . array ], np . array ]] = None , thickness : Optional [ int ] = None , color : Optional [ Tuple [ int , int , int ]] = None , radius : Optional [ int ] = None , max_history = 20 , ): if get_points_to_draw is None : def get_points_to_draw ( points ): return [ np . mean ( np . array ( points ), axis = 0 )] self . get_points_to_draw = get_points_to_draw self . radius = radius self . thickness = thickness self . color = color self . past_points = defaultdict ( lambda : []) self . max_history = max_history self . alphas = np . linspace ( 0.99 , 0.01 , max_history ) def draw ( self , frame , tracked_objects , coord_transform = None ): frame_scale = frame . shape [ 0 ] / 100 if self . radius is None : self . radius = int ( max ( frame_scale * 0.7 , 1 )) if self . thickness is None : self . thickness = int ( max ( frame_scale / 7 , 1 )) for obj in tracked_objects : if not obj . live_points . any (): continue if self . color is None : color = Palette . choose_color ( obj . id ) else : color = self . color points_to_draw = self . get_points_to_draw ( obj . get_estimate ( absolute = True )) for point in coord_transform . abs_to_rel ( points_to_draw ): Drawer . circle ( frame , position = tuple ( point . astype ( int )), radius = self . radius , color = color , thickness = self . thickness , ) last = points_to_draw for i , past_points in enumerate ( self . past_points [ obj . id ]): overlay = frame . copy () last = coord_transform . abs_to_rel ( last ) for j , point in enumerate ( coord_transform . abs_to_rel ( past_points )): Drawer . line ( overlay , tuple ( last [ j ] . astype ( int )), tuple ( point . astype ( int )), color = color , thickness = self . thickness , ) last = past_points alpha = self . alphas [ i ] frame = Drawer . alpha_blend ( overlay , frame , alpha = alpha ) self . past_points [ obj . id ] . insert ( 0 , points_to_draw ) self . past_points [ obj . id ] = self . past_points [ obj . id ][: self . max_history ] return frame","title":"AbsolutePaths"},{"location":"reference/drawing/#norfair.drawing.fixed_camera","text":"","title":"fixed_camera"},{"location":"reference/drawing/#norfair.drawing.fixed_camera.FixedCamera","text":"Class used to stabilize video based on the camera motion. Starts with a larger frame, where the original frame is drawn on top of a black background. As the camera moves, the smaller frame moves in the opposite direction, stabilizing the objects in it. Useful for debugging or demoing the camera motion. Warning This only works with TranslationTransformation , using HomographyTransformation will result in unexpected behaviour. Warning If using other drawers, always apply this one last. Using other drawers on the scaled up frame will not work as expected. Note Sometimes the camera moves so far from the original point that the result won't fit in the scaled-up frame. In this case, a warning will be logged and the frames will be cropped to avoid errors. Parameters: Name Type Description Default scale float , optional The resulting video will have a resolution of scale * (H, W) where HxW is the resolution of the original video. Use a bigger scale if the camera is moving too much. 2 attenuation float , optional Controls how fast the older frames fade to black. 0.05 Examples: >>> # setup >>> tracker = Tracker ( \"frobenious\" , 100 ) >>> motion_estimator = MotionEstimator () >>> video = Video ( input_path = \"video.mp4\" ) >>> fixed_camera = FixedCamera () >>> # process video >>> for frame in video : >>> coord_transformations = motion_estimator . update ( frame ) >>> detections = get_detections ( frame ) >>> tracked_objects = tracker . update ( detections , coord_transformations ) >>> draw_tracked_objects ( frame , tracked_objects ) # fixed_camera should always be the last drawer >>> bigger_frame = fixed_camera . adjust_frame ( frame , coord_transformations ) >>> video . write ( bigger_frame ) Source code in norfair/drawing/fixed_camera.py 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 class FixedCamera : \"\"\" Class used to stabilize video based on the camera motion. Starts with a larger frame, where the original frame is drawn on top of a black background. As the camera moves, the smaller frame moves in the opposite direction, stabilizing the objects in it. Useful for debugging or demoing the camera motion. ![Example GIF](../../videos/camera_stabilization.gif) !!! Warning This only works with [`TranslationTransformation`][norfair.camera_motion.TranslationTransformation], using [`HomographyTransformation`][norfair.camera_motion.HomographyTransformation] will result in unexpected behaviour. !!! Warning If using other drawers, always apply this one last. Using other drawers on the scaled up frame will not work as expected. !!! Note Sometimes the camera moves so far from the original point that the result won't fit in the scaled-up frame. In this case, a warning will be logged and the frames will be cropped to avoid errors. Parameters ---------- scale : float, optional The resulting video will have a resolution of `scale * (H, W)` where HxW is the resolution of the original video. Use a bigger scale if the camera is moving too much. attenuation : float, optional Controls how fast the older frames fade to black. Examples -------- >>> # setup >>> tracker = Tracker(\"frobenious\", 100) >>> motion_estimator = MotionEstimator() >>> video = Video(input_path=\"video.mp4\") >>> fixed_camera = FixedCamera() >>> # process video >>> for frame in video: >>> coord_transformations = motion_estimator.update(frame) >>> detections = get_detections(frame) >>> tracked_objects = tracker.update(detections, coord_transformations) >>> draw_tracked_objects(frame, tracked_objects) # fixed_camera should always be the last drawer >>> bigger_frame = fixed_camera.adjust_frame(frame, coord_transformations) >>> video.write(bigger_frame) \"\"\" def __init__ ( self , scale : float = 2 , attenuation : float = 0.05 ): self . scale = scale self . _background = None self . _attenuation_factor = 1 - attenuation def adjust_frame ( self , frame : np . ndarray , coord_transformation : TranslationTransformation ) -> np . ndarray : \"\"\" Render scaled up frame. Parameters ---------- frame : np.ndarray The OpenCV frame. coord_transformation : TranslationTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] Returns ------- np.ndarray The new bigger frame with the original frame drawn on it. \"\"\" # initialize background if necessary if self . _background is None : original_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) scaled_size = tuple ( ( np . array ( original_size ) * np . array ( self . scale )) . round () . astype ( int ) ) self . _background = np . zeros ( [ scaled_size [ 1 ], scaled_size [ 0 ], frame . shape [ - 1 ]], frame . dtype , ) else : self . _background = ( self . _background * self . _attenuation_factor ) . astype ( frame . dtype ) # top_left is the anchor coordinate from where we start drawing the fame on top of the background # aim to draw it in the center of the background but transformations will move this point top_left = ( np . array ( self . _background . shape [: 2 ]) // 2 - np . array ( frame . shape [: 2 ]) // 2 ) top_left = ( coord_transformation . rel_to_abs ( top_left [:: - 1 ]) . round () . astype ( int )[:: - 1 ] ) # box of the background that will be updated and the limits of it background_y0 , background_y1 = ( top_left [ 0 ], top_left [ 0 ] + frame . shape [ 0 ]) background_x0 , background_x1 = ( top_left [ 1 ], top_left [ 1 ] + frame . shape [ 1 ]) background_size_y , background_size_x = self . _background . shape [: 2 ] # define box of the frame that will be used # if the scale is not enough to support the movement, warn the user but keep drawing # cropping the frame so that the operation doesn't fail frame_y0 , frame_y1 , frame_x0 , frame_x1 = ( 0 , frame . shape [ 0 ], 0 , frame . shape [ 1 ]) if ( background_y0 < 0 or background_x0 < 0 or background_y1 > background_size_y or background_x1 > background_size_x ): warn_once ( \"moving_camera_scale is not enough to cover the range of camera movement, frame will be cropped\" ) # crop left or top of the frame if necessary frame_y0 = max ( - background_y0 , 0 ) frame_x0 = max ( - background_x0 , 0 ) # crop right or bottom of the frame if necessary frame_y1 = max ( min ( background_size_y - background_y0 , background_y1 - background_y0 ), 0 ) frame_x1 = max ( min ( background_size_x - background_x0 , background_x1 - background_x0 ), 0 ) # handle cases where the limits of the background become negative which numpy will interpret incorrectly background_y0 = max ( background_y0 , 0 ) background_x0 = max ( background_x0 , 0 ) background_y1 = max ( background_y1 , 0 ) background_x1 = max ( background_x1 , 0 ) self . _background [ background_y0 : background_y1 , background_x0 : background_x1 , : ] = frame [ frame_y0 : frame_y1 , frame_x0 : frame_x1 , :] return self . _background","title":"FixedCamera"},{"location":"reference/drawing/#norfair.drawing.fixed_camera.FixedCamera.adjust_frame","text":"Render scaled up frame. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame. required coord_transformation TranslationTransformation The coordinate transformation as returned by the MotionEstimator required Returns: Type Description np . ndarray The new bigger frame with the original frame drawn on it. Source code in norfair/drawing/fixed_camera.py 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 def adjust_frame ( self , frame : np . ndarray , coord_transformation : TranslationTransformation ) -> np . ndarray : \"\"\" Render scaled up frame. Parameters ---------- frame : np.ndarray The OpenCV frame. coord_transformation : TranslationTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] Returns ------- np.ndarray The new bigger frame with the original frame drawn on it. \"\"\" # initialize background if necessary if self . _background is None : original_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) scaled_size = tuple ( ( np . array ( original_size ) * np . array ( self . scale )) . round () . astype ( int ) ) self . _background = np . zeros ( [ scaled_size [ 1 ], scaled_size [ 0 ], frame . shape [ - 1 ]], frame . dtype , ) else : self . _background = ( self . _background * self . _attenuation_factor ) . astype ( frame . dtype ) # top_left is the anchor coordinate from where we start drawing the fame on top of the background # aim to draw it in the center of the background but transformations will move this point top_left = ( np . array ( self . _background . shape [: 2 ]) // 2 - np . array ( frame . shape [: 2 ]) // 2 ) top_left = ( coord_transformation . rel_to_abs ( top_left [:: - 1 ]) . round () . astype ( int )[:: - 1 ] ) # box of the background that will be updated and the limits of it background_y0 , background_y1 = ( top_left [ 0 ], top_left [ 0 ] + frame . shape [ 0 ]) background_x0 , background_x1 = ( top_left [ 1 ], top_left [ 1 ] + frame . shape [ 1 ]) background_size_y , background_size_x = self . _background . shape [: 2 ] # define box of the frame that will be used # if the scale is not enough to support the movement, warn the user but keep drawing # cropping the frame so that the operation doesn't fail frame_y0 , frame_y1 , frame_x0 , frame_x1 = ( 0 , frame . shape [ 0 ], 0 , frame . shape [ 1 ]) if ( background_y0 < 0 or background_x0 < 0 or background_y1 > background_size_y or background_x1 > background_size_x ): warn_once ( \"moving_camera_scale is not enough to cover the range of camera movement, frame will be cropped\" ) # crop left or top of the frame if necessary frame_y0 = max ( - background_y0 , 0 ) frame_x0 = max ( - background_x0 , 0 ) # crop right or bottom of the frame if necessary frame_y1 = max ( min ( background_size_y - background_y0 , background_y1 - background_y0 ), 0 ) frame_x1 = max ( min ( background_size_x - background_x0 , background_x1 - background_x0 ), 0 ) # handle cases where the limits of the background become negative which numpy will interpret incorrectly background_y0 = max ( background_y0 , 0 ) background_x0 = max ( background_x0 , 0 ) background_y1 = max ( background_y1 , 0 ) background_x1 = max ( background_x1 , 0 ) self . _background [ background_y0 : background_y1 , background_x0 : background_x1 , : ] = frame [ frame_y0 : frame_y1 , frame_x0 : frame_x1 , :] return self . _background","title":"adjust_frame()"},{"location":"reference/drawing/#norfair.drawing.absolute_grid","text":"","title":"absolute_grid"},{"location":"reference/drawing/#norfair.drawing.absolute_grid.draw_absolute_grid","text":"Draw a grid of points in absolute coordinates. Useful for debugging camera motion. The points are drawn as if the camera were in the center of a sphere and points are drawn in the intersection of latitude and longitude lines over the surface of the sphere. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to draw on. required coord_transformations CoordinatesTransformation The coordinate transformation as returned by the MotionEstimator required grid_size int , optional How many points to draw. 20 radius int , optional Size of each point. 2 thickness int , optional Thickness of each point 1 color ColorType , optional Color of the points. Color.black polar Bool , optional If True, the points on the first frame are drawn as if the camera were pointing to a pole (viewed from the center of the earth). By default, False is used which means the points are drawn as if the camera were pointing to the Equator. False Source code in norfair/drawing/absolute_grid.py 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 def draw_absolute_grid ( frame : np . ndarray , coord_transformations : CoordinatesTransformation , grid_size : int = 20 , radius : int = 2 , thickness : int = 1 , color : ColorType = Color . black , polar : bool = False , ): \"\"\" Draw a grid of points in absolute coordinates. Useful for debugging camera motion. The points are drawn as if the camera were in the center of a sphere and points are drawn in the intersection of latitude and longitude lines over the surface of the sphere. Parameters ---------- frame : np.ndarray The OpenCV frame to draw on. coord_transformations : CoordinatesTransformation The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator] grid_size : int, optional How many points to draw. radius : int, optional Size of each point. thickness : int, optional Thickness of each point color : ColorType, optional Color of the points. polar : Bool, optional If True, the points on the first frame are drawn as if the camera were pointing to a pole (viewed from the center of the earth). By default, False is used which means the points are drawn as if the camera were pointing to the Equator. \"\"\" h , w , _ = frame . shape # get absolute points grid points = _get_grid ( grid_size , w , h , polar = polar ) # transform the points to relative coordinates if coord_transformations is None : points_transformed = points else : points_transformed = coord_transformations . abs_to_rel ( points ) # filter points that are not visible visible_points = points_transformed [ ( points_transformed <= np . array ([ w , h ])) . all ( axis = 1 ) & ( points_transformed >= 0 ) . all ( axis = 1 ) ] for point in visible_points : Drawer . cross ( frame , point . astype ( int ), radius = radius , thickness = thickness , color = color )","title":"draw_absolute_grid()"},{"location":"reference/filter/","text":"Filter # FilterPyKalmanFilterFactory # Bases: FilterFactory This class can be used either to change some parameters of the KalmanFilter that the tracker uses, or to fully customize the predictive filter implementation to use (as long as the methods and properties are compatible). The former case only requires changing the default parameters upon tracker creation: tracker = Tracker(..., filter_factory=FilterPyKalmanFilterFactory(R=100)) , while the latter requires creating your own class extending FilterPyKalmanFilterFactory , and rewriting its create_filter method to return your own customized filter. Parameters: Name Type Description Default R float , optional Multiplier for the sensor measurement noise matrix, by default 4.0 4.0 Q float , optional Multiplier for the process uncertainty, by default 0.1 0.1 P float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables, by default 10.0 10.0 See Also # filterpy.KalmanFilter . Source code in norfair/filter.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 class FilterPyKalmanFilterFactory ( FilterFactory ): \"\"\" This class can be used either to change some parameters of the [KalmanFilter](https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html) that the tracker uses, or to fully customize the predictive filter implementation to use (as long as the methods and properties are compatible). The former case only requires changing the default parameters upon tracker creation: `tracker = Tracker(..., filter_factory=FilterPyKalmanFilterFactory(R=100))`, while the latter requires creating your own class extending `FilterPyKalmanFilterFactory`, and rewriting its `create_filter` method to return your own customized filter. Parameters ---------- R : float, optional Multiplier for the sensor measurement noise matrix, by default 4.0 Q : float, optional Multiplier for the process uncertainty, by default 0.1 P : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables, by default 10.0 See Also -------- [`filterpy.KalmanFilter`](https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html). \"\"\" def __init__ ( self , R : float = 4.0 , Q : float = 0.1 , P : float = 10.0 ): self . R = R self . Q = Q self . P = P def create_filter ( self , initial_detection : np . ndarray ) -> KalmanFilter : \"\"\" This method returns a new predictive filter instance with the current setup, to be used by each new [`TrackedObject`][norfair.tracker.TrackedObject] that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters ---------- initial_detection : np.ndarray numpy array of shape `(number of points per object, 2)`, corresponding to the [`Detection.points`][norfair.tracker.Detection] of the tracked object being born, which shall be used as initial position estimation for it. Returns ------- KalmanFilter The kalman filter \"\"\" num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points dim_x = 2 * dim_z # We need to accommodate for velocities filter = KalmanFilter ( dim_x = dim_x , dim_z = dim_z ) # State transition matrix (models physics): numpy.array() filter . F = np . eye ( dim_x ) dt = 1 # At each step we update pos with v * dt filter . F [: dim_z , dim_z :] = dt * np . eye ( dim_z ) # Measurement function: numpy.array(dim_z, dim_x) filter . H = np . eye ( dim_z , dim_x , ) # Measurement uncertainty (sensor noise): numpy.array(dim_z, dim_z) filter . R *= self . R # Process uncertainty: numpy.array(dim_x, dim_x) # Don't decrease it too much or trackers pay too little attention to detections filter . Q [ dim_z :, dim_z :] *= self . Q # Initial state: numpy.array(dim_x, 1) filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T filter . x [ dim_z :] = 0 # Estimation uncertainty: numpy.array(dim_x, dim_x) filter . P [ dim_z :, dim_z :] *= self . P return filter create_filter ( initial_detection ) # This method returns a new predictive filter instance with the current setup, to be used by each new TrackedObject that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters: Name Type Description Default initial_detection np . ndarray numpy array of shape (number of points per object, 2) , corresponding to the Detection.points of the tracked object being born, which shall be used as initial position estimation for it. required Returns: Type Description KalmanFilter The kalman filter Source code in norfair/filter.py 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 def create_filter ( self , initial_detection : np . ndarray ) -> KalmanFilter : \"\"\" This method returns a new predictive filter instance with the current setup, to be used by each new [`TrackedObject`][norfair.tracker.TrackedObject] that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters ---------- initial_detection : np.ndarray numpy array of shape `(number of points per object, 2)`, corresponding to the [`Detection.points`][norfair.tracker.Detection] of the tracked object being born, which shall be used as initial position estimation for it. Returns ------- KalmanFilter The kalman filter \"\"\" num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points dim_x = 2 * dim_z # We need to accommodate for velocities filter = KalmanFilter ( dim_x = dim_x , dim_z = dim_z ) # State transition matrix (models physics): numpy.array() filter . F = np . eye ( dim_x ) dt = 1 # At each step we update pos with v * dt filter . F [: dim_z , dim_z :] = dt * np . eye ( dim_z ) # Measurement function: numpy.array(dim_z, dim_x) filter . H = np . eye ( dim_z , dim_x , ) # Measurement uncertainty (sensor noise): numpy.array(dim_z, dim_z) filter . R *= self . R # Process uncertainty: numpy.array(dim_x, dim_x) # Don't decrease it too much or trackers pay too little attention to detections filter . Q [ dim_z :, dim_z :] *= self . Q # Initial state: numpy.array(dim_x, 1) filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T filter . x [ dim_z :] = 0 # Estimation uncertainty: numpy.array(dim_x, dim_x) filter . P [ dim_z :, dim_z :] *= self . P return filter OptimizedKalmanFilterFactory # Bases: FilterFactory Creates faster Filters than FilterPyKalmanFilterFactory . It allows the user to create Kalman Filter optimized for tracking and set its parameters. Parameters: Name Type Description Default R float , optional Multiplier for the sensor measurement noise matrix. 4.0 Q float , optional Multiplier for the process uncertainty. 0.1 pos_variance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables. 10 pos_vel_covariance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to the covariance between position and speed. 0 vel_variance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to velocity (not position) variables. 1 Source code in norfair/filter.py 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 class OptimizedKalmanFilterFactory ( FilterFactory ): \"\"\" Creates faster Filters than [`FilterPyKalmanFilterFactory`][norfair.filter.FilterPyKalmanFilterFactory]. It allows the user to create Kalman Filter optimized for tracking and set its parameters. Parameters ---------- R : float, optional Multiplier for the sensor measurement noise matrix. Q : float, optional Multiplier for the process uncertainty. pos_variance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables. pos_vel_covariance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to the covariance between position and speed. vel_variance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to velocity (not position) variables. \"\"\" def __init__ ( self , R : float = 4.0 , Q : float = 0.1 , pos_variance : float = 10 , pos_vel_covariance : float = 0 , vel_variance : float = 1 , ): self . R = R self . Q = Q # entrances P matrix of KF self . pos_variance = pos_variance self . pos_vel_covariance = pos_vel_covariance self . vel_variance = vel_variance def create_filter ( self , initial_detection : np . ndarray ): num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points # flattened positions dim_x = 2 * dim_z # We need to accommodate for velocities custom_filter = OptimizedKalmanFilter ( dim_x , dim_z , pos_variance = self . pos_variance , pos_vel_covariance = self . pos_vel_covariance , vel_variance = self . vel_variance , q = self . Q , r = self . R , ) custom_filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T return custom_filter","title":"Filter"},{"location":"reference/filter/#filter","text":"","title":"Filter"},{"location":"reference/filter/#norfair.filter.FilterPyKalmanFilterFactory","text":"Bases: FilterFactory This class can be used either to change some parameters of the KalmanFilter that the tracker uses, or to fully customize the predictive filter implementation to use (as long as the methods and properties are compatible). The former case only requires changing the default parameters upon tracker creation: tracker = Tracker(..., filter_factory=FilterPyKalmanFilterFactory(R=100)) , while the latter requires creating your own class extending FilterPyKalmanFilterFactory , and rewriting its create_filter method to return your own customized filter. Parameters: Name Type Description Default R float , optional Multiplier for the sensor measurement noise matrix, by default 4.0 4.0 Q float , optional Multiplier for the process uncertainty, by default 0.1 0.1 P float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables, by default 10.0 10.0","title":"FilterPyKalmanFilterFactory"},{"location":"reference/filter/#norfair.filter.FilterPyKalmanFilterFactory--see-also","text":"filterpy.KalmanFilter . Source code in norfair/filter.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 class FilterPyKalmanFilterFactory ( FilterFactory ): \"\"\" This class can be used either to change some parameters of the [KalmanFilter](https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html) that the tracker uses, or to fully customize the predictive filter implementation to use (as long as the methods and properties are compatible). The former case only requires changing the default parameters upon tracker creation: `tracker = Tracker(..., filter_factory=FilterPyKalmanFilterFactory(R=100))`, while the latter requires creating your own class extending `FilterPyKalmanFilterFactory`, and rewriting its `create_filter` method to return your own customized filter. Parameters ---------- R : float, optional Multiplier for the sensor measurement noise matrix, by default 4.0 Q : float, optional Multiplier for the process uncertainty, by default 0.1 P : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables, by default 10.0 See Also -------- [`filterpy.KalmanFilter`](https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html). \"\"\" def __init__ ( self , R : float = 4.0 , Q : float = 0.1 , P : float = 10.0 ): self . R = R self . Q = Q self . P = P def create_filter ( self , initial_detection : np . ndarray ) -> KalmanFilter : \"\"\" This method returns a new predictive filter instance with the current setup, to be used by each new [`TrackedObject`][norfair.tracker.TrackedObject] that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters ---------- initial_detection : np.ndarray numpy array of shape `(number of points per object, 2)`, corresponding to the [`Detection.points`][norfair.tracker.Detection] of the tracked object being born, which shall be used as initial position estimation for it. Returns ------- KalmanFilter The kalman filter \"\"\" num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points dim_x = 2 * dim_z # We need to accommodate for velocities filter = KalmanFilter ( dim_x = dim_x , dim_z = dim_z ) # State transition matrix (models physics): numpy.array() filter . F = np . eye ( dim_x ) dt = 1 # At each step we update pos with v * dt filter . F [: dim_z , dim_z :] = dt * np . eye ( dim_z ) # Measurement function: numpy.array(dim_z, dim_x) filter . H = np . eye ( dim_z , dim_x , ) # Measurement uncertainty (sensor noise): numpy.array(dim_z, dim_z) filter . R *= self . R # Process uncertainty: numpy.array(dim_x, dim_x) # Don't decrease it too much or trackers pay too little attention to detections filter . Q [ dim_z :, dim_z :] *= self . Q # Initial state: numpy.array(dim_x, 1) filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T filter . x [ dim_z :] = 0 # Estimation uncertainty: numpy.array(dim_x, dim_x) filter . P [ dim_z :, dim_z :] *= self . P return filter","title":"See Also"},{"location":"reference/filter/#norfair.filter.FilterPyKalmanFilterFactory.create_filter","text":"This method returns a new predictive filter instance with the current setup, to be used by each new TrackedObject that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters: Name Type Description Default initial_detection np . ndarray numpy array of shape (number of points per object, 2) , corresponding to the Detection.points of the tracked object being born, which shall be used as initial position estimation for it. required Returns: Type Description KalmanFilter The kalman filter Source code in norfair/filter.py 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 def create_filter ( self , initial_detection : np . ndarray ) -> KalmanFilter : \"\"\" This method returns a new predictive filter instance with the current setup, to be used by each new [`TrackedObject`][norfair.tracker.TrackedObject] that is created. This predictive filter will be used to estimate speed and future positions of the object, to better match the detections during its trajectory. Parameters ---------- initial_detection : np.ndarray numpy array of shape `(number of points per object, 2)`, corresponding to the [`Detection.points`][norfair.tracker.Detection] of the tracked object being born, which shall be used as initial position estimation for it. Returns ------- KalmanFilter The kalman filter \"\"\" num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points dim_x = 2 * dim_z # We need to accommodate for velocities filter = KalmanFilter ( dim_x = dim_x , dim_z = dim_z ) # State transition matrix (models physics): numpy.array() filter . F = np . eye ( dim_x ) dt = 1 # At each step we update pos with v * dt filter . F [: dim_z , dim_z :] = dt * np . eye ( dim_z ) # Measurement function: numpy.array(dim_z, dim_x) filter . H = np . eye ( dim_z , dim_x , ) # Measurement uncertainty (sensor noise): numpy.array(dim_z, dim_z) filter . R *= self . R # Process uncertainty: numpy.array(dim_x, dim_x) # Don't decrease it too much or trackers pay too little attention to detections filter . Q [ dim_z :, dim_z :] *= self . Q # Initial state: numpy.array(dim_x, 1) filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T filter . x [ dim_z :] = 0 # Estimation uncertainty: numpy.array(dim_x, dim_x) filter . P [ dim_z :, dim_z :] *= self . P return filter","title":"create_filter()"},{"location":"reference/filter/#norfair.filter.OptimizedKalmanFilterFactory","text":"Bases: FilterFactory Creates faster Filters than FilterPyKalmanFilterFactory . It allows the user to create Kalman Filter optimized for tracking and set its parameters. Parameters: Name Type Description Default R float , optional Multiplier for the sensor measurement noise matrix. 4.0 Q float , optional Multiplier for the process uncertainty. 0.1 pos_variance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables. 10 pos_vel_covariance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to the covariance between position and speed. 0 vel_variance float , optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to velocity (not position) variables. 1 Source code in norfair/filter.py 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 class OptimizedKalmanFilterFactory ( FilterFactory ): \"\"\" Creates faster Filters than [`FilterPyKalmanFilterFactory`][norfair.filter.FilterPyKalmanFilterFactory]. It allows the user to create Kalman Filter optimized for tracking and set its parameters. Parameters ---------- R : float, optional Multiplier for the sensor measurement noise matrix. Q : float, optional Multiplier for the process uncertainty. pos_variance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to position (not speed) variables. pos_vel_covariance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to the covariance between position and speed. vel_variance : float, optional Multiplier for the initial covariance matrix estimation, only in the entries that correspond to velocity (not position) variables. \"\"\" def __init__ ( self , R : float = 4.0 , Q : float = 0.1 , pos_variance : float = 10 , pos_vel_covariance : float = 0 , vel_variance : float = 1 , ): self . R = R self . Q = Q # entrances P matrix of KF self . pos_variance = pos_variance self . pos_vel_covariance = pos_vel_covariance self . vel_variance = vel_variance def create_filter ( self , initial_detection : np . ndarray ): num_points = initial_detection . shape [ 0 ] dim_points = initial_detection . shape [ 1 ] dim_z = dim_points * num_points # flattened positions dim_x = 2 * dim_z # We need to accommodate for velocities custom_filter = OptimizedKalmanFilter ( dim_x , dim_z , pos_variance = self . pos_variance , pos_vel_covariance = self . pos_vel_covariance , vel_variance = self . vel_variance , q = self . Q , r = self . R , ) custom_filter . x [: dim_z ] = np . expand_dims ( initial_detection . flatten (), 0 ) . T return custom_filter","title":"OptimizedKalmanFilterFactory"},{"location":"reference/metrics/","text":"Metrics # PredictionsTextFile # Generates a text file with your predicted tracked objects, in the MOTChallenge format. It needs the 'input_path', which is the path to the sequence being processed, the 'save_path', and optionally the 'information_file' (in case you don't give an 'information_file', is assumed there is one in the input_path folder). Source code in norfair/metrics.py 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 class PredictionsTextFile : \"\"\"Generates a text file with your predicted tracked objects, in the MOTChallenge format. It needs the 'input_path', which is the path to the sequence being processed, the 'save_path', and optionally the 'information_file' (in case you don't give an 'information_file', is assumed there is one in the input_path folder). \"\"\" def __init__ ( self , input_path , save_path = \".\" , information_file = None ): file_name = os . path . split ( input_path )[ 1 ] if information_file is None : seqinfo_path = os . path . join ( input_path , \"seqinfo.ini\" ) information_file = InformationFile ( file_path = seqinfo_path ) self . length = information_file . search ( variable_name = \"seqLength\" ) predictions_folder = os . path . join ( save_path , \"predictions\" ) if not os . path . exists ( predictions_folder ): os . makedirs ( predictions_folder ) out_file_name = os . path . join ( predictions_folder , file_name + \".txt\" ) self . text_file = open ( out_file_name , \"w+\" ) self . frame_number = 1 def update ( self , predictions , frame_number = None ): if frame_number is None : frame_number = self . frame_number \"\"\" Write tracked object information in the output file (for this frame), in the format frame_number, id, bb_left, bb_top, bb_width, bb_height, -1, -1, -1, -1 \"\"\" for obj in predictions : frame_str = str ( int ( frame_number )) id_str = str ( int ( obj . id )) bb_left_str = str (( obj . estimate [ 0 , 0 ])) bb_top_str = str (( obj . estimate [ 0 , 1 ])) # [0,1] bb_width_str = str (( obj . estimate [ 1 , 0 ] - obj . estimate [ 0 , 0 ])) bb_height_str = str (( obj . estimate [ 1 , 1 ] - obj . estimate [ 0 , 1 ])) row_text_out = ( frame_str + \",\" + id_str + \",\" + bb_left_str + \",\" + bb_top_str + \",\" + bb_width_str + \",\" + bb_height_str + \",-1,-1,-1,-1\" ) self . text_file . write ( row_text_out ) self . text_file . write ( \" \\n \" ) self . frame_number += 1 if self . frame_number > self . length : self . text_file . close () DetectionFileParser # Get Norfair detections from MOTChallenge text files containing detections Source code in norfair/metrics.py 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 class DetectionFileParser : \"\"\"Get Norfair detections from MOTChallenge text files containing detections\"\"\" def __init__ ( self , input_path , information_file = None ): self . frame_number = 1 # Get detecions matrix data with rows corresponding to: # frame, id, bb_left, bb_top, bb_right, bb_down, conf, x, y, z detections_path = os . path . join ( input_path , \"det/det.txt\" ) self . matrix_detections = np . loadtxt ( detections_path , dtype = \"f\" , delimiter = \",\" ) row_order = np . argsort ( self . matrix_detections [:, 0 ]) self . matrix_detections = self . matrix_detections [ row_order ] # Coordinates refer to box corners self . matrix_detections [:, 4 ] = ( self . matrix_detections [:, 2 ] + self . matrix_detections [:, 4 ] ) self . matrix_detections [:, 5 ] = ( self . matrix_detections [:, 3 ] + self . matrix_detections [:, 5 ] ) if information_file is None : seqinfo_path = os . path . join ( input_path , \"seqinfo.ini\" ) information_file = InformationFile ( file_path = seqinfo_path ) self . length = information_file . search ( variable_name = \"seqLength\" ) self . sorted_by_frame = [] for frame_number in range ( 1 , self . length + 1 ): self . sorted_by_frame . append ( self . get_dets_from_frame ( frame_number )) def get_dets_from_frame ( self , frame_number ): \"\"\"this function returns a list of norfair Detections class, corresponding to frame=frame_number\"\"\" indexes = np . argwhere ( self . matrix_detections [:, 0 ] == frame_number ) detections = [] if len ( indexes ) > 0 : actual_det = self . matrix_detections [ indexes ] actual_det . shape = [ actual_det . shape [ 0 ], actual_det . shape [ 2 ]] for det in actual_det : points = np . array ([[ det [ 2 ], det [ 3 ]], [ det [ 4 ], det [ 5 ]]]) conf = det [ 6 ] new_detection = Detection ( points , np . array ([ conf , conf ])) detections . append ( new_detection ) self . actual_detections = detections return detections def __iter__ ( self ): self . frame_number = 1 return self def __next__ ( self ): if self . frame_number <= self . length : self . frame_number += 1 # Frame_number is always 1 unit bigger than the corresponding index in self.sorted_by_frame, and # also we just incremented the frame_number, so now is 2 units bigger than the corresponding index return self . sorted_by_frame [ self . frame_number - 2 ] raise StopIteration () get_dets_from_frame ( frame_number ) # this function returns a list of norfair Detections class, corresponding to frame=frame_number Source code in norfair/metrics.py 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 def get_dets_from_frame ( self , frame_number ): \"\"\"this function returns a list of norfair Detections class, corresponding to frame=frame_number\"\"\" indexes = np . argwhere ( self . matrix_detections [:, 0 ] == frame_number ) detections = [] if len ( indexes ) > 0 : actual_det = self . matrix_detections [ indexes ] actual_det . shape = [ actual_det . shape [ 0 ], actual_det . shape [ 2 ]] for det in actual_det : points = np . array ([[ det [ 2 ], det [ 3 ]], [ det [ 4 ], det [ 5 ]]]) conf = det [ 6 ] new_detection = Detection ( points , np . array ([ conf , conf ])) detections . append ( new_detection ) self . actual_detections = detections return detections load_motchallenge ( matrix_data , min_confidence =- 1 ) # Load MOT challenge data. This is a modification of the function load_motchallenge from the py-motmetrics library, defined in io.py In this version, the pandas dataframe is generated from a numpy array (matrix_data) instead of a text file. Params # matrix_data : array of float that has [frame, id, X, Y, width, height, conf, cassId, visibility] in each row, for each prediction on a particular video min_confidence : float Rows with confidence less than this threshold are removed. Defaults to -1. You should set this to 1 when loading ground truth MOTChallenge data, so that invalid rectangles in the ground truth are not considered during matching. Returns: Name Type Description df pandas . DataFrame The returned dataframe has the following columns 'X', 'Y', 'Width', 'Height', 'Confidence', 'ClassId', 'Visibility' The dataframe is indexed by ('FrameId', 'Id') Source code in norfair/metrics.py 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 def load_motchallenge ( matrix_data , min_confidence =- 1 ): \"\"\"Load MOT challenge data. This is a modification of the function load_motchallenge from the py-motmetrics library, defined in io.py In this version, the pandas dataframe is generated from a numpy array (matrix_data) instead of a text file. Params ------ matrix_data : array of float that has [frame, id, X, Y, width, height, conf, cassId, visibility] in each row, for each prediction on a particular video min_confidence : float Rows with confidence less than this threshold are removed. Defaults to -1. You should set this to 1 when loading ground truth MOTChallenge data, so that invalid rectangles in the ground truth are not considered during matching. Returns ------ df : pandas.DataFrame The returned dataframe has the following columns 'X', 'Y', 'Width', 'Height', 'Confidence', 'ClassId', 'Visibility' The dataframe is indexed by ('FrameId', 'Id') \"\"\" df = pd . DataFrame ( data = matrix_data , columns = [ \"FrameId\" , \"Id\" , \"X\" , \"Y\" , \"Width\" , \"Height\" , \"Confidence\" , \"ClassId\" , \"Visibility\" , \"unused\" , ], ) df = df . set_index ([ \"FrameId\" , \"Id\" ]) # Account for matlab convention. df [[ \"X\" , \"Y\" ]] -= ( 1 , 1 ) # Removed trailing column del df [ \"unused\" ] # Remove all rows without sufficient confidence return df [ df [ \"Confidence\" ] >= min_confidence ] compare_dataframes ( gts , ts ) # Builds accumulator for each sequence. Source code in norfair/metrics.py 297 298 299 300 301 302 303 304 305 306 307 308 309 def compare_dataframes ( gts , ts ): \"\"\"Builds accumulator for each sequence.\"\"\" accs = [] names = [] for k , tsacc in ts . items (): print ( \"Comparing \" , k , \"...\" ) if k in gts : accs . append ( mm . utils . compare_to_groundtruth ( gts [ k ], tsacc , \"iou\" , distth = 0.5 ) ) names . append ( k ) return accs , names","title":"Metrics"},{"location":"reference/metrics/#metrics","text":"","title":"Metrics"},{"location":"reference/metrics/#norfair.metrics.PredictionsTextFile","text":"Generates a text file with your predicted tracked objects, in the MOTChallenge format. It needs the 'input_path', which is the path to the sequence being processed, the 'save_path', and optionally the 'information_file' (in case you don't give an 'information_file', is assumed there is one in the input_path folder). Source code in norfair/metrics.py 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 class PredictionsTextFile : \"\"\"Generates a text file with your predicted tracked objects, in the MOTChallenge format. It needs the 'input_path', which is the path to the sequence being processed, the 'save_path', and optionally the 'information_file' (in case you don't give an 'information_file', is assumed there is one in the input_path folder). \"\"\" def __init__ ( self , input_path , save_path = \".\" , information_file = None ): file_name = os . path . split ( input_path )[ 1 ] if information_file is None : seqinfo_path = os . path . join ( input_path , \"seqinfo.ini\" ) information_file = InformationFile ( file_path = seqinfo_path ) self . length = information_file . search ( variable_name = \"seqLength\" ) predictions_folder = os . path . join ( save_path , \"predictions\" ) if not os . path . exists ( predictions_folder ): os . makedirs ( predictions_folder ) out_file_name = os . path . join ( predictions_folder , file_name + \".txt\" ) self . text_file = open ( out_file_name , \"w+\" ) self . frame_number = 1 def update ( self , predictions , frame_number = None ): if frame_number is None : frame_number = self . frame_number \"\"\" Write tracked object information in the output file (for this frame), in the format frame_number, id, bb_left, bb_top, bb_width, bb_height, -1, -1, -1, -1 \"\"\" for obj in predictions : frame_str = str ( int ( frame_number )) id_str = str ( int ( obj . id )) bb_left_str = str (( obj . estimate [ 0 , 0 ])) bb_top_str = str (( obj . estimate [ 0 , 1 ])) # [0,1] bb_width_str = str (( obj . estimate [ 1 , 0 ] - obj . estimate [ 0 , 0 ])) bb_height_str = str (( obj . estimate [ 1 , 1 ] - obj . estimate [ 0 , 1 ])) row_text_out = ( frame_str + \",\" + id_str + \",\" + bb_left_str + \",\" + bb_top_str + \",\" + bb_width_str + \",\" + bb_height_str + \",-1,-1,-1,-1\" ) self . text_file . write ( row_text_out ) self . text_file . write ( \" \\n \" ) self . frame_number += 1 if self . frame_number > self . length : self . text_file . close ()","title":"PredictionsTextFile"},{"location":"reference/metrics/#norfair.metrics.DetectionFileParser","text":"Get Norfair detections from MOTChallenge text files containing detections Source code in norfair/metrics.py 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 class DetectionFileParser : \"\"\"Get Norfair detections from MOTChallenge text files containing detections\"\"\" def __init__ ( self , input_path , information_file = None ): self . frame_number = 1 # Get detecions matrix data with rows corresponding to: # frame, id, bb_left, bb_top, bb_right, bb_down, conf, x, y, z detections_path = os . path . join ( input_path , \"det/det.txt\" ) self . matrix_detections = np . loadtxt ( detections_path , dtype = \"f\" , delimiter = \",\" ) row_order = np . argsort ( self . matrix_detections [:, 0 ]) self . matrix_detections = self . matrix_detections [ row_order ] # Coordinates refer to box corners self . matrix_detections [:, 4 ] = ( self . matrix_detections [:, 2 ] + self . matrix_detections [:, 4 ] ) self . matrix_detections [:, 5 ] = ( self . matrix_detections [:, 3 ] + self . matrix_detections [:, 5 ] ) if information_file is None : seqinfo_path = os . path . join ( input_path , \"seqinfo.ini\" ) information_file = InformationFile ( file_path = seqinfo_path ) self . length = information_file . search ( variable_name = \"seqLength\" ) self . sorted_by_frame = [] for frame_number in range ( 1 , self . length + 1 ): self . sorted_by_frame . append ( self . get_dets_from_frame ( frame_number )) def get_dets_from_frame ( self , frame_number ): \"\"\"this function returns a list of norfair Detections class, corresponding to frame=frame_number\"\"\" indexes = np . argwhere ( self . matrix_detections [:, 0 ] == frame_number ) detections = [] if len ( indexes ) > 0 : actual_det = self . matrix_detections [ indexes ] actual_det . shape = [ actual_det . shape [ 0 ], actual_det . shape [ 2 ]] for det in actual_det : points = np . array ([[ det [ 2 ], det [ 3 ]], [ det [ 4 ], det [ 5 ]]]) conf = det [ 6 ] new_detection = Detection ( points , np . array ([ conf , conf ])) detections . append ( new_detection ) self . actual_detections = detections return detections def __iter__ ( self ): self . frame_number = 1 return self def __next__ ( self ): if self . frame_number <= self . length : self . frame_number += 1 # Frame_number is always 1 unit bigger than the corresponding index in self.sorted_by_frame, and # also we just incremented the frame_number, so now is 2 units bigger than the corresponding index return self . sorted_by_frame [ self . frame_number - 2 ] raise StopIteration ()","title":"DetectionFileParser"},{"location":"reference/metrics/#norfair.metrics.DetectionFileParser.get_dets_from_frame","text":"this function returns a list of norfair Detections class, corresponding to frame=frame_number Source code in norfair/metrics.py 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 def get_dets_from_frame ( self , frame_number ): \"\"\"this function returns a list of norfair Detections class, corresponding to frame=frame_number\"\"\" indexes = np . argwhere ( self . matrix_detections [:, 0 ] == frame_number ) detections = [] if len ( indexes ) > 0 : actual_det = self . matrix_detections [ indexes ] actual_det . shape = [ actual_det . shape [ 0 ], actual_det . shape [ 2 ]] for det in actual_det : points = np . array ([[ det [ 2 ], det [ 3 ]], [ det [ 4 ], det [ 5 ]]]) conf = det [ 6 ] new_detection = Detection ( points , np . array ([ conf , conf ])) detections . append ( new_detection ) self . actual_detections = detections return detections","title":"get_dets_from_frame()"},{"location":"reference/metrics/#norfair.metrics.load_motchallenge","text":"Load MOT challenge data. This is a modification of the function load_motchallenge from the py-motmetrics library, defined in io.py In this version, the pandas dataframe is generated from a numpy array (matrix_data) instead of a text file.","title":"load_motchallenge()"},{"location":"reference/metrics/#norfair.metrics.load_motchallenge--params","text":"matrix_data : array of float that has [frame, id, X, Y, width, height, conf, cassId, visibility] in each row, for each prediction on a particular video min_confidence : float Rows with confidence less than this threshold are removed. Defaults to -1. You should set this to 1 when loading ground truth MOTChallenge data, so that invalid rectangles in the ground truth are not considered during matching. Returns: Name Type Description df pandas . DataFrame The returned dataframe has the following columns 'X', 'Y', 'Width', 'Height', 'Confidence', 'ClassId', 'Visibility' The dataframe is indexed by ('FrameId', 'Id') Source code in norfair/metrics.py 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 def load_motchallenge ( matrix_data , min_confidence =- 1 ): \"\"\"Load MOT challenge data. This is a modification of the function load_motchallenge from the py-motmetrics library, defined in io.py In this version, the pandas dataframe is generated from a numpy array (matrix_data) instead of a text file. Params ------ matrix_data : array of float that has [frame, id, X, Y, width, height, conf, cassId, visibility] in each row, for each prediction on a particular video min_confidence : float Rows with confidence less than this threshold are removed. Defaults to -1. You should set this to 1 when loading ground truth MOTChallenge data, so that invalid rectangles in the ground truth are not considered during matching. Returns ------ df : pandas.DataFrame The returned dataframe has the following columns 'X', 'Y', 'Width', 'Height', 'Confidence', 'ClassId', 'Visibility' The dataframe is indexed by ('FrameId', 'Id') \"\"\" df = pd . DataFrame ( data = matrix_data , columns = [ \"FrameId\" , \"Id\" , \"X\" , \"Y\" , \"Width\" , \"Height\" , \"Confidence\" , \"ClassId\" , \"Visibility\" , \"unused\" , ], ) df = df . set_index ([ \"FrameId\" , \"Id\" ]) # Account for matlab convention. df [[ \"X\" , \"Y\" ]] -= ( 1 , 1 ) # Removed trailing column del df [ \"unused\" ] # Remove all rows without sufficient confidence return df [ df [ \"Confidence\" ] >= min_confidence ]","title":"Params"},{"location":"reference/metrics/#norfair.metrics.compare_dataframes","text":"Builds accumulator for each sequence. Source code in norfair/metrics.py 297 298 299 300 301 302 303 304 305 306 307 308 309 def compare_dataframes ( gts , ts ): \"\"\"Builds accumulator for each sequence.\"\"\" accs = [] names = [] for k , tsacc in ts . items (): print ( \"Comparing \" , k , \"...\" ) if k in gts : accs . append ( mm . utils . compare_to_groundtruth ( gts [ k ], tsacc , \"iou\" , distth = 0.5 ) ) names . append ( k ) return accs , names","title":"compare_dataframes()"},{"location":"reference/tracker/","text":"Tracker # Tracker # The class in charge of performing the tracking of the detections produced by a detector. Parameters: Name Type Description Default distance_function Union [ str , Callable [[ Detection , TrackedObject ], float ]] Function used by the tracker to determine the distance between newly detected objects and the objects that are currently being tracked. This function should take 2 input arguments, the first being a Detection , and the second a TrackedObject . It has to return a float with the distance it calculates. Some common distances are implemented in distances , as a shortcut the tracker accepts the name of these predefined distances . Scipy's predefined distances are also accepted. A str with one of the available metrics in scipy.spatial.distance.cdist . required distance_threshold float Defines what is the maximum distance that can constitute a match. Detections and tracked objects whose distances are above this threshold won't be matched by the tracker. required hit_counter_max int , optional Each tracked objects keeps an internal hit counter which tracks how often it's getting matched to a detection, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. This argument defines how large this inertia can grow, and therefore defines how long an object can live without getting matched to any detections, before it is displaced as a dead object, if no ReID distance function is implemented it will be destroyed. 15 initialization_delay Optional [ int ], optional Determines how large the object's hit counter must be in order to be considered as initialized, and get returned to the user as a real object. It must be smaller than hit_counter_max or otherwise the object would never be initialized. If set to 0, objects will get returned to the user as soon as they are detected for the first time, which can be problematic as this can result in objects appearing and immediately dissapearing. Defaults to hit_counter_max / 2 None pointwise_hit_counter_max int , optional Each tracked object keeps track of how often the points it's tracking have been getting matched. Points that are getting matched ( pointwise_hit_counter > 0 ) are said to be live, and points which aren't ( pointwise_hit_counter = 0 ) are said to not be live. This is used to determine things like which individual points in a tracked object get drawn by draw_tracked_objects and which don't. This argument defines how large the inertia for each point of a tracker can grow. 4 detection_threshold float , optional Sets the threshold at which the scores of the points in a detection being fed into the tracker must dip below to be ignored by the tracker. 0 filter_factory FilterFactory , optional This parameter can be used to change what filter the TrackedObject instances created by the tracker will use. Defaults to OptimizedKalmanFilterFactory() OptimizedKalmanFilterFactory() past_detections_length int , optional How many past detections to save for each tracked object. Norfair tries to distribute these past detections uniformly through the object's lifetime so they're more representative. Very useful if you want to add metric learning to your model, as you can associate an embedding to each detection and access them in your distance function. 4 reid_distance_function Optional [ Callable [[ TrackedObject , TrackedObject ], float ]] Function used by the tracker to determine the ReID distance between newly detected trackers and unmatched trackers by the distance function. This function should take 2 input arguments, the first being tracked objects in the initialization phase of type TrackedObject , and the second being tracked objects that have been unmatched of type TrackedObject . It returns a float with the distance it calculates. None reid_distance_threshold float Defines what is the maximum ReID distance that can constitute a match. Tracked objects whose distance is above this threshold won't be merged, if they are the oldest tracked object will be maintained with the position of the new tracked object. 0 reid_hit_counter_max Optional [ int ] Each tracked object keeps an internal ReID hit counter which tracks how often it's getting recognized by another tracker, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. If used, this argument ( reid_hit_counter_max ) defines how long an object can live without getting matched to any detections, before it is destroyed. None Source code in norfair/tracker.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 class Tracker : \"\"\" The class in charge of performing the tracking of the detections produced by a detector. Parameters ---------- distance_function : Union[str, Callable[[Detection, TrackedObject], float]] Function used by the tracker to determine the distance between newly detected objects and the objects that are currently being tracked. This function should take 2 input arguments, the first being a [Detection][norfair.tracker.Detection], and the second a [TrackedObject][norfair.tracker.TrackedObject]. It has to return a `float` with the distance it calculates. Some common distances are implemented in [distances][], as a shortcut the tracker accepts the name of these [predefined distances][norfair.distances.get_distance_by_name]. Scipy's predefined distances are also accepted. A `str` with one of the available metrics in [`scipy.spatial.distance.cdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html). distance_threshold : float Defines what is the maximum distance that can constitute a match. Detections and tracked objects whose distances are above this threshold won't be matched by the tracker. hit_counter_max : int, optional Each tracked objects keeps an internal hit counter which tracks how often it's getting matched to a detection, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. This argument defines how large this inertia can grow, and therefore defines how long an object can live without getting matched to any detections, before it is displaced as a dead object, if no ReID distance function is implemented it will be destroyed. initialization_delay : Optional[int], optional Determines how large the object's hit counter must be in order to be considered as initialized, and get returned to the user as a real object. It must be smaller than `hit_counter_max` or otherwise the object would never be initialized. If set to 0, objects will get returned to the user as soon as they are detected for the first time, which can be problematic as this can result in objects appearing and immediately dissapearing. Defaults to `hit_counter_max / 2` pointwise_hit_counter_max : int, optional Each tracked object keeps track of how often the points it's tracking have been getting matched. Points that are getting matched (`pointwise_hit_counter > 0`) are said to be live, and points which aren't (`pointwise_hit_counter = 0`) are said to not be live. This is used to determine things like which individual points in a tracked object get drawn by [`draw_tracked_objects`][norfair.drawing.draw_tracked_objects] and which don't. This argument defines how large the inertia for each point of a tracker can grow. detection_threshold : float, optional Sets the threshold at which the scores of the points in a detection being fed into the tracker must dip below to be ignored by the tracker. filter_factory : FilterFactory, optional This parameter can be used to change what filter the [`TrackedObject`][norfair.tracker.TrackedObject] instances created by the tracker will use. Defaults to [`OptimizedKalmanFilterFactory()`][norfair.filter.OptimizedKalmanFilterFactory] past_detections_length : int, optional How many past detections to save for each tracked object. Norfair tries to distribute these past detections uniformly through the object's lifetime so they're more representative. Very useful if you want to add metric learning to your model, as you can associate an embedding to each detection and access them in your distance function. reid_distance_function: Optional[Callable[[\"TrackedObject\", \"TrackedObject\"], float]] Function used by the tracker to determine the ReID distance between newly detected trackers and unmatched trackers by the distance function. This function should take 2 input arguments, the first being tracked objects in the initialization phase of type [`TrackedObject`][norfair.tracker.TrackedObject], and the second being tracked objects that have been unmatched of type [`TrackedObject`][norfair.tracker.TrackedObject]. It returns a `float` with the distance it calculates. reid_distance_threshold: float Defines what is the maximum ReID distance that can constitute a match. Tracked objects whose distance is above this threshold won't be merged, if they are the oldest tracked object will be maintained with the position of the new tracked object. reid_hit_counter_max: Optional[int] Each tracked object keeps an internal ReID hit counter which tracks how often it's getting recognized by another tracker, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. If used, this argument (`reid_hit_counter_max`) defines how long an object can live without getting matched to any detections, before it is destroyed. \"\"\" def __init__ ( self , distance_function : Union [ str , Callable [[ \"Detection\" , \"TrackedObject\" ], float ]], distance_threshold : float , hit_counter_max : int = 15 , initialization_delay : Optional [ int ] = None , pointwise_hit_counter_max : int = 4 , detection_threshold : float = 0 , filter_factory : FilterFactory = OptimizedKalmanFilterFactory (), past_detections_length : int = 4 , reid_distance_function : Optional [ Callable [[ \"TrackedObject\" , \"TrackedObject\" ], float ] ] = None , reid_distance_threshold : float = 0 , reid_hit_counter_max : Optional [ int ] = None , ): self . tracked_objects : Sequence [ \"TrackedObject\" ] = [] if isinstance ( distance_function , str ): distance_function = get_distance_by_name ( distance_function ) elif isinstance ( distance_function , Callable ): warning ( \"You are using a scalar distance function. If you want to speed up the\" \" tracking process please consider using a vectorized distance\" f \" function such as { AVAILABLE_VECTORIZED_DISTANCES } .\" ) distance_function = ScalarDistance ( distance_function ) else : raise ValueError ( \"Argument `distance_function` should be a string or function but is\" f \" { type ( distance_function ) } instead.\" ) self . distance_function = distance_function self . hit_counter_max = hit_counter_max self . reid_hit_counter_max = reid_hit_counter_max self . pointwise_hit_counter_max = pointwise_hit_counter_max self . filter_factory = filter_factory if past_detections_length >= 0 : self . past_detections_length = past_detections_length else : raise ValueError ( f \"Argument `past_detections_length` is { past_detections_length } and should be larger than 0.\" ) if initialization_delay is None : self . initialization_delay = int ( self . hit_counter_max / 2 ) elif initialization_delay < 0 or initialization_delay >= self . hit_counter_max : raise ValueError ( f \"Argument 'initialization_delay' for 'Tracker' class should be an int between 0 and (hit_counter_max = { hit_counter_max } ). The selected value is { initialization_delay } . \\n \" ) else : self . initialization_delay = initialization_delay self . distance_threshold = distance_threshold self . detection_threshold = detection_threshold if reid_distance_function is not None : self . reid_distance_function = ScalarDistance ( reid_distance_function ) else : self . reid_distance_function = reid_distance_function self . reid_distance_threshold = reid_distance_threshold self . _obj_factory = _TrackedObjectFactory () def update ( self , detections : Optional [ List [ \"Detection\" ]] = None , period : int = 1 , coord_transformations : Optional [ CoordinatesTransformation ] = None , ) -> List [ \"TrackedObject\" ]: \"\"\" Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters ---------- detections : Optional[List[Detection]], optional A list of [`Detection`][norfair.tracker.Detection] which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. period : int, optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. coord_transformations: Optional[CoordinatesTransformation] The coordinate transformation calculated by the [MotionEstimator][norfair.camera_motion.MotionEstimator]. Returns ------- List[TrackedObject] The list of active tracked objects. \"\"\" if coord_transformations is not None : for det in detections : det . update_coordinate_transformation ( coord_transformations ) # Remove stale trackers and make candidate object real if the hit counter is positive alive_objects = [] dead_objects = [] if self . reid_hit_counter_max is None : self . tracked_objects = [ o for o in self . tracked_objects if o . hit_counter_is_positive ] alive_objects = self . tracked_objects else : tracked_objects = [] for o in self . tracked_objects : if o . reid_hit_counter_is_positive : tracked_objects . append ( o ) if o . hit_counter_is_positive : alive_objects . append ( o ) else : dead_objects . append ( o ) self . tracked_objects = tracked_objects # Update tracker for obj in self . tracked_objects : obj . tracker_step () obj . update_coordinate_transformation ( coord_transformations ) # Update initialized tracked objects with detections ( unmatched_detections , _ , unmatched_init_trackers , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if not o . is_initializing ], detections , period , ) # Update not yet initialized tracked objects with yet unmatched detections ( unmatched_detections , matched_not_init_trackers , _ , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if o . is_initializing ], unmatched_detections , period , ) if self . reid_distance_function is not None : # Match unmatched initialized tracked objects with not yet initialized tracked objects _ , _ , _ = self . _update_objects_in_place ( self . reid_distance_function , self . reid_distance_threshold , unmatched_init_trackers + dead_objects , matched_not_init_trackers , period , ) # Create new tracked objects from remaining unmatched detections for detection in unmatched_detections : self . tracked_objects . append ( self . _obj_factory . create ( initial_detection = detection , hit_counter_max = self . hit_counter_max , initialization_delay = self . initialization_delay , pointwise_hit_counter_max = self . pointwise_hit_counter_max , detection_threshold = self . detection_threshold , period = period , filter_factory = self . filter_factory , past_detections_length = self . past_detections_length , reid_hit_counter_max = self . reid_hit_counter_max , coord_transformations = coord_transformations , ) ) return self . get_active_objects () @property def current_object_count ( self ) -> int : \"\"\"Number of active TrackedObjects\"\"\" return len ( self . get_active_objects ()) @property def total_object_count ( self ) -> int : \"\"\"Total number of TrackedObjects initialized in the by this Tracker\"\"\" return self . _obj_factory . count def get_active_objects ( self ) -> List [ \"TrackedObject\" ]: \"\"\"Get the list of active objects Returns ------- List[\"TrackedObject\"] The list of active objects \"\"\" return [ o for o in self . tracked_objects if not o . is_initializing and o . hit_counter_is_positive ] def _update_objects_in_place ( self , distance_function , distance_threshold , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], period : int , ): if candidates is not None and len ( candidates ) > 0 : distance_matrix = distance_function . get_distances ( objects , candidates ) if np . isnan ( distance_matrix ) . any (): print ( \" \\n Received nan values from distance function, please check your distance function for errors!\" ) exit () # Used just for debugging distance function if distance_matrix . any (): for i , minimum in enumerate ( distance_matrix . min ( axis = 0 )): objects [ i ] . current_min_distance = ( minimum if minimum < distance_threshold else None ) matched_cand_indices , matched_obj_indices = self . match_dets_and_objs ( distance_matrix , distance_threshold ) if len ( matched_cand_indices ) > 0 : unmatched_candidates = [ d for i , d in enumerate ( candidates ) if i not in matched_cand_indices ] unmatched_objects = [ d for i , d in enumerate ( objects ) if i not in matched_obj_indices ] matched_objects = [] # Handle matched people/detections for ( match_cand_idx , match_obj_idx ) in zip ( matched_cand_indices , matched_obj_indices ): match_distance = distance_matrix [ match_cand_idx , match_obj_idx ] matched_candidate = candidates [ match_cand_idx ] matched_object = objects [ match_obj_idx ] if match_distance < distance_threshold : if isinstance ( matched_candidate , Detection ): matched_object . hit ( matched_candidate , period = period ) matched_object . last_distance = match_distance matched_objects . append ( matched_object ) elif isinstance ( matched_candidate , TrackedObject ): # Merge new TrackedObject with the old one matched_object . merge ( matched_candidate ) # If we are matching TrackedObject instances we want to get rid of the # already matched candidate to avoid matching it again in future frames self . tracked_objects . remove ( matched_candidate ) else : unmatched_candidates . append ( matched_candidate ) unmatched_objects . append ( matched_object ) else : unmatched_candidates , matched_objects , unmatched_objects = ( candidates , [], objects , ) else : unmatched_candidates , matched_objects , unmatched_objects = [], [], objects return unmatched_candidates , matched_objects , unmatched_objects def match_dets_and_objs ( self , distance_matrix : np . ndarray , distance_threshold ): \"\"\"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen \"\"\" # NOTE: This implementation is terribly inefficient, but it doesn't # seem to affect the fps at all. distance_matrix = distance_matrix . copy () if distance_matrix . size > 0 : det_idxs = [] obj_idxs = [] current_min = distance_matrix . min () while current_min < distance_threshold : flattened_arg_min = distance_matrix . argmin () det_idx = flattened_arg_min // distance_matrix . shape [ 1 ] obj_idx = flattened_arg_min % distance_matrix . shape [ 1 ] det_idxs . append ( det_idx ) obj_idxs . append ( obj_idx ) distance_matrix [ det_idx , :] = distance_threshold + 1 distance_matrix [:, obj_idx ] = distance_threshold + 1 current_min = distance_matrix . min () return det_idxs , obj_idxs else : return [], [] current_object_count : int property # Number of active TrackedObjects total_object_count : int property # Total number of TrackedObjects initialized in the by this Tracker update ( detections = None , period = 1 , coord_transformations = None ) # Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters: Name Type Description Default detections Optional [ List [ Detection ]], optional A list of Detection which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. None period int , optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. 1 coord_transformations Optional [ CoordinatesTransformation ] The coordinate transformation calculated by the MotionEstimator . None Returns: Type Description List [ TrackedObject ] The list of active tracked objects. Source code in norfair/tracker.py 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 def update ( self , detections : Optional [ List [ \"Detection\" ]] = None , period : int = 1 , coord_transformations : Optional [ CoordinatesTransformation ] = None , ) -> List [ \"TrackedObject\" ]: \"\"\" Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters ---------- detections : Optional[List[Detection]], optional A list of [`Detection`][norfair.tracker.Detection] which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. period : int, optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. coord_transformations: Optional[CoordinatesTransformation] The coordinate transformation calculated by the [MotionEstimator][norfair.camera_motion.MotionEstimator]. Returns ------- List[TrackedObject] The list of active tracked objects. \"\"\" if coord_transformations is not None : for det in detections : det . update_coordinate_transformation ( coord_transformations ) # Remove stale trackers and make candidate object real if the hit counter is positive alive_objects = [] dead_objects = [] if self . reid_hit_counter_max is None : self . tracked_objects = [ o for o in self . tracked_objects if o . hit_counter_is_positive ] alive_objects = self . tracked_objects else : tracked_objects = [] for o in self . tracked_objects : if o . reid_hit_counter_is_positive : tracked_objects . append ( o ) if o . hit_counter_is_positive : alive_objects . append ( o ) else : dead_objects . append ( o ) self . tracked_objects = tracked_objects # Update tracker for obj in self . tracked_objects : obj . tracker_step () obj . update_coordinate_transformation ( coord_transformations ) # Update initialized tracked objects with detections ( unmatched_detections , _ , unmatched_init_trackers , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if not o . is_initializing ], detections , period , ) # Update not yet initialized tracked objects with yet unmatched detections ( unmatched_detections , matched_not_init_trackers , _ , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if o . is_initializing ], unmatched_detections , period , ) if self . reid_distance_function is not None : # Match unmatched initialized tracked objects with not yet initialized tracked objects _ , _ , _ = self . _update_objects_in_place ( self . reid_distance_function , self . reid_distance_threshold , unmatched_init_trackers + dead_objects , matched_not_init_trackers , period , ) # Create new tracked objects from remaining unmatched detections for detection in unmatched_detections : self . tracked_objects . append ( self . _obj_factory . create ( initial_detection = detection , hit_counter_max = self . hit_counter_max , initialization_delay = self . initialization_delay , pointwise_hit_counter_max = self . pointwise_hit_counter_max , detection_threshold = self . detection_threshold , period = period , filter_factory = self . filter_factory , past_detections_length = self . past_detections_length , reid_hit_counter_max = self . reid_hit_counter_max , coord_transformations = coord_transformations , ) ) return self . get_active_objects () get_active_objects () # Get the list of active objects Returns: Type Description List [ TrackedObject ] The list of active objects Source code in norfair/tracker.py 272 273 274 275 276 277 278 279 280 281 282 283 284 def get_active_objects ( self ) -> List [ \"TrackedObject\" ]: \"\"\"Get the list of active objects Returns ------- List[\"TrackedObject\"] The list of active objects \"\"\" return [ o for o in self . tracked_objects if not o . is_initializing and o . hit_counter_is_positive ] match_dets_and_objs ( distance_matrix , distance_threshold ) # Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen Source code in norfair/tracker.py 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 def match_dets_and_objs ( self , distance_matrix : np . ndarray , distance_threshold ): \"\"\"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen \"\"\" # NOTE: This implementation is terribly inefficient, but it doesn't # seem to affect the fps at all. distance_matrix = distance_matrix . copy () if distance_matrix . size > 0 : det_idxs = [] obj_idxs = [] current_min = distance_matrix . min () while current_min < distance_threshold : flattened_arg_min = distance_matrix . argmin () det_idx = flattened_arg_min // distance_matrix . shape [ 1 ] obj_idx = flattened_arg_min % distance_matrix . shape [ 1 ] det_idxs . append ( det_idx ) obj_idxs . append ( obj_idx ) distance_matrix [ det_idx , :] = distance_threshold + 1 distance_matrix [:, obj_idx ] = distance_threshold + 1 current_min = distance_matrix . min () return det_idxs , obj_idxs else : return [], [] TrackedObject # The objects returned by the tracker's update function on each iteration. They represent the objects currently being tracked by the tracker. Users should not instantiate TrackedObjects manually; the Tracker will be in charge of creating them. Attributes: Name Type Description estimate np . ndarray Where the tracker predicts the point will be in the current frame based on past detections. A numpy array with the same shape as the detections being fed to the tracker that produced it. id Optional [ int ] The unique identifier assigned to this object by the tracker. Set to None if the object is initializing. global_id Optional [ int ] The globally unique identifier assigned to this object. Set to None if the object is initializing last_detection Detection The last detection that matched with this tracked object. Useful if you are storing embeddings in your detections and want to do metric learning, or for debugging. last_distance Optional [ float ] The distance the tracker had with the last object it matched with. age int The age of this object measured in number of frames. live_points A boolean mask with shape (n_points,) . Points marked as True have recently been matched with detections. Points marked as False haven't and are to be considered stale, and should be ignored. Functions like draw_tracked_objects use this property to determine which points not to draw. initializing_id int On top of id , objects also have an initializing_id which is the id they are given internally by the Tracker ; this id is used solely for debugging. Each new object created by the Tracker starts as an uninitialized TrackedObject , which needs to reach a certain match rate to be converted into a full blown TrackedObject . initializing_id is the id temporarily assigned to TrackedObject while they are getting initialized. Source code in norfair/tracker.py 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 class TrackedObject : \"\"\" The objects returned by the tracker's `update` function on each iteration. They represent the objects currently being tracked by the tracker. Users should not instantiate TrackedObjects manually; the Tracker will be in charge of creating them. Attributes ---------- estimate : np.ndarray Where the tracker predicts the point will be in the current frame based on past detections. A numpy array with the same shape as the detections being fed to the tracker that produced it. id : Optional[int] The unique identifier assigned to this object by the tracker. Set to `None` if the object is initializing. global_id : Optional[int] The globally unique identifier assigned to this object. Set to `None` if the object is initializing last_detection : Detection The last detection that matched with this tracked object. Useful if you are storing embeddings in your detections and want to do metric learning, or for debugging. last_distance : Optional[float] The distance the tracker had with the last object it matched with. age : int The age of this object measured in number of frames. live_points : A boolean mask with shape `(n_points,)`. Points marked as `True` have recently been matched with detections. Points marked as `False` haven't and are to be considered stale, and should be ignored. Functions like [`draw_tracked_objects`][norfair.drawing.draw_tracked_objects] use this property to determine which points not to draw. initializing_id : int On top of `id`, objects also have an `initializing_id` which is the id they are given internally by the `Tracker`; this id is used solely for debugging. Each new object created by the `Tracker` starts as an uninitialized `TrackedObject`, which needs to reach a certain match rate to be converted into a full blown `TrackedObject`. `initializing_id` is the id temporarily assigned to `TrackedObject` while they are getting initialized. \"\"\" def __init__ ( self , obj_factory : _TrackedObjectFactory , initial_detection : \"Detection\" , hit_counter_max : int , initialization_delay : int , pointwise_hit_counter_max : int , detection_threshold : float , period : int , filter_factory : \"FilterFactory\" , past_detections_length : int , reid_hit_counter_max : Optional [ int ], coord_transformations : Optional [ CoordinatesTransformation ] = None , ): if not isinstance ( initial_detection , Detection ): print ( f \" \\n [red]ERROR[/red]: The detection list fed into `tracker.update()` should be composed of { Detection } objects not { type ( initial_detection ) } . \\n \" ) exit () self . _obj_factory = obj_factory self . dim_points = initial_detection . absolute_points . shape [ 1 ] self . num_points = initial_detection . absolute_points . shape [ 0 ] self . hit_counter_max : int = hit_counter_max self . pointwise_hit_counter_max : int = max ( pointwise_hit_counter_max , period ) self . initialization_delay = initialization_delay self . detection_threshold : float = detection_threshold self . initial_period : int = period self . hit_counter : int = period self . reid_hit_counter_max = reid_hit_counter_max self . reid_hit_counter : Optional [ int ] = None self . last_distance : Optional [ float ] = None self . current_min_distance : Optional [ float ] = None self . last_detection : \"Detection\" = initial_detection self . age : int = 0 self . is_initializing : bool = self . hit_counter <= self . initialization_delay self . initializing_id : Optional [ int ] = self . _obj_factory . get_initializing_id () self . id : Optional [ int ] = None self . global_id : Optional [ int ] = None if not self . is_initializing : self . _acquire_ids () if initial_detection . scores is None : self . detected_at_least_once_points = np . array ([ True ] * self . num_points ) else : self . detected_at_least_once_points = ( initial_detection . scores > self . detection_threshold ) self . point_hit_counter : np . ndarray = self . detected_at_least_once_points . astype ( int ) initial_detection . age = self . age self . past_detections_length = past_detections_length if past_detections_length > 0 : self . past_detections : Sequence [ \"Detection\" ] = [ initial_detection ] else : self . past_detections : Sequence [ \"Detection\" ] = [] # Create Kalman Filter self . filter = filter_factory . create_filter ( initial_detection . absolute_points ) self . dim_z = self . dim_points * self . num_points self . label = initial_detection . label self . abs_to_rel = None if coord_transformations is not None : self . update_coordinate_transformation ( coord_transformations ) def tracker_step ( self ): if self . reid_hit_counter is None : if self . hit_counter <= 0 : self . reid_hit_counter = self . reid_hit_counter_max else : self . reid_hit_counter -= 1 self . hit_counter -= 1 self . point_hit_counter -= 1 self . age += 1 # Advances the tracker's state self . filter . predict () @property def hit_counter_is_positive ( self ): return self . hit_counter >= 0 @property def reid_hit_counter_is_positive ( self ): return self . reid_hit_counter is None or self . reid_hit_counter >= 0 @property def estimate_velocity ( self ) -> np . ndarray : \"\"\"Get the velocity estimate of the object from the Kalman filter. This velocity is in the absolute coordinate system. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the velocity estimate of the object on each axis. \"\"\" return self . filter . x . T . flatten ()[ self . dim_z :] . reshape ( - 1 , self . dim_points ) @property def estimate ( self ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. \"\"\" return self . get_estimate () def get_estimate ( self , absolute = False ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters ---------- absolute : bool, optional If true the coordinates are returned in absolute format, by default False, by default False. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises ------ ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. \"\"\" positions = self . filter . x . T . flatten ()[: self . dim_z ] . reshape ( - 1 , self . dim_points ) if self . abs_to_rel is None : if not absolute : return positions else : raise ValueError ( \"You must provide 'coord_transformations' to the tracker to get absolute coordinates\" ) else : if absolute : return positions else : return self . abs_to_rel ( positions ) @property def live_points ( self ): return self . point_hit_counter > 0 def hit ( self , detection : \"Detection\" , period : int = 1 ): \"\"\"Update tracked object with a new detection Parameters ---------- detection : Detection the new detection matched to this tracked object period : int, optional frames corresponding to the period of time since last update. \"\"\" self . _conditionally_add_to_past_detections ( detection ) self . last_detection = detection self . hit_counter = min ( self . hit_counter + 2 * period , self . hit_counter_max ) if self . is_initializing and self . hit_counter > self . initialization_delay : self . is_initializing = False self . _acquire_ids () # We use a kalman filter in which we consider each coordinate on each point as a sensor. # This is a hacky way to update only certain sensors (only x, y coordinates for # points which were detected). # TODO: Use keypoint confidence information to change R on each sensor instead? if detection . scores is not None : assert len ( detection . scores . shape ) == 1 points_over_threshold_mask = detection . scores > self . detection_threshold matched_sensors_mask = np . array ( [( m ,) * self . dim_points for m in points_over_threshold_mask ] ) . flatten () H_pos = np . diag ( matched_sensors_mask ) . astype ( float ) # We measure x, y positions self . point_hit_counter [ points_over_threshold_mask ] += 2 * period else : points_over_threshold_mask = np . array ([ True ] * self . num_points ) H_pos = np . identity ( self . num_points * self . dim_points ) self . point_hit_counter += 2 * period self . point_hit_counter [ self . point_hit_counter >= self . pointwise_hit_counter_max ] = self . pointwise_hit_counter_max self . point_hit_counter [ self . point_hit_counter < 0 ] = 0 H_vel = np . zeros ( H_pos . shape ) # But we don't directly measure velocity H = np . hstack ([ H_pos , H_vel ]) self . filter . update ( np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T , None , H ) detected_at_least_once_mask = np . array ( [( m ,) * self . dim_points for m in self . detected_at_least_once_points ] ) . flatten () now_detected_mask = np . hstack ( ( points_over_threshold_mask ,) * self . dim_points ) . flatten () first_detection_mask = np . logical_and ( now_detected_mask , np . logical_not ( detected_at_least_once_mask ) ) self . filter . x [: self . dim_z ][ first_detection_mask ] = np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T [ first_detection_mask ] # Force points being detected for the first time to have velocity = 0 # This is needed because some detectors (like OpenPose) set points with # low confidence to coordinates (0, 0). And when they then get their first # real detection this creates a huge velocity vector in our KalmanFilter # and causes the tracker to start with wildly inaccurate estimations which # eventually coverge to the real detections. self . filter . x [ self . dim_z :][ np . logical_not ( detected_at_least_once_mask )] = 0 self . detected_at_least_once_points = np . logical_or ( self . detected_at_least_once_points , points_over_threshold_mask ) def __repr__ ( self ): if self . last_distance is None : placeholder_text = \" \\033 [1mObject_ {} \\033 [0m(age: {} , hit_counter: {} , last_distance: {} , init_id: {} )\" else : placeholder_text = \" \\033 [1mObject_ {} \\033 [0m(age: {} , hit_counter: {} , last_distance: {:.2f} , init_id: {} )\" return placeholder_text . format ( self . id , self . age , self . hit_counter , self . last_distance , self . initializing_id , ) def _conditionally_add_to_past_detections ( self , detection ): \"\"\"Adds detections into (and pops detections away) from `past_detections` It does so by keeping a fixed amount of past detections saved into each TrackedObject, while maintaining them distributed uniformly through the object's lifetime. \"\"\" if self . past_detections_length == 0 : return if len ( self . past_detections ) < self . past_detections_length : detection . age = self . age self . past_detections . append ( detection ) elif self . age >= self . past_detections [ 0 ] . age * self . past_detections_length : self . past_detections . pop ( 0 ) detection . age = self . age self . past_detections . append ( detection ) def merge ( self , tracked_object ): \"\"\"Merge with a not yet initialized TrackedObject instance\"\"\" self . reid_hit_counter = None self . hit_counter = self . initial_period * 2 self . point_hit_counter = tracked_object . point_hit_counter self . last_distance = tracked_object . last_distance self . current_min_distance = tracked_object . current_min_distance self . last_detection = tracked_object . last_detection self . detected_at_least_once_points = ( tracked_object . detected_at_least_once_points ) self . filter = tracked_object . filter for past_detection in tracked_object . past_detections : self . _conditionally_add_to_past_detections ( past_detection ) def update_coordinate_transformation ( self , coordinate_transformation : CoordinatesTransformation ): if coordinate_transformation is not None : self . abs_to_rel = coordinate_transformation . abs_to_rel def _acquire_ids ( self ): self . id , self . global_id = self . _obj_factory . get_ids () estimate_velocity : np . ndarray property # Get the velocity estimate of the object from the Kalman filter. This velocity is in the absolute coordinate system. Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the velocity estimate of the object on each axis. estimate : np . ndarray property # Get the position estimate of the object from the Kalman filter. Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. get_estimate ( absolute = False ) # Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters: Name Type Description Default absolute bool , optional If true the coordinates are returned in absolute format, by default False, by default False. False Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises: Type Description ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. Source code in norfair/tracker.py 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 def get_estimate ( self , absolute = False ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters ---------- absolute : bool, optional If true the coordinates are returned in absolute format, by default False, by default False. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises ------ ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. \"\"\" positions = self . filter . x . T . flatten ()[: self . dim_z ] . reshape ( - 1 , self . dim_points ) if self . abs_to_rel is None : if not absolute : return positions else : raise ValueError ( \"You must provide 'coord_transformations' to the tracker to get absolute coordinates\" ) else : if absolute : return positions else : return self . abs_to_rel ( positions ) hit ( detection , period = 1 ) # Update tracked object with a new detection Parameters: Name Type Description Default detection Detection the new detection matched to this tracked object required period int , optional frames corresponding to the period of time since last update. 1 Source code in norfair/tracker.py 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 def hit ( self , detection : \"Detection\" , period : int = 1 ): \"\"\"Update tracked object with a new detection Parameters ---------- detection : Detection the new detection matched to this tracked object period : int, optional frames corresponding to the period of time since last update. \"\"\" self . _conditionally_add_to_past_detections ( detection ) self . last_detection = detection self . hit_counter = min ( self . hit_counter + 2 * period , self . hit_counter_max ) if self . is_initializing and self . hit_counter > self . initialization_delay : self . is_initializing = False self . _acquire_ids () # We use a kalman filter in which we consider each coordinate on each point as a sensor. # This is a hacky way to update only certain sensors (only x, y coordinates for # points which were detected). # TODO: Use keypoint confidence information to change R on each sensor instead? if detection . scores is not None : assert len ( detection . scores . shape ) == 1 points_over_threshold_mask = detection . scores > self . detection_threshold matched_sensors_mask = np . array ( [( m ,) * self . dim_points for m in points_over_threshold_mask ] ) . flatten () H_pos = np . diag ( matched_sensors_mask ) . astype ( float ) # We measure x, y positions self . point_hit_counter [ points_over_threshold_mask ] += 2 * period else : points_over_threshold_mask = np . array ([ True ] * self . num_points ) H_pos = np . identity ( self . num_points * self . dim_points ) self . point_hit_counter += 2 * period self . point_hit_counter [ self . point_hit_counter >= self . pointwise_hit_counter_max ] = self . pointwise_hit_counter_max self . point_hit_counter [ self . point_hit_counter < 0 ] = 0 H_vel = np . zeros ( H_pos . shape ) # But we don't directly measure velocity H = np . hstack ([ H_pos , H_vel ]) self . filter . update ( np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T , None , H ) detected_at_least_once_mask = np . array ( [( m ,) * self . dim_points for m in self . detected_at_least_once_points ] ) . flatten () now_detected_mask = np . hstack ( ( points_over_threshold_mask ,) * self . dim_points ) . flatten () first_detection_mask = np . logical_and ( now_detected_mask , np . logical_not ( detected_at_least_once_mask ) ) self . filter . x [: self . dim_z ][ first_detection_mask ] = np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T [ first_detection_mask ] # Force points being detected for the first time to have velocity = 0 # This is needed because some detectors (like OpenPose) set points with # low confidence to coordinates (0, 0). And when they then get their first # real detection this creates a huge velocity vector in our KalmanFilter # and causes the tracker to start with wildly inaccurate estimations which # eventually coverge to the real detections. self . filter . x [ self . dim_z :][ np . logical_not ( detected_at_least_once_mask )] = 0 self . detected_at_least_once_points = np . logical_or ( self . detected_at_least_once_points , points_over_threshold_mask ) merge ( tracked_object ) # Merge with a not yet initialized TrackedObject instance Source code in norfair/tracker.py 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 def merge ( self , tracked_object ): \"\"\"Merge with a not yet initialized TrackedObject instance\"\"\" self . reid_hit_counter = None self . hit_counter = self . initial_period * 2 self . point_hit_counter = tracked_object . point_hit_counter self . last_distance = tracked_object . last_distance self . current_min_distance = tracked_object . current_min_distance self . last_detection = tracked_object . last_detection self . detected_at_least_once_points = ( tracked_object . detected_at_least_once_points ) self . filter = tracked_object . filter for past_detection in tracked_object . past_detections : self . _conditionally_add_to_past_detections ( past_detection ) Detection # Detections returned by the detector must be converted to a Detection object before being used by Norfair. Parameters: Name Type Description Default points np . ndarray Points detected. Must be a rank 2 array with shape (n_points, n_dimensions) where n_dimensions is 2 or 3. required scores np . ndarray , optional An array of length n_points which assigns a score to each of the points defined in points . This is used to inform the tracker of which points to ignore; any point with a score below detection_threshold will be ignored. This useful for cases in which detections don't always have every point present, as is often the case in pose estimators. None data Any , optional The place to store any extra data which may be useful when calculating the distance function. Anything stored here will be available to use inside the distance function. This enables the development of more interesting trackers which can do things like assign an appearance embedding to each detection to aid in its tracking. None label Hashable , optional When working with multiple classes the detection's label can be stored to be used as a matching condition when associating tracked objects with new detections. Label's type must be hashable for drawing purposes. None embedding Any , optional The embedding for the reid_distance. None Source code in norfair/tracker.py 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 class Detection : \"\"\"Detections returned by the detector must be converted to a `Detection` object before being used by Norfair. Parameters ---------- points : np.ndarray Points detected. Must be a rank 2 array with shape `(n_points, n_dimensions)` where n_dimensions is 2 or 3. scores : np.ndarray, optional An array of length `n_points` which assigns a score to each of the points defined in `points`. This is used to inform the tracker of which points to ignore; any point with a score below `detection_threshold` will be ignored. This useful for cases in which detections don't always have every point present, as is often the case in pose estimators. data : Any, optional The place to store any extra data which may be useful when calculating the distance function. Anything stored here will be available to use inside the distance function. This enables the development of more interesting trackers which can do things like assign an appearance embedding to each detection to aid in its tracking. label : Hashable, optional When working with multiple classes the detection's label can be stored to be used as a matching condition when associating tracked objects with new detections. Label's type must be hashable for drawing purposes. embedding : Any, optional The embedding for the reid_distance. \"\"\" def __init__ ( self , points : np . ndarray , scores : np . ndarray = None , data : Any = None , label : Hashable = None , embedding = None , ): self . points = validate_points ( points ) self . scores = scores self . data = data self . label = label self . absolute_points = self . points . copy () self . embedding = embedding self . age = None def update_coordinate_transformation ( self , coordinate_transformation : CoordinatesTransformation ): if coordinate_transformation is not None : self . absolute_points = coordinate_transformation . rel_to_abs ( self . absolute_points )","title":"Tracker"},{"location":"reference/tracker/#tracker","text":"","title":"Tracker"},{"location":"reference/tracker/#norfair.tracker.Tracker","text":"The class in charge of performing the tracking of the detections produced by a detector. Parameters: Name Type Description Default distance_function Union [ str , Callable [[ Detection , TrackedObject ], float ]] Function used by the tracker to determine the distance between newly detected objects and the objects that are currently being tracked. This function should take 2 input arguments, the first being a Detection , and the second a TrackedObject . It has to return a float with the distance it calculates. Some common distances are implemented in distances , as a shortcut the tracker accepts the name of these predefined distances . Scipy's predefined distances are also accepted. A str with one of the available metrics in scipy.spatial.distance.cdist . required distance_threshold float Defines what is the maximum distance that can constitute a match. Detections and tracked objects whose distances are above this threshold won't be matched by the tracker. required hit_counter_max int , optional Each tracked objects keeps an internal hit counter which tracks how often it's getting matched to a detection, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. This argument defines how large this inertia can grow, and therefore defines how long an object can live without getting matched to any detections, before it is displaced as a dead object, if no ReID distance function is implemented it will be destroyed. 15 initialization_delay Optional [ int ], optional Determines how large the object's hit counter must be in order to be considered as initialized, and get returned to the user as a real object. It must be smaller than hit_counter_max or otherwise the object would never be initialized. If set to 0, objects will get returned to the user as soon as they are detected for the first time, which can be problematic as this can result in objects appearing and immediately dissapearing. Defaults to hit_counter_max / 2 None pointwise_hit_counter_max int , optional Each tracked object keeps track of how often the points it's tracking have been getting matched. Points that are getting matched ( pointwise_hit_counter > 0 ) are said to be live, and points which aren't ( pointwise_hit_counter = 0 ) are said to not be live. This is used to determine things like which individual points in a tracked object get drawn by draw_tracked_objects and which don't. This argument defines how large the inertia for each point of a tracker can grow. 4 detection_threshold float , optional Sets the threshold at which the scores of the points in a detection being fed into the tracker must dip below to be ignored by the tracker. 0 filter_factory FilterFactory , optional This parameter can be used to change what filter the TrackedObject instances created by the tracker will use. Defaults to OptimizedKalmanFilterFactory() OptimizedKalmanFilterFactory() past_detections_length int , optional How many past detections to save for each tracked object. Norfair tries to distribute these past detections uniformly through the object's lifetime so they're more representative. Very useful if you want to add metric learning to your model, as you can associate an embedding to each detection and access them in your distance function. 4 reid_distance_function Optional [ Callable [[ TrackedObject , TrackedObject ], float ]] Function used by the tracker to determine the ReID distance between newly detected trackers and unmatched trackers by the distance function. This function should take 2 input arguments, the first being tracked objects in the initialization phase of type TrackedObject , and the second being tracked objects that have been unmatched of type TrackedObject . It returns a float with the distance it calculates. None reid_distance_threshold float Defines what is the maximum ReID distance that can constitute a match. Tracked objects whose distance is above this threshold won't be merged, if they are the oldest tracked object will be maintained with the position of the new tracked object. 0 reid_hit_counter_max Optional [ int ] Each tracked object keeps an internal ReID hit counter which tracks how often it's getting recognized by another tracker, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. If used, this argument ( reid_hit_counter_max ) defines how long an object can live without getting matched to any detections, before it is destroyed. None Source code in norfair/tracker.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 class Tracker : \"\"\" The class in charge of performing the tracking of the detections produced by a detector. Parameters ---------- distance_function : Union[str, Callable[[Detection, TrackedObject], float]] Function used by the tracker to determine the distance between newly detected objects and the objects that are currently being tracked. This function should take 2 input arguments, the first being a [Detection][norfair.tracker.Detection], and the second a [TrackedObject][norfair.tracker.TrackedObject]. It has to return a `float` with the distance it calculates. Some common distances are implemented in [distances][], as a shortcut the tracker accepts the name of these [predefined distances][norfair.distances.get_distance_by_name]. Scipy's predefined distances are also accepted. A `str` with one of the available metrics in [`scipy.spatial.distance.cdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html). distance_threshold : float Defines what is the maximum distance that can constitute a match. Detections and tracked objects whose distances are above this threshold won't be matched by the tracker. hit_counter_max : int, optional Each tracked objects keeps an internal hit counter which tracks how often it's getting matched to a detection, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. This argument defines how large this inertia can grow, and therefore defines how long an object can live without getting matched to any detections, before it is displaced as a dead object, if no ReID distance function is implemented it will be destroyed. initialization_delay : Optional[int], optional Determines how large the object's hit counter must be in order to be considered as initialized, and get returned to the user as a real object. It must be smaller than `hit_counter_max` or otherwise the object would never be initialized. If set to 0, objects will get returned to the user as soon as they are detected for the first time, which can be problematic as this can result in objects appearing and immediately dissapearing. Defaults to `hit_counter_max / 2` pointwise_hit_counter_max : int, optional Each tracked object keeps track of how often the points it's tracking have been getting matched. Points that are getting matched (`pointwise_hit_counter > 0`) are said to be live, and points which aren't (`pointwise_hit_counter = 0`) are said to not be live. This is used to determine things like which individual points in a tracked object get drawn by [`draw_tracked_objects`][norfair.drawing.draw_tracked_objects] and which don't. This argument defines how large the inertia for each point of a tracker can grow. detection_threshold : float, optional Sets the threshold at which the scores of the points in a detection being fed into the tracker must dip below to be ignored by the tracker. filter_factory : FilterFactory, optional This parameter can be used to change what filter the [`TrackedObject`][norfair.tracker.TrackedObject] instances created by the tracker will use. Defaults to [`OptimizedKalmanFilterFactory()`][norfair.filter.OptimizedKalmanFilterFactory] past_detections_length : int, optional How many past detections to save for each tracked object. Norfair tries to distribute these past detections uniformly through the object's lifetime so they're more representative. Very useful if you want to add metric learning to your model, as you can associate an embedding to each detection and access them in your distance function. reid_distance_function: Optional[Callable[[\"TrackedObject\", \"TrackedObject\"], float]] Function used by the tracker to determine the ReID distance between newly detected trackers and unmatched trackers by the distance function. This function should take 2 input arguments, the first being tracked objects in the initialization phase of type [`TrackedObject`][norfair.tracker.TrackedObject], and the second being tracked objects that have been unmatched of type [`TrackedObject`][norfair.tracker.TrackedObject]. It returns a `float` with the distance it calculates. reid_distance_threshold: float Defines what is the maximum ReID distance that can constitute a match. Tracked objects whose distance is above this threshold won't be merged, if they are the oldest tracked object will be maintained with the position of the new tracked object. reid_hit_counter_max: Optional[int] Each tracked object keeps an internal ReID hit counter which tracks how often it's getting recognized by another tracker, each time it gets a match this counter goes up, and each time it doesn't it goes down. If it goes below 0 the object gets destroyed. If used, this argument (`reid_hit_counter_max`) defines how long an object can live without getting matched to any detections, before it is destroyed. \"\"\" def __init__ ( self , distance_function : Union [ str , Callable [[ \"Detection\" , \"TrackedObject\" ], float ]], distance_threshold : float , hit_counter_max : int = 15 , initialization_delay : Optional [ int ] = None , pointwise_hit_counter_max : int = 4 , detection_threshold : float = 0 , filter_factory : FilterFactory = OptimizedKalmanFilterFactory (), past_detections_length : int = 4 , reid_distance_function : Optional [ Callable [[ \"TrackedObject\" , \"TrackedObject\" ], float ] ] = None , reid_distance_threshold : float = 0 , reid_hit_counter_max : Optional [ int ] = None , ): self . tracked_objects : Sequence [ \"TrackedObject\" ] = [] if isinstance ( distance_function , str ): distance_function = get_distance_by_name ( distance_function ) elif isinstance ( distance_function , Callable ): warning ( \"You are using a scalar distance function. If you want to speed up the\" \" tracking process please consider using a vectorized distance\" f \" function such as { AVAILABLE_VECTORIZED_DISTANCES } .\" ) distance_function = ScalarDistance ( distance_function ) else : raise ValueError ( \"Argument `distance_function` should be a string or function but is\" f \" { type ( distance_function ) } instead.\" ) self . distance_function = distance_function self . hit_counter_max = hit_counter_max self . reid_hit_counter_max = reid_hit_counter_max self . pointwise_hit_counter_max = pointwise_hit_counter_max self . filter_factory = filter_factory if past_detections_length >= 0 : self . past_detections_length = past_detections_length else : raise ValueError ( f \"Argument `past_detections_length` is { past_detections_length } and should be larger than 0.\" ) if initialization_delay is None : self . initialization_delay = int ( self . hit_counter_max / 2 ) elif initialization_delay < 0 or initialization_delay >= self . hit_counter_max : raise ValueError ( f \"Argument 'initialization_delay' for 'Tracker' class should be an int between 0 and (hit_counter_max = { hit_counter_max } ). The selected value is { initialization_delay } . \\n \" ) else : self . initialization_delay = initialization_delay self . distance_threshold = distance_threshold self . detection_threshold = detection_threshold if reid_distance_function is not None : self . reid_distance_function = ScalarDistance ( reid_distance_function ) else : self . reid_distance_function = reid_distance_function self . reid_distance_threshold = reid_distance_threshold self . _obj_factory = _TrackedObjectFactory () def update ( self , detections : Optional [ List [ \"Detection\" ]] = None , period : int = 1 , coord_transformations : Optional [ CoordinatesTransformation ] = None , ) -> List [ \"TrackedObject\" ]: \"\"\" Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters ---------- detections : Optional[List[Detection]], optional A list of [`Detection`][norfair.tracker.Detection] which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. period : int, optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. coord_transformations: Optional[CoordinatesTransformation] The coordinate transformation calculated by the [MotionEstimator][norfair.camera_motion.MotionEstimator]. Returns ------- List[TrackedObject] The list of active tracked objects. \"\"\" if coord_transformations is not None : for det in detections : det . update_coordinate_transformation ( coord_transformations ) # Remove stale trackers and make candidate object real if the hit counter is positive alive_objects = [] dead_objects = [] if self . reid_hit_counter_max is None : self . tracked_objects = [ o for o in self . tracked_objects if o . hit_counter_is_positive ] alive_objects = self . tracked_objects else : tracked_objects = [] for o in self . tracked_objects : if o . reid_hit_counter_is_positive : tracked_objects . append ( o ) if o . hit_counter_is_positive : alive_objects . append ( o ) else : dead_objects . append ( o ) self . tracked_objects = tracked_objects # Update tracker for obj in self . tracked_objects : obj . tracker_step () obj . update_coordinate_transformation ( coord_transformations ) # Update initialized tracked objects with detections ( unmatched_detections , _ , unmatched_init_trackers , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if not o . is_initializing ], detections , period , ) # Update not yet initialized tracked objects with yet unmatched detections ( unmatched_detections , matched_not_init_trackers , _ , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if o . is_initializing ], unmatched_detections , period , ) if self . reid_distance_function is not None : # Match unmatched initialized tracked objects with not yet initialized tracked objects _ , _ , _ = self . _update_objects_in_place ( self . reid_distance_function , self . reid_distance_threshold , unmatched_init_trackers + dead_objects , matched_not_init_trackers , period , ) # Create new tracked objects from remaining unmatched detections for detection in unmatched_detections : self . tracked_objects . append ( self . _obj_factory . create ( initial_detection = detection , hit_counter_max = self . hit_counter_max , initialization_delay = self . initialization_delay , pointwise_hit_counter_max = self . pointwise_hit_counter_max , detection_threshold = self . detection_threshold , period = period , filter_factory = self . filter_factory , past_detections_length = self . past_detections_length , reid_hit_counter_max = self . reid_hit_counter_max , coord_transformations = coord_transformations , ) ) return self . get_active_objects () @property def current_object_count ( self ) -> int : \"\"\"Number of active TrackedObjects\"\"\" return len ( self . get_active_objects ()) @property def total_object_count ( self ) -> int : \"\"\"Total number of TrackedObjects initialized in the by this Tracker\"\"\" return self . _obj_factory . count def get_active_objects ( self ) -> List [ \"TrackedObject\" ]: \"\"\"Get the list of active objects Returns ------- List[\"TrackedObject\"] The list of active objects \"\"\" return [ o for o in self . tracked_objects if not o . is_initializing and o . hit_counter_is_positive ] def _update_objects_in_place ( self , distance_function , distance_threshold , objects : Sequence [ \"TrackedObject\" ], candidates : Optional [ Union [ List [ \"Detection\" ], List [ \"TrackedObject\" ]]], period : int , ): if candidates is not None and len ( candidates ) > 0 : distance_matrix = distance_function . get_distances ( objects , candidates ) if np . isnan ( distance_matrix ) . any (): print ( \" \\n Received nan values from distance function, please check your distance function for errors!\" ) exit () # Used just for debugging distance function if distance_matrix . any (): for i , minimum in enumerate ( distance_matrix . min ( axis = 0 )): objects [ i ] . current_min_distance = ( minimum if minimum < distance_threshold else None ) matched_cand_indices , matched_obj_indices = self . match_dets_and_objs ( distance_matrix , distance_threshold ) if len ( matched_cand_indices ) > 0 : unmatched_candidates = [ d for i , d in enumerate ( candidates ) if i not in matched_cand_indices ] unmatched_objects = [ d for i , d in enumerate ( objects ) if i not in matched_obj_indices ] matched_objects = [] # Handle matched people/detections for ( match_cand_idx , match_obj_idx ) in zip ( matched_cand_indices , matched_obj_indices ): match_distance = distance_matrix [ match_cand_idx , match_obj_idx ] matched_candidate = candidates [ match_cand_idx ] matched_object = objects [ match_obj_idx ] if match_distance < distance_threshold : if isinstance ( matched_candidate , Detection ): matched_object . hit ( matched_candidate , period = period ) matched_object . last_distance = match_distance matched_objects . append ( matched_object ) elif isinstance ( matched_candidate , TrackedObject ): # Merge new TrackedObject with the old one matched_object . merge ( matched_candidate ) # If we are matching TrackedObject instances we want to get rid of the # already matched candidate to avoid matching it again in future frames self . tracked_objects . remove ( matched_candidate ) else : unmatched_candidates . append ( matched_candidate ) unmatched_objects . append ( matched_object ) else : unmatched_candidates , matched_objects , unmatched_objects = ( candidates , [], objects , ) else : unmatched_candidates , matched_objects , unmatched_objects = [], [], objects return unmatched_candidates , matched_objects , unmatched_objects def match_dets_and_objs ( self , distance_matrix : np . ndarray , distance_threshold ): \"\"\"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen \"\"\" # NOTE: This implementation is terribly inefficient, but it doesn't # seem to affect the fps at all. distance_matrix = distance_matrix . copy () if distance_matrix . size > 0 : det_idxs = [] obj_idxs = [] current_min = distance_matrix . min () while current_min < distance_threshold : flattened_arg_min = distance_matrix . argmin () det_idx = flattened_arg_min // distance_matrix . shape [ 1 ] obj_idx = flattened_arg_min % distance_matrix . shape [ 1 ] det_idxs . append ( det_idx ) obj_idxs . append ( obj_idx ) distance_matrix [ det_idx , :] = distance_threshold + 1 distance_matrix [:, obj_idx ] = distance_threshold + 1 current_min = distance_matrix . min () return det_idxs , obj_idxs else : return [], []","title":"Tracker"},{"location":"reference/tracker/#norfair.tracker.Tracker.current_object_count","text":"Number of active TrackedObjects","title":"current_object_count"},{"location":"reference/tracker/#norfair.tracker.Tracker.total_object_count","text":"Total number of TrackedObjects initialized in the by this Tracker","title":"total_object_count"},{"location":"reference/tracker/#norfair.tracker.Tracker.update","text":"Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters: Name Type Description Default detections Optional [ List [ Detection ]], optional A list of Detection which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. None period int , optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. 1 coord_transformations Optional [ CoordinatesTransformation ] The coordinate transformation calculated by the MotionEstimator . None Returns: Type Description List [ TrackedObject ] The list of active tracked objects. Source code in norfair/tracker.py 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 def update ( self , detections : Optional [ List [ \"Detection\" ]] = None , period : int = 1 , coord_transformations : Optional [ CoordinatesTransformation ] = None , ) -> List [ \"TrackedObject\" ]: \"\"\" Process detections found in each frame. The detections can be matched to previous tracked objects or new ones will be created according to the configuration of the Tracker. The currently alive and initialized tracked objects are returned Parameters ---------- detections : Optional[List[Detection]], optional A list of [`Detection`][norfair.tracker.Detection] which represent the detections found in the current frame being processed. If no detections have been found in the current frame, or the user is purposely skipping frames to improve video processing time, this argument should be set to None or ignored, as the update function is needed to advance the state of the Kalman Filters inside the tracker. period : int, optional The user can chose not to run their detector on all frames, so as to process video faster. This parameter sets every how many frames the detector is getting ran, so that the tracker is aware of this situation and can handle it properly. This argument can be reset on each frame processed, which is useful if the user is dynamically changing how many frames the detector is skipping on a video when working in real-time. coord_transformations: Optional[CoordinatesTransformation] The coordinate transformation calculated by the [MotionEstimator][norfair.camera_motion.MotionEstimator]. Returns ------- List[TrackedObject] The list of active tracked objects. \"\"\" if coord_transformations is not None : for det in detections : det . update_coordinate_transformation ( coord_transformations ) # Remove stale trackers and make candidate object real if the hit counter is positive alive_objects = [] dead_objects = [] if self . reid_hit_counter_max is None : self . tracked_objects = [ o for o in self . tracked_objects if o . hit_counter_is_positive ] alive_objects = self . tracked_objects else : tracked_objects = [] for o in self . tracked_objects : if o . reid_hit_counter_is_positive : tracked_objects . append ( o ) if o . hit_counter_is_positive : alive_objects . append ( o ) else : dead_objects . append ( o ) self . tracked_objects = tracked_objects # Update tracker for obj in self . tracked_objects : obj . tracker_step () obj . update_coordinate_transformation ( coord_transformations ) # Update initialized tracked objects with detections ( unmatched_detections , _ , unmatched_init_trackers , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if not o . is_initializing ], detections , period , ) # Update not yet initialized tracked objects with yet unmatched detections ( unmatched_detections , matched_not_init_trackers , _ , ) = self . _update_objects_in_place ( self . distance_function , self . distance_threshold , [ o for o in alive_objects if o . is_initializing ], unmatched_detections , period , ) if self . reid_distance_function is not None : # Match unmatched initialized tracked objects with not yet initialized tracked objects _ , _ , _ = self . _update_objects_in_place ( self . reid_distance_function , self . reid_distance_threshold , unmatched_init_trackers + dead_objects , matched_not_init_trackers , period , ) # Create new tracked objects from remaining unmatched detections for detection in unmatched_detections : self . tracked_objects . append ( self . _obj_factory . create ( initial_detection = detection , hit_counter_max = self . hit_counter_max , initialization_delay = self . initialization_delay , pointwise_hit_counter_max = self . pointwise_hit_counter_max , detection_threshold = self . detection_threshold , period = period , filter_factory = self . filter_factory , past_detections_length = self . past_detections_length , reid_hit_counter_max = self . reid_hit_counter_max , coord_transformations = coord_transformations , ) ) return self . get_active_objects ()","title":"update()"},{"location":"reference/tracker/#norfair.tracker.Tracker.get_active_objects","text":"Get the list of active objects Returns: Type Description List [ TrackedObject ] The list of active objects Source code in norfair/tracker.py 272 273 274 275 276 277 278 279 280 281 282 283 284 def get_active_objects ( self ) -> List [ \"TrackedObject\" ]: \"\"\"Get the list of active objects Returns ------- List[\"TrackedObject\"] The list of active objects \"\"\" return [ o for o in self . tracked_objects if not o . is_initializing and o . hit_counter_is_positive ]","title":"get_active_objects()"},{"location":"reference/tracker/#norfair.tracker.Tracker.match_dets_and_objs","text":"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen Source code in norfair/tracker.py 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 def match_dets_and_objs ( self , distance_matrix : np . ndarray , distance_threshold ): \"\"\"Matches detections with tracked_objects from a distance matrix I used to match by minimizing the global distances, but found several cases in which this was not optimal. So now I just match by starting with the global minimum distance and matching the det-obj corresponding to that distance, then taking the second minimum, and so on until we reach the distance_threshold. This avoids the the algorithm getting cute with us and matching things that shouldn't be matching just for the sake of minimizing the global distance, which is what used to happen \"\"\" # NOTE: This implementation is terribly inefficient, but it doesn't # seem to affect the fps at all. distance_matrix = distance_matrix . copy () if distance_matrix . size > 0 : det_idxs = [] obj_idxs = [] current_min = distance_matrix . min () while current_min < distance_threshold : flattened_arg_min = distance_matrix . argmin () det_idx = flattened_arg_min // distance_matrix . shape [ 1 ] obj_idx = flattened_arg_min % distance_matrix . shape [ 1 ] det_idxs . append ( det_idx ) obj_idxs . append ( obj_idx ) distance_matrix [ det_idx , :] = distance_threshold + 1 distance_matrix [:, obj_idx ] = distance_threshold + 1 current_min = distance_matrix . min () return det_idxs , obj_idxs else : return [], []","title":"match_dets_and_objs()"},{"location":"reference/tracker/#norfair.tracker.TrackedObject","text":"The objects returned by the tracker's update function on each iteration. They represent the objects currently being tracked by the tracker. Users should not instantiate TrackedObjects manually; the Tracker will be in charge of creating them. Attributes: Name Type Description estimate np . ndarray Where the tracker predicts the point will be in the current frame based on past detections. A numpy array with the same shape as the detections being fed to the tracker that produced it. id Optional [ int ] The unique identifier assigned to this object by the tracker. Set to None if the object is initializing. global_id Optional [ int ] The globally unique identifier assigned to this object. Set to None if the object is initializing last_detection Detection The last detection that matched with this tracked object. Useful if you are storing embeddings in your detections and want to do metric learning, or for debugging. last_distance Optional [ float ] The distance the tracker had with the last object it matched with. age int The age of this object measured in number of frames. live_points A boolean mask with shape (n_points,) . Points marked as True have recently been matched with detections. Points marked as False haven't and are to be considered stale, and should be ignored. Functions like draw_tracked_objects use this property to determine which points not to draw. initializing_id int On top of id , objects also have an initializing_id which is the id they are given internally by the Tracker ; this id is used solely for debugging. Each new object created by the Tracker starts as an uninitialized TrackedObject , which needs to reach a certain match rate to be converted into a full blown TrackedObject . initializing_id is the id temporarily assigned to TrackedObject while they are getting initialized. Source code in norfair/tracker.py 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 class TrackedObject : \"\"\" The objects returned by the tracker's `update` function on each iteration. They represent the objects currently being tracked by the tracker. Users should not instantiate TrackedObjects manually; the Tracker will be in charge of creating them. Attributes ---------- estimate : np.ndarray Where the tracker predicts the point will be in the current frame based on past detections. A numpy array with the same shape as the detections being fed to the tracker that produced it. id : Optional[int] The unique identifier assigned to this object by the tracker. Set to `None` if the object is initializing. global_id : Optional[int] The globally unique identifier assigned to this object. Set to `None` if the object is initializing last_detection : Detection The last detection that matched with this tracked object. Useful if you are storing embeddings in your detections and want to do metric learning, or for debugging. last_distance : Optional[float] The distance the tracker had with the last object it matched with. age : int The age of this object measured in number of frames. live_points : A boolean mask with shape `(n_points,)`. Points marked as `True` have recently been matched with detections. Points marked as `False` haven't and are to be considered stale, and should be ignored. Functions like [`draw_tracked_objects`][norfair.drawing.draw_tracked_objects] use this property to determine which points not to draw. initializing_id : int On top of `id`, objects also have an `initializing_id` which is the id they are given internally by the `Tracker`; this id is used solely for debugging. Each new object created by the `Tracker` starts as an uninitialized `TrackedObject`, which needs to reach a certain match rate to be converted into a full blown `TrackedObject`. `initializing_id` is the id temporarily assigned to `TrackedObject` while they are getting initialized. \"\"\" def __init__ ( self , obj_factory : _TrackedObjectFactory , initial_detection : \"Detection\" , hit_counter_max : int , initialization_delay : int , pointwise_hit_counter_max : int , detection_threshold : float , period : int , filter_factory : \"FilterFactory\" , past_detections_length : int , reid_hit_counter_max : Optional [ int ], coord_transformations : Optional [ CoordinatesTransformation ] = None , ): if not isinstance ( initial_detection , Detection ): print ( f \" \\n [red]ERROR[/red]: The detection list fed into `tracker.update()` should be composed of { Detection } objects not { type ( initial_detection ) } . \\n \" ) exit () self . _obj_factory = obj_factory self . dim_points = initial_detection . absolute_points . shape [ 1 ] self . num_points = initial_detection . absolute_points . shape [ 0 ] self . hit_counter_max : int = hit_counter_max self . pointwise_hit_counter_max : int = max ( pointwise_hit_counter_max , period ) self . initialization_delay = initialization_delay self . detection_threshold : float = detection_threshold self . initial_period : int = period self . hit_counter : int = period self . reid_hit_counter_max = reid_hit_counter_max self . reid_hit_counter : Optional [ int ] = None self . last_distance : Optional [ float ] = None self . current_min_distance : Optional [ float ] = None self . last_detection : \"Detection\" = initial_detection self . age : int = 0 self . is_initializing : bool = self . hit_counter <= self . initialization_delay self . initializing_id : Optional [ int ] = self . _obj_factory . get_initializing_id () self . id : Optional [ int ] = None self . global_id : Optional [ int ] = None if not self . is_initializing : self . _acquire_ids () if initial_detection . scores is None : self . detected_at_least_once_points = np . array ([ True ] * self . num_points ) else : self . detected_at_least_once_points = ( initial_detection . scores > self . detection_threshold ) self . point_hit_counter : np . ndarray = self . detected_at_least_once_points . astype ( int ) initial_detection . age = self . age self . past_detections_length = past_detections_length if past_detections_length > 0 : self . past_detections : Sequence [ \"Detection\" ] = [ initial_detection ] else : self . past_detections : Sequence [ \"Detection\" ] = [] # Create Kalman Filter self . filter = filter_factory . create_filter ( initial_detection . absolute_points ) self . dim_z = self . dim_points * self . num_points self . label = initial_detection . label self . abs_to_rel = None if coord_transformations is not None : self . update_coordinate_transformation ( coord_transformations ) def tracker_step ( self ): if self . reid_hit_counter is None : if self . hit_counter <= 0 : self . reid_hit_counter = self . reid_hit_counter_max else : self . reid_hit_counter -= 1 self . hit_counter -= 1 self . point_hit_counter -= 1 self . age += 1 # Advances the tracker's state self . filter . predict () @property def hit_counter_is_positive ( self ): return self . hit_counter >= 0 @property def reid_hit_counter_is_positive ( self ): return self . reid_hit_counter is None or self . reid_hit_counter >= 0 @property def estimate_velocity ( self ) -> np . ndarray : \"\"\"Get the velocity estimate of the object from the Kalman filter. This velocity is in the absolute coordinate system. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the velocity estimate of the object on each axis. \"\"\" return self . filter . x . T . flatten ()[ self . dim_z :] . reshape ( - 1 , self . dim_points ) @property def estimate ( self ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. \"\"\" return self . get_estimate () def get_estimate ( self , absolute = False ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters ---------- absolute : bool, optional If true the coordinates are returned in absolute format, by default False, by default False. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises ------ ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. \"\"\" positions = self . filter . x . T . flatten ()[: self . dim_z ] . reshape ( - 1 , self . dim_points ) if self . abs_to_rel is None : if not absolute : return positions else : raise ValueError ( \"You must provide 'coord_transformations' to the tracker to get absolute coordinates\" ) else : if absolute : return positions else : return self . abs_to_rel ( positions ) @property def live_points ( self ): return self . point_hit_counter > 0 def hit ( self , detection : \"Detection\" , period : int = 1 ): \"\"\"Update tracked object with a new detection Parameters ---------- detection : Detection the new detection matched to this tracked object period : int, optional frames corresponding to the period of time since last update. \"\"\" self . _conditionally_add_to_past_detections ( detection ) self . last_detection = detection self . hit_counter = min ( self . hit_counter + 2 * period , self . hit_counter_max ) if self . is_initializing and self . hit_counter > self . initialization_delay : self . is_initializing = False self . _acquire_ids () # We use a kalman filter in which we consider each coordinate on each point as a sensor. # This is a hacky way to update only certain sensors (only x, y coordinates for # points which were detected). # TODO: Use keypoint confidence information to change R on each sensor instead? if detection . scores is not None : assert len ( detection . scores . shape ) == 1 points_over_threshold_mask = detection . scores > self . detection_threshold matched_sensors_mask = np . array ( [( m ,) * self . dim_points for m in points_over_threshold_mask ] ) . flatten () H_pos = np . diag ( matched_sensors_mask ) . astype ( float ) # We measure x, y positions self . point_hit_counter [ points_over_threshold_mask ] += 2 * period else : points_over_threshold_mask = np . array ([ True ] * self . num_points ) H_pos = np . identity ( self . num_points * self . dim_points ) self . point_hit_counter += 2 * period self . point_hit_counter [ self . point_hit_counter >= self . pointwise_hit_counter_max ] = self . pointwise_hit_counter_max self . point_hit_counter [ self . point_hit_counter < 0 ] = 0 H_vel = np . zeros ( H_pos . shape ) # But we don't directly measure velocity H = np . hstack ([ H_pos , H_vel ]) self . filter . update ( np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T , None , H ) detected_at_least_once_mask = np . array ( [( m ,) * self . dim_points for m in self . detected_at_least_once_points ] ) . flatten () now_detected_mask = np . hstack ( ( points_over_threshold_mask ,) * self . dim_points ) . flatten () first_detection_mask = np . logical_and ( now_detected_mask , np . logical_not ( detected_at_least_once_mask ) ) self . filter . x [: self . dim_z ][ first_detection_mask ] = np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T [ first_detection_mask ] # Force points being detected for the first time to have velocity = 0 # This is needed because some detectors (like OpenPose) set points with # low confidence to coordinates (0, 0). And when they then get their first # real detection this creates a huge velocity vector in our KalmanFilter # and causes the tracker to start with wildly inaccurate estimations which # eventually coverge to the real detections. self . filter . x [ self . dim_z :][ np . logical_not ( detected_at_least_once_mask )] = 0 self . detected_at_least_once_points = np . logical_or ( self . detected_at_least_once_points , points_over_threshold_mask ) def __repr__ ( self ): if self . last_distance is None : placeholder_text = \" \\033 [1mObject_ {} \\033 [0m(age: {} , hit_counter: {} , last_distance: {} , init_id: {} )\" else : placeholder_text = \" \\033 [1mObject_ {} \\033 [0m(age: {} , hit_counter: {} , last_distance: {:.2f} , init_id: {} )\" return placeholder_text . format ( self . id , self . age , self . hit_counter , self . last_distance , self . initializing_id , ) def _conditionally_add_to_past_detections ( self , detection ): \"\"\"Adds detections into (and pops detections away) from `past_detections` It does so by keeping a fixed amount of past detections saved into each TrackedObject, while maintaining them distributed uniformly through the object's lifetime. \"\"\" if self . past_detections_length == 0 : return if len ( self . past_detections ) < self . past_detections_length : detection . age = self . age self . past_detections . append ( detection ) elif self . age >= self . past_detections [ 0 ] . age * self . past_detections_length : self . past_detections . pop ( 0 ) detection . age = self . age self . past_detections . append ( detection ) def merge ( self , tracked_object ): \"\"\"Merge with a not yet initialized TrackedObject instance\"\"\" self . reid_hit_counter = None self . hit_counter = self . initial_period * 2 self . point_hit_counter = tracked_object . point_hit_counter self . last_distance = tracked_object . last_distance self . current_min_distance = tracked_object . current_min_distance self . last_detection = tracked_object . last_detection self . detected_at_least_once_points = ( tracked_object . detected_at_least_once_points ) self . filter = tracked_object . filter for past_detection in tracked_object . past_detections : self . _conditionally_add_to_past_detections ( past_detection ) def update_coordinate_transformation ( self , coordinate_transformation : CoordinatesTransformation ): if coordinate_transformation is not None : self . abs_to_rel = coordinate_transformation . abs_to_rel def _acquire_ids ( self ): self . id , self . global_id = self . _obj_factory . get_ids ()","title":"TrackedObject"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.estimate_velocity","text":"Get the velocity estimate of the object from the Kalman filter. This velocity is in the absolute coordinate system. Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the velocity estimate of the object on each axis.","title":"estimate_velocity"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.estimate","text":"Get the position estimate of the object from the Kalman filter. Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis.","title":"estimate"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.get_estimate","text":"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters: Name Type Description Default absolute bool , optional If true the coordinates are returned in absolute format, by default False, by default False. False Returns: Type Description np . ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises: Type Description ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. Source code in norfair/tracker.py 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 def get_estimate ( self , absolute = False ) -> np . ndarray : \"\"\"Get the position estimate of the object from the Kalman filter in an absolute or relative format. Parameters ---------- absolute : bool, optional If true the coordinates are returned in absolute format, by default False, by default False. Returns ------- np.ndarray An array of shape (self.num_points, self.dim_points) containing the position estimate of the object on each axis. Raises ------ ValueError Alert if the coordinates are requested in absolute format but the tracker has no coordinate transformation. \"\"\" positions = self . filter . x . T . flatten ()[: self . dim_z ] . reshape ( - 1 , self . dim_points ) if self . abs_to_rel is None : if not absolute : return positions else : raise ValueError ( \"You must provide 'coord_transformations' to the tracker to get absolute coordinates\" ) else : if absolute : return positions else : return self . abs_to_rel ( positions )","title":"get_estimate()"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.hit","text":"Update tracked object with a new detection Parameters: Name Type Description Default detection Detection the new detection matched to this tracked object required period int , optional frames corresponding to the period of time since last update. 1 Source code in norfair/tracker.py 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 def hit ( self , detection : \"Detection\" , period : int = 1 ): \"\"\"Update tracked object with a new detection Parameters ---------- detection : Detection the new detection matched to this tracked object period : int, optional frames corresponding to the period of time since last update. \"\"\" self . _conditionally_add_to_past_detections ( detection ) self . last_detection = detection self . hit_counter = min ( self . hit_counter + 2 * period , self . hit_counter_max ) if self . is_initializing and self . hit_counter > self . initialization_delay : self . is_initializing = False self . _acquire_ids () # We use a kalman filter in which we consider each coordinate on each point as a sensor. # This is a hacky way to update only certain sensors (only x, y coordinates for # points which were detected). # TODO: Use keypoint confidence information to change R on each sensor instead? if detection . scores is not None : assert len ( detection . scores . shape ) == 1 points_over_threshold_mask = detection . scores > self . detection_threshold matched_sensors_mask = np . array ( [( m ,) * self . dim_points for m in points_over_threshold_mask ] ) . flatten () H_pos = np . diag ( matched_sensors_mask ) . astype ( float ) # We measure x, y positions self . point_hit_counter [ points_over_threshold_mask ] += 2 * period else : points_over_threshold_mask = np . array ([ True ] * self . num_points ) H_pos = np . identity ( self . num_points * self . dim_points ) self . point_hit_counter += 2 * period self . point_hit_counter [ self . point_hit_counter >= self . pointwise_hit_counter_max ] = self . pointwise_hit_counter_max self . point_hit_counter [ self . point_hit_counter < 0 ] = 0 H_vel = np . zeros ( H_pos . shape ) # But we don't directly measure velocity H = np . hstack ([ H_pos , H_vel ]) self . filter . update ( np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T , None , H ) detected_at_least_once_mask = np . array ( [( m ,) * self . dim_points for m in self . detected_at_least_once_points ] ) . flatten () now_detected_mask = np . hstack ( ( points_over_threshold_mask ,) * self . dim_points ) . flatten () first_detection_mask = np . logical_and ( now_detected_mask , np . logical_not ( detected_at_least_once_mask ) ) self . filter . x [: self . dim_z ][ first_detection_mask ] = np . expand_dims ( detection . absolute_points . flatten (), 0 ) . T [ first_detection_mask ] # Force points being detected for the first time to have velocity = 0 # This is needed because some detectors (like OpenPose) set points with # low confidence to coordinates (0, 0). And when they then get their first # real detection this creates a huge velocity vector in our KalmanFilter # and causes the tracker to start with wildly inaccurate estimations which # eventually coverge to the real detections. self . filter . x [ self . dim_z :][ np . logical_not ( detected_at_least_once_mask )] = 0 self . detected_at_least_once_points = np . logical_or ( self . detected_at_least_once_points , points_over_threshold_mask )","title":"hit()"},{"location":"reference/tracker/#norfair.tracker.TrackedObject.merge","text":"Merge with a not yet initialized TrackedObject instance Source code in norfair/tracker.py 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 def merge ( self , tracked_object ): \"\"\"Merge with a not yet initialized TrackedObject instance\"\"\" self . reid_hit_counter = None self . hit_counter = self . initial_period * 2 self . point_hit_counter = tracked_object . point_hit_counter self . last_distance = tracked_object . last_distance self . current_min_distance = tracked_object . current_min_distance self . last_detection = tracked_object . last_detection self . detected_at_least_once_points = ( tracked_object . detected_at_least_once_points ) self . filter = tracked_object . filter for past_detection in tracked_object . past_detections : self . _conditionally_add_to_past_detections ( past_detection )","title":"merge()"},{"location":"reference/tracker/#norfair.tracker.Detection","text":"Detections returned by the detector must be converted to a Detection object before being used by Norfair. Parameters: Name Type Description Default points np . ndarray Points detected. Must be a rank 2 array with shape (n_points, n_dimensions) where n_dimensions is 2 or 3. required scores np . ndarray , optional An array of length n_points which assigns a score to each of the points defined in points . This is used to inform the tracker of which points to ignore; any point with a score below detection_threshold will be ignored. This useful for cases in which detections don't always have every point present, as is often the case in pose estimators. None data Any , optional The place to store any extra data which may be useful when calculating the distance function. Anything stored here will be available to use inside the distance function. This enables the development of more interesting trackers which can do things like assign an appearance embedding to each detection to aid in its tracking. None label Hashable , optional When working with multiple classes the detection's label can be stored to be used as a matching condition when associating tracked objects with new detections. Label's type must be hashable for drawing purposes. None embedding Any , optional The embedding for the reid_distance. None Source code in norfair/tracker.py 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 class Detection : \"\"\"Detections returned by the detector must be converted to a `Detection` object before being used by Norfair. Parameters ---------- points : np.ndarray Points detected. Must be a rank 2 array with shape `(n_points, n_dimensions)` where n_dimensions is 2 or 3. scores : np.ndarray, optional An array of length `n_points` which assigns a score to each of the points defined in `points`. This is used to inform the tracker of which points to ignore; any point with a score below `detection_threshold` will be ignored. This useful for cases in which detections don't always have every point present, as is often the case in pose estimators. data : Any, optional The place to store any extra data which may be useful when calculating the distance function. Anything stored here will be available to use inside the distance function. This enables the development of more interesting trackers which can do things like assign an appearance embedding to each detection to aid in its tracking. label : Hashable, optional When working with multiple classes the detection's label can be stored to be used as a matching condition when associating tracked objects with new detections. Label's type must be hashable for drawing purposes. embedding : Any, optional The embedding for the reid_distance. \"\"\" def __init__ ( self , points : np . ndarray , scores : np . ndarray = None , data : Any = None , label : Hashable = None , embedding = None , ): self . points = validate_points ( points ) self . scores = scores self . data = data self . label = label self . absolute_points = self . points . copy () self . embedding = embedding self . age = None def update_coordinate_transformation ( self , coordinate_transformation : CoordinatesTransformation ): if coordinate_transformation is not None : self . absolute_points = coordinate_transformation . rel_to_abs ( self . absolute_points )","title":"Detection"},{"location":"reference/utils/","text":"Utils # print_objects_as_table ( tracked_objects ) # Used for helping in debugging Source code in norfair/utils.py 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 def print_objects_as_table ( tracked_objects : Sequence ): \"\"\"Used for helping in debugging\"\"\" print () console = Console () table = Table ( show_header = True , header_style = \"bold magenta\" ) table . add_column ( \"Id\" , style = \"yellow\" , justify = \"center\" ) table . add_column ( \"Age\" , justify = \"right\" ) table . add_column ( \"Hit Counter\" , justify = \"right\" ) table . add_column ( \"Last distance\" , justify = \"right\" ) table . add_column ( \"Init Id\" , justify = \"center\" ) for obj in tracked_objects : table . add_row ( str ( obj . id ), str ( obj . age ), str ( obj . hit_counter ), f \" { obj . last_distance : .4f } \" , str ( obj . initializing_id ), ) console . print ( table ) get_cutout ( points , image ) # Returns a rectangular cut-out from a set of points on an image Source code in norfair/utils.py 65 66 67 68 69 70 71 def get_cutout ( points , image ): \"\"\"Returns a rectangular cut-out from a set of points on an image\"\"\" max_x = int ( max ( points [:, 0 ])) min_x = int ( min ( points [:, 0 ])) max_y = int ( max ( points [:, 1 ])) min_y = int ( min ( points [:, 1 ])) return image [ min_y : max_y , min_x : max_x ] warn_once ( message ) cached # Write a warning message only once. Source code in norfair/utils.py 95 96 97 98 99 100 @lru_cache ( maxsize = None ) def warn_once ( message ): \"\"\" Write a warning message only once. \"\"\" warn ( message )","title":"Utils"},{"location":"reference/utils/#utils","text":"","title":"Utils"},{"location":"reference/utils/#norfair.utils.print_objects_as_table","text":"Used for helping in debugging Source code in norfair/utils.py 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 def print_objects_as_table ( tracked_objects : Sequence ): \"\"\"Used for helping in debugging\"\"\" print () console = Console () table = Table ( show_header = True , header_style = \"bold magenta\" ) table . add_column ( \"Id\" , style = \"yellow\" , justify = \"center\" ) table . add_column ( \"Age\" , justify = \"right\" ) table . add_column ( \"Hit Counter\" , justify = \"right\" ) table . add_column ( \"Last distance\" , justify = \"right\" ) table . add_column ( \"Init Id\" , justify = \"center\" ) for obj in tracked_objects : table . add_row ( str ( obj . id ), str ( obj . age ), str ( obj . hit_counter ), f \" { obj . last_distance : .4f } \" , str ( obj . initializing_id ), ) console . print ( table )","title":"print_objects_as_table()"},{"location":"reference/utils/#norfair.utils.get_cutout","text":"Returns a rectangular cut-out from a set of points on an image Source code in norfair/utils.py 65 66 67 68 69 70 71 def get_cutout ( points , image ): \"\"\"Returns a rectangular cut-out from a set of points on an image\"\"\" max_x = int ( max ( points [:, 0 ])) min_x = int ( min ( points [:, 0 ])) max_y = int ( max ( points [:, 1 ])) min_y = int ( min ( points [:, 1 ])) return image [ min_y : max_y , min_x : max_x ]","title":"get_cutout()"},{"location":"reference/utils/#norfair.utils.warn_once","text":"Write a warning message only once. Source code in norfair/utils.py 95 96 97 98 99 100 @lru_cache ( maxsize = None ) def warn_once ( message ): \"\"\" Write a warning message only once. \"\"\" warn ( message )","title":"warn_once()"},{"location":"reference/video/","text":"Video # Video # Class that provides a simple and pythonic way to interact with video. It returns regular OpenCV frames which enables the usage of the huge number of tools OpenCV provides to modify images. Parameters: Name Type Description Default camera Optional [ int ], optional An integer representing the device id of the camera to be used as the video source. Webcams tend to have an id of 0 . Arguments camera and input_path can't be used at the same time, one must be chosen. None input_path Optional [ str ], optional A string consisting of the path to the video file to be used as the video source. Arguments camera and input_path can't be used at the same time, one must be chosen. None output_path str , optional The path to the output video to be generated. Can be a folder were the file will be created or a full path with a file name. '.' output_fps Optional [ float ], optional The frames per second at which to encode the output video file. If not provided it is set to be equal to the input video source's fps. This argument is useful when using live video cameras as a video source, where the user may know the input fps, but where the frames are being fed to the output video at a rate that is lower than the video source's fps, due to the latency added by the detector. None label str , optional Label to add to the progress bar that appears when processing the current video. '' output_fourcc Optional [ str ], optional OpenCV encoding for output video file. By default we use mp4v for .mp4 and XVID for .avi . This is a combination that works on most systems but it results in larger files. To get smaller files use avc1 or H264 if available. Notice that some fourcc are not compatible with some extensions. None output_extension str , optional File extension used for the output video. Ignored if output_path is not a folder. 'mp4' Examples: >>> video = Video ( input_path = \"video.mp4\" ) >>> for frame in video : >>> # << Your modifications to the frame would go here >> >>> video . write ( frame ) Source code in norfair/video.py 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 class Video : \"\"\" Class that provides a simple and pythonic way to interact with video. It returns regular OpenCV frames which enables the usage of the huge number of tools OpenCV provides to modify images. Parameters ---------- camera : Optional[int], optional An integer representing the device id of the camera to be used as the video source. Webcams tend to have an id of `0`. Arguments `camera` and `input_path` can't be used at the same time, one must be chosen. input_path : Optional[str], optional A string consisting of the path to the video file to be used as the video source. Arguments `camera` and `input_path` can't be used at the same time, one must be chosen. output_path : str, optional The path to the output video to be generated. Can be a folder were the file will be created or a full path with a file name. output_fps : Optional[float], optional The frames per second at which to encode the output video file. If not provided it is set to be equal to the input video source's fps. This argument is useful when using live video cameras as a video source, where the user may know the input fps, but where the frames are being fed to the output video at a rate that is lower than the video source's fps, due to the latency added by the detector. label : str, optional Label to add to the progress bar that appears when processing the current video. output_fourcc : Optional[str], optional OpenCV encoding for output video file. By default we use `mp4v` for `.mp4` and `XVID` for `.avi`. This is a combination that works on most systems but it results in larger files. To get smaller files use `avc1` or `H264` if available. Notice that some fourcc are not compatible with some extensions. output_extension : str, optional File extension used for the output video. Ignored if `output_path` is not a folder. Examples -------- >>> video = Video(input_path=\"video.mp4\") >>> for frame in video: >>> # << Your modifications to the frame would go here >> >>> video.write(frame) \"\"\" def __init__ ( self , camera : Optional [ int ] = None , input_path : Optional [ str ] = None , output_path : str = \".\" , output_fps : Optional [ float ] = None , label : str = \"\" , output_fourcc : Optional [ str ] = None , output_extension : str = \"mp4\" , ): self . camera = camera self . input_path = input_path self . output_path = output_path self . label = label self . output_fourcc = output_fourcc self . output_extension = output_extension self . output_video : Optional [ cv2 . VideoWriter ] = None # Input validation if ( input_path is None and camera is None ) or ( input_path is not None and camera is not None ): raise ValueError ( \"You must set either 'camera' or 'input_path' arguments when setting 'Video' class\" ) if camera is not None and type ( camera ) is not int : raise ValueError ( \"Argument 'camera' refers to the device-id of your camera, and must be an int. Setting it to 0 usually works if you don't know the id.\" ) # Read Input Video if self . input_path is not None : if \"~\" in self . input_path : self . input_path = os . path . expanduser ( self . input_path ) if not os . path . isfile ( self . input_path ): self . _fail ( f \"[bold red]Error:[/bold red] File ' { self . input_path } ' does not exist.\" ) self . video_capture = cv2 . VideoCapture ( self . input_path ) total_frames = int ( self . video_capture . get ( cv2 . CAP_PROP_FRAME_COUNT )) if total_frames == 0 : self . _fail ( f \"[bold red]Error:[/bold red] ' { self . input_path } ' does not seem to be a video file supported by OpenCV. If the video file is not the problem, please check that your OpenCV installation is working correctly.\" ) description = os . path . basename ( self . input_path ) else : self . video_capture = cv2 . VideoCapture ( self . camera ) total_frames = 0 description = f \"Camera( { self . camera } )\" self . output_fps = ( output_fps if output_fps is not None else self . video_capture . get ( cv2 . CAP_PROP_FPS ) ) self . input_height = self . video_capture . get ( cv2 . CAP_PROP_FRAME_HEIGHT ) self . input_width = self . video_capture . get ( cv2 . CAP_PROP_FRAME_WIDTH ) self . frame_counter = 0 # Setup progressbar if self . label : description += f \" | { self . label } \" progress_bar_fields : List [ Union [ str , ProgressColumn ]] = [ \"[progress.description] {task.description} \" , BarColumn (), \"[yellow] {task.fields[process_fps]:.2f} fps[/yellow]\" , ] if self . input_path is not None : progress_bar_fields . insert ( 2 , \"[progress.percentage] {task.percentage:>3.0f} %\" ) progress_bar_fields . insert ( 3 , TimeRemainingColumn (), ) self . progress_bar = Progress ( * progress_bar_fields , auto_refresh = False , redirect_stdout = False , redirect_stderr = False , ) self . task = self . progress_bar . add_task ( self . abbreviate_description ( description ), total = total_frames , start = self . input_path is not None , process_fps = 0 , ) # This is a generator, note the yield keyword below. def __iter__ ( self ): with self . progress_bar as progress_bar : start = time . time () # Iterate over video while True : self . frame_counter += 1 ret , frame = self . video_capture . read () if ret is False or frame is None : break process_fps = self . frame_counter / ( time . time () - start ) progress_bar . update ( self . task , advance = 1 , refresh = True , process_fps = process_fps ) yield frame # Cleanup if self . output_video is not None : self . output_video . release () print ( f \"[white]Output video file saved to: { self . get_output_file_path () } [/white]\" ) self . video_capture . release () cv2 . destroyAllWindows () def _fail ( self , msg : str ): print ( msg ) exit () def write ( self , frame : np . ndarray ) -> int : \"\"\" Write one frame to the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to write to file. Returns ------- int _description_ \"\"\" if self . output_video is None : # The user may need to access the output file path on their code output_file_path = self . get_output_file_path () fourcc = cv2 . VideoWriter_fourcc ( * self . get_codec_fourcc ( output_file_path )) # Set on first frame write in case the user resizes the frame in some way output_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) self . output_video = cv2 . VideoWriter ( output_file_path , fourcc , self . output_fps , output_size , ) self . output_video . write ( frame ) return cv2 . waitKey ( 1 ) def show ( self , frame : np . ndarray , downsample_ratio : float = 1.0 ) -> int : \"\"\" Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to be displayed. downsample_ratio : float, optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. Returns ------- int _description_ \"\"\" # Resize to lower resolution for faster streaming over slow connections if downsample_ratio != 1.0 : frame = cv2 . resize ( frame , ( frame . shape [ 1 ] // downsample_ratio , frame . shape [ 0 ] // downsample_ratio , ), ) cv2 . imshow ( \"Output\" , frame ) return cv2 . waitKey ( 1 ) def get_output_file_path ( self ) -> str : \"\"\" Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set `output_path`, and want to know what the autogenerated output file path by Norfair will be. Returns ------- str The path to the file. \"\"\" if not os . path . isdir ( self . output_path ): return self . output_path if self . input_path is not None : file_name = self . input_path . split ( \"/\" )[ - 1 ] . split ( \".\" )[ 0 ] else : file_name = \"camera_ {self.camera} \" file_name = f \" { file_name } _out. { self . output_extension } \" return os . path . join ( self . output_path , file_name ) def get_codec_fourcc ( self , filename : str ) -> Optional [ str ]: if self . output_fourcc is not None : return self . output_fourcc # Default codecs for each extension extension = filename [ - 3 :] . lower () if \"avi\" == extension : return \"XVID\" elif \"mp4\" == extension : return \"mp4v\" # When available, \"avc1\" is better else : self . _fail ( f \"[bold red]Could not determine video codec for the provided output filename[/bold red]: \" f \"[yellow] { filename } [/yellow] \\n \" f \"Please use '.mp4', '.avi', or provide a custom OpenCV fourcc codec name.\" ) return ( None # Had to add this return to make mypya happy. I don't like this. ) def abbreviate_description ( self , description : str ) -> str : \"\"\"Conditionally abbreviate description so that progress bar fits in small terminals\"\"\" terminal_columns , _ = get_terminal_size () space_for_description = ( int ( terminal_columns ) - 25 ) # Leave 25 space for progressbar if len ( description ) < space_for_description : return description else : return \" {} ... {} \" . format ( description [: space_for_description // 2 - 3 ], description [ - space_for_description // 2 + 3 :], ) write ( frame ) # Write one frame to the output video. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to write to file. required Returns: Type Description int description Source code in norfair/video.py 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 def write ( self , frame : np . ndarray ) -> int : \"\"\" Write one frame to the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to write to file. Returns ------- int _description_ \"\"\" if self . output_video is None : # The user may need to access the output file path on their code output_file_path = self . get_output_file_path () fourcc = cv2 . VideoWriter_fourcc ( * self . get_codec_fourcc ( output_file_path )) # Set on first frame write in case the user resizes the frame in some way output_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) self . output_video = cv2 . VideoWriter ( output_file_path , fourcc , self . output_fps , output_size , ) self . output_video . write ( frame ) return cv2 . waitKey ( 1 ) show ( frame , downsample_ratio = 1.0 ) # Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to be displayed. required downsample_ratio float , optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. 1.0 Returns: Type Description int description Source code in norfair/video.py 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 def show ( self , frame : np . ndarray , downsample_ratio : float = 1.0 ) -> int : \"\"\" Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to be displayed. downsample_ratio : float, optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. Returns ------- int _description_ \"\"\" # Resize to lower resolution for faster streaming over slow connections if downsample_ratio != 1.0 : frame = cv2 . resize ( frame , ( frame . shape [ 1 ] // downsample_ratio , frame . shape [ 0 ] // downsample_ratio , ), ) cv2 . imshow ( \"Output\" , frame ) return cv2 . waitKey ( 1 ) get_output_file_path () # Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set output_path , and want to know what the autogenerated output file path by Norfair will be. Returns: Type Description str The path to the file. Source code in norfair/video.py 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 def get_output_file_path ( self ) -> str : \"\"\" Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set `output_path`, and want to know what the autogenerated output file path by Norfair will be. Returns ------- str The path to the file. \"\"\" if not os . path . isdir ( self . output_path ): return self . output_path if self . input_path is not None : file_name = self . input_path . split ( \"/\" )[ - 1 ] . split ( \".\" )[ 0 ] else : file_name = \"camera_ {self.camera} \" file_name = f \" { file_name } _out. { self . output_extension } \" return os . path . join ( self . output_path , file_name ) abbreviate_description ( description ) # Conditionally abbreviate description so that progress bar fits in small terminals Source code in norfair/video.py 287 288 289 290 291 292 293 294 295 296 297 298 299 def abbreviate_description ( self , description : str ) -> str : \"\"\"Conditionally abbreviate description so that progress bar fits in small terminals\"\"\" terminal_columns , _ = get_terminal_size () space_for_description = ( int ( terminal_columns ) - 25 ) # Leave 25 space for progressbar if len ( description ) < space_for_description : return description else : return \" {} ... {} \" . format ( description [: space_for_description // 2 - 3 ], description [ - space_for_description // 2 + 3 :], )","title":"Video"},{"location":"reference/video/#video","text":"","title":"Video"},{"location":"reference/video/#norfair.video.Video","text":"Class that provides a simple and pythonic way to interact with video. It returns regular OpenCV frames which enables the usage of the huge number of tools OpenCV provides to modify images. Parameters: Name Type Description Default camera Optional [ int ], optional An integer representing the device id of the camera to be used as the video source. Webcams tend to have an id of 0 . Arguments camera and input_path can't be used at the same time, one must be chosen. None input_path Optional [ str ], optional A string consisting of the path to the video file to be used as the video source. Arguments camera and input_path can't be used at the same time, one must be chosen. None output_path str , optional The path to the output video to be generated. Can be a folder were the file will be created or a full path with a file name. '.' output_fps Optional [ float ], optional The frames per second at which to encode the output video file. If not provided it is set to be equal to the input video source's fps. This argument is useful when using live video cameras as a video source, where the user may know the input fps, but where the frames are being fed to the output video at a rate that is lower than the video source's fps, due to the latency added by the detector. None label str , optional Label to add to the progress bar that appears when processing the current video. '' output_fourcc Optional [ str ], optional OpenCV encoding for output video file. By default we use mp4v for .mp4 and XVID for .avi . This is a combination that works on most systems but it results in larger files. To get smaller files use avc1 or H264 if available. Notice that some fourcc are not compatible with some extensions. None output_extension str , optional File extension used for the output video. Ignored if output_path is not a folder. 'mp4' Examples: >>> video = Video ( input_path = \"video.mp4\" ) >>> for frame in video : >>> # << Your modifications to the frame would go here >> >>> video . write ( frame ) Source code in norfair/video.py 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 class Video : \"\"\" Class that provides a simple and pythonic way to interact with video. It returns regular OpenCV frames which enables the usage of the huge number of tools OpenCV provides to modify images. Parameters ---------- camera : Optional[int], optional An integer representing the device id of the camera to be used as the video source. Webcams tend to have an id of `0`. Arguments `camera` and `input_path` can't be used at the same time, one must be chosen. input_path : Optional[str], optional A string consisting of the path to the video file to be used as the video source. Arguments `camera` and `input_path` can't be used at the same time, one must be chosen. output_path : str, optional The path to the output video to be generated. Can be a folder were the file will be created or a full path with a file name. output_fps : Optional[float], optional The frames per second at which to encode the output video file. If not provided it is set to be equal to the input video source's fps. This argument is useful when using live video cameras as a video source, where the user may know the input fps, but where the frames are being fed to the output video at a rate that is lower than the video source's fps, due to the latency added by the detector. label : str, optional Label to add to the progress bar that appears when processing the current video. output_fourcc : Optional[str], optional OpenCV encoding for output video file. By default we use `mp4v` for `.mp4` and `XVID` for `.avi`. This is a combination that works on most systems but it results in larger files. To get smaller files use `avc1` or `H264` if available. Notice that some fourcc are not compatible with some extensions. output_extension : str, optional File extension used for the output video. Ignored if `output_path` is not a folder. Examples -------- >>> video = Video(input_path=\"video.mp4\") >>> for frame in video: >>> # << Your modifications to the frame would go here >> >>> video.write(frame) \"\"\" def __init__ ( self , camera : Optional [ int ] = None , input_path : Optional [ str ] = None , output_path : str = \".\" , output_fps : Optional [ float ] = None , label : str = \"\" , output_fourcc : Optional [ str ] = None , output_extension : str = \"mp4\" , ): self . camera = camera self . input_path = input_path self . output_path = output_path self . label = label self . output_fourcc = output_fourcc self . output_extension = output_extension self . output_video : Optional [ cv2 . VideoWriter ] = None # Input validation if ( input_path is None and camera is None ) or ( input_path is not None and camera is not None ): raise ValueError ( \"You must set either 'camera' or 'input_path' arguments when setting 'Video' class\" ) if camera is not None and type ( camera ) is not int : raise ValueError ( \"Argument 'camera' refers to the device-id of your camera, and must be an int. Setting it to 0 usually works if you don't know the id.\" ) # Read Input Video if self . input_path is not None : if \"~\" in self . input_path : self . input_path = os . path . expanduser ( self . input_path ) if not os . path . isfile ( self . input_path ): self . _fail ( f \"[bold red]Error:[/bold red] File ' { self . input_path } ' does not exist.\" ) self . video_capture = cv2 . VideoCapture ( self . input_path ) total_frames = int ( self . video_capture . get ( cv2 . CAP_PROP_FRAME_COUNT )) if total_frames == 0 : self . _fail ( f \"[bold red]Error:[/bold red] ' { self . input_path } ' does not seem to be a video file supported by OpenCV. If the video file is not the problem, please check that your OpenCV installation is working correctly.\" ) description = os . path . basename ( self . input_path ) else : self . video_capture = cv2 . VideoCapture ( self . camera ) total_frames = 0 description = f \"Camera( { self . camera } )\" self . output_fps = ( output_fps if output_fps is not None else self . video_capture . get ( cv2 . CAP_PROP_FPS ) ) self . input_height = self . video_capture . get ( cv2 . CAP_PROP_FRAME_HEIGHT ) self . input_width = self . video_capture . get ( cv2 . CAP_PROP_FRAME_WIDTH ) self . frame_counter = 0 # Setup progressbar if self . label : description += f \" | { self . label } \" progress_bar_fields : List [ Union [ str , ProgressColumn ]] = [ \"[progress.description] {task.description} \" , BarColumn (), \"[yellow] {task.fields[process_fps]:.2f} fps[/yellow]\" , ] if self . input_path is not None : progress_bar_fields . insert ( 2 , \"[progress.percentage] {task.percentage:>3.0f} %\" ) progress_bar_fields . insert ( 3 , TimeRemainingColumn (), ) self . progress_bar = Progress ( * progress_bar_fields , auto_refresh = False , redirect_stdout = False , redirect_stderr = False , ) self . task = self . progress_bar . add_task ( self . abbreviate_description ( description ), total = total_frames , start = self . input_path is not None , process_fps = 0 , ) # This is a generator, note the yield keyword below. def __iter__ ( self ): with self . progress_bar as progress_bar : start = time . time () # Iterate over video while True : self . frame_counter += 1 ret , frame = self . video_capture . read () if ret is False or frame is None : break process_fps = self . frame_counter / ( time . time () - start ) progress_bar . update ( self . task , advance = 1 , refresh = True , process_fps = process_fps ) yield frame # Cleanup if self . output_video is not None : self . output_video . release () print ( f \"[white]Output video file saved to: { self . get_output_file_path () } [/white]\" ) self . video_capture . release () cv2 . destroyAllWindows () def _fail ( self , msg : str ): print ( msg ) exit () def write ( self , frame : np . ndarray ) -> int : \"\"\" Write one frame to the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to write to file. Returns ------- int _description_ \"\"\" if self . output_video is None : # The user may need to access the output file path on their code output_file_path = self . get_output_file_path () fourcc = cv2 . VideoWriter_fourcc ( * self . get_codec_fourcc ( output_file_path )) # Set on first frame write in case the user resizes the frame in some way output_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) self . output_video = cv2 . VideoWriter ( output_file_path , fourcc , self . output_fps , output_size , ) self . output_video . write ( frame ) return cv2 . waitKey ( 1 ) def show ( self , frame : np . ndarray , downsample_ratio : float = 1.0 ) -> int : \"\"\" Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to be displayed. downsample_ratio : float, optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. Returns ------- int _description_ \"\"\" # Resize to lower resolution for faster streaming over slow connections if downsample_ratio != 1.0 : frame = cv2 . resize ( frame , ( frame . shape [ 1 ] // downsample_ratio , frame . shape [ 0 ] // downsample_ratio , ), ) cv2 . imshow ( \"Output\" , frame ) return cv2 . waitKey ( 1 ) def get_output_file_path ( self ) -> str : \"\"\" Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set `output_path`, and want to know what the autogenerated output file path by Norfair will be. Returns ------- str The path to the file. \"\"\" if not os . path . isdir ( self . output_path ): return self . output_path if self . input_path is not None : file_name = self . input_path . split ( \"/\" )[ - 1 ] . split ( \".\" )[ 0 ] else : file_name = \"camera_ {self.camera} \" file_name = f \" { file_name } _out. { self . output_extension } \" return os . path . join ( self . output_path , file_name ) def get_codec_fourcc ( self , filename : str ) -> Optional [ str ]: if self . output_fourcc is not None : return self . output_fourcc # Default codecs for each extension extension = filename [ - 3 :] . lower () if \"avi\" == extension : return \"XVID\" elif \"mp4\" == extension : return \"mp4v\" # When available, \"avc1\" is better else : self . _fail ( f \"[bold red]Could not determine video codec for the provided output filename[/bold red]: \" f \"[yellow] { filename } [/yellow] \\n \" f \"Please use '.mp4', '.avi', or provide a custom OpenCV fourcc codec name.\" ) return ( None # Had to add this return to make mypya happy. I don't like this. ) def abbreviate_description ( self , description : str ) -> str : \"\"\"Conditionally abbreviate description so that progress bar fits in small terminals\"\"\" terminal_columns , _ = get_terminal_size () space_for_description = ( int ( terminal_columns ) - 25 ) # Leave 25 space for progressbar if len ( description ) < space_for_description : return description else : return \" {} ... {} \" . format ( description [: space_for_description // 2 - 3 ], description [ - space_for_description // 2 + 3 :], )","title":"Video"},{"location":"reference/video/#norfair.video.Video.write","text":"Write one frame to the output video. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to write to file. required Returns: Type Description int description Source code in norfair/video.py 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 def write ( self , frame : np . ndarray ) -> int : \"\"\" Write one frame to the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to write to file. Returns ------- int _description_ \"\"\" if self . output_video is None : # The user may need to access the output file path on their code output_file_path = self . get_output_file_path () fourcc = cv2 . VideoWriter_fourcc ( * self . get_codec_fourcc ( output_file_path )) # Set on first frame write in case the user resizes the frame in some way output_size = ( frame . shape [ 1 ], frame . shape [ 0 ], ) # OpenCV format is (width, height) self . output_video = cv2 . VideoWriter ( output_file_path , fourcc , self . output_fps , output_size , ) self . output_video . write ( frame ) return cv2 . waitKey ( 1 )","title":"write()"},{"location":"reference/video/#norfair.video.Video.show","text":"Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters: Name Type Description Default frame np . ndarray The OpenCV frame to be displayed. required downsample_ratio float , optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. 1.0 Returns: Type Description int description Source code in norfair/video.py 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 def show ( self , frame : np . ndarray , downsample_ratio : float = 1.0 ) -> int : \"\"\" Display a frame through a GUI. Usually used inside a video inference loop to show the output video. Parameters ---------- frame : np.ndarray The OpenCV frame to be displayed. downsample_ratio : float, optional How much to downsample the frame being show. Useful when streaming the GUI video display through a slow internet connection using something like X11 forwarding on an ssh connection. Returns ------- int _description_ \"\"\" # Resize to lower resolution for faster streaming over slow connections if downsample_ratio != 1.0 : frame = cv2 . resize ( frame , ( frame . shape [ 1 ] // downsample_ratio , frame . shape [ 0 ] // downsample_ratio , ), ) cv2 . imshow ( \"Output\" , frame ) return cv2 . waitKey ( 1 )","title":"show()"},{"location":"reference/video/#norfair.video.Video.get_output_file_path","text":"Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set output_path , and want to know what the autogenerated output file path by Norfair will be. Returns: Type Description str The path to the file. Source code in norfair/video.py 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 def get_output_file_path ( self ) -> str : \"\"\" Calculate the output path being used in case you are writing your frames to a video file. Useful if you didn't set `output_path`, and want to know what the autogenerated output file path by Norfair will be. Returns ------- str The path to the file. \"\"\" if not os . path . isdir ( self . output_path ): return self . output_path if self . input_path is not None : file_name = self . input_path . split ( \"/\" )[ - 1 ] . split ( \".\" )[ 0 ] else : file_name = \"camera_ {self.camera} \" file_name = f \" { file_name } _out. { self . output_extension } \" return os . path . join ( self . output_path , file_name )","title":"get_output_file_path()"},{"location":"reference/video/#norfair.video.Video.abbreviate_description","text":"Conditionally abbreviate description so that progress bar fits in small terminals Source code in norfair/video.py 287 288 289 290 291 292 293 294 295 296 297 298 299 def abbreviate_description ( self , description : str ) -> str : \"\"\"Conditionally abbreviate description so that progress bar fits in small terminals\"\"\" terminal_columns , _ = get_terminal_size () space_for_description = ( int ( terminal_columns ) - 25 ) # Leave 25 space for progressbar if len ( description ) < space_for_description : return description else : return \" {} ... {} \" . format ( description [: space_for_description // 2 - 3 ], description [ - space_for_description // 2 + 3 :], )","title":"abbreviate_description()"}]}
\ No newline at end of file
diff --git a/dev/sitemap.xml b/dev/sitemap.xml
index 058ba76d..0f8724ef 100644
--- a/dev/sitemap.xml
+++ b/dev/sitemap.xml
@@ -1,58 +1,3 @@
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
-
- None
- 2023-02-09
- daily
-
\ No newline at end of file
diff --git a/dev/sitemap.xml.gz b/dev/sitemap.xml.gz
index 4ae3d490..613dc3a8 100644
Binary files a/dev/sitemap.xml.gz and b/dev/sitemap.xml.gz differ