Skip to content

Register now for

# Reference for `ultralytics/trackers/utils/matching.py`

Note

This file is available at https://github.com/ultralytics/ultralytics/blob/main/ultralytics/trackers/utils/matching.py. If you spot a problem please help fix it by contributing a Pull Request 🛠️. Thank you 🙏!

## `` ultralytics.trackers.utils.matching.linear_assignment

    linear_assignment(cost_matrix: ndarray, thresh: float, use_lap: bool = True)

Perform linear assignment using either the scipy or lap.lapjv method.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`cost_matrix` | `ndarray` | The matrix containing cost values for assignments, with shape (N, M). | required
`thresh` | `float` | Threshold for considering an assignment valid. | required
`use_lap` | `bool` | Use lap.lapjv for the assignment. If False, scipy.optimize.linear_sum_assignment is used. | `True`

Returns:

Name | Type | Description
--- | --- | ---
`matched_indices` | `ndarray` | Array of matched indices of shape (K, 2), where K is the number of matches.
`unmatched_a` | `ndarray` | Array of unmatched indices from the first set, with shape (L,).
`unmatched_b` | `ndarray` | Array of unmatched indices from the second set, with shape (M,).

Examples:

    >>> cost_matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    >>> thresh = 5.0
    >>> matched_indices, unmatched_a, unmatched_b = linear_assignment(cost_matrix, thresh, use_lap=True)

Source code in `ultralytics/trackers/utils/matching.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
    def linear_assignment(cost_matrix: np.ndarray, thresh: float, use_lap: bool = True):
        """
        Perform linear assignment using either the scipy or lap.lapjv method.

        Args:
            cost_matrix (np.ndarray): The matrix containing cost values for assignments, with shape (N, M).
            thresh (float): Threshold for considering an assignment valid.
            use_lap (bool): Use lap.lapjv for the assignment. If False, scipy.optimize.linear_sum_assignment is used.

        Returns:
            matched_indices (np.ndarray): Array of matched indices of shape (K, 2), where K is the number of matches.
            unmatched_a (np.ndarray): Array of unmatched indices from the first set, with shape (L,).
            unmatched_b (np.ndarray): Array of unmatched indices from the second set, with shape (M,).

        Examples:
            >>> cost_matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
            >>> thresh = 5.0
            >>> matched_indices, unmatched_a, unmatched_b = linear_assignment(cost_matrix, thresh, use_lap=True)
        """
        if cost_matrix.size == 0:
            return np.empty((0, 2), dtype=int), tuple(range(cost_matrix.shape[0])), tuple(range(cost_matrix.shape[1]))

        if use_lap:
            # Use lap.lapjv
            # https://github.com/gatagat/lap
            _, x, y = lap.lapjv(cost_matrix, extend_cost=True, cost_limit=thresh)
            matches = [[ix, mx] for ix, mx in enumerate(x) if mx >= 0]
            unmatched_a = np.where(x < 0)[0]
            unmatched_b = np.where(y < 0)[0]
        else:
            # Use scipy.optimize.linear_sum_assignment
            # https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.linear_sum_assignment.html
            x, y = scipy.optimize.linear_sum_assignment(cost_matrix)  # row x, col y
            matches = np.asarray([[x[i], y[i]] for i in range(len(x)) if cost_matrix[x[i], y[i]] <= thresh])
            if len(matches) == 0:
                unmatched_a = list(np.arange(cost_matrix.shape[0]))
                unmatched_b = list(np.arange(cost_matrix.shape[1]))
            else:
                unmatched_a = list(frozenset(np.arange(cost_matrix.shape[0])) - frozenset(matches[:, 0]))
                unmatched_b = list(frozenset(np.arange(cost_matrix.shape[1])) - frozenset(matches[:, 1]))

        return matches, unmatched_a, unmatched_b

## `` ultralytics.trackers.utils.matching.iou_distance

    iou_distance(atracks: list, btracks: list) -> np.ndarray

Compute cost based on Intersection over Union (IoU) between tracks.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`atracks` | `list[STrack] | list[ndarray]` | List of tracks 'a' or bounding boxes. | required
`btracks` | `list[STrack] | list[ndarray]` | List of tracks 'b' or bounding boxes. | required

Returns:

Type | Description
--- | ---
`ndarray` | Cost matrix computed based on IoU with shape (len(atracks), len(btracks)).

Examples:

Compute IoU distance between two sets of tracks

    >>> atracks = [np.array([0, 0, 10, 10]), np.array([20, 20, 30, 30])]
    >>> btracks = [np.array([5, 5, 15, 15]), np.array([25, 25, 35, 35])]
    >>> cost_matrix = iou_distance(atracks, btracks)

Source code in `ultralytics/trackers/utils/matching.py`

     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
    def iou_distance(atracks: list, btracks: list) -> np.ndarray:
        """
        Compute cost based on Intersection over Union (IoU) between tracks.

        Args:
            atracks (list[STrack] | list[np.ndarray]): List of tracks 'a' or bounding boxes.
            btracks (list[STrack] | list[np.ndarray]): List of tracks 'b' or bounding boxes.

        Returns:
            (np.ndarray): Cost matrix computed based on IoU with shape (len(atracks), len(btracks)).

        Examples:
            Compute IoU distance between two sets of tracks
            >>> atracks = [np.array([0, 0, 10, 10]), np.array([20, 20, 30, 30])]
            >>> btracks = [np.array([5, 5, 15, 15]), np.array([25, 25, 35, 35])]
            >>> cost_matrix = iou_distance(atracks, btracks)
        """
        if atracks and isinstance(atracks[0], np.ndarray) or btracks and isinstance(btracks[0], np.ndarray):
            atlbrs = atracks
            btlbrs = btracks
        else:
            atlbrs = [track.xywha if track.angle is not None else track.xyxy for track in atracks]
            btlbrs = [track.xywha if track.angle is not None else track.xyxy for track in btracks]

        ious = np.zeros((len(atlbrs), len(btlbrs)), dtype=np.float32)
        if len(atlbrs) and len(btlbrs):
            if len(atlbrs[0]) == 5 and len(btlbrs[0]) == 5:
                ious = batch_probiou(
                    np.ascontiguousarray(atlbrs, dtype=np.float32),
                    np.ascontiguousarray(btlbrs, dtype=np.float32),
                ).numpy()
            else:
                ious = bbox_ioa(
                    np.ascontiguousarray(atlbrs, dtype=np.float32),
                    np.ascontiguousarray(btlbrs, dtype=np.float32),
                    iou=True,
                )
        return 1 - ious  # cost matrix

## `` ultralytics.trackers.utils.matching.embedding_distance

    embedding_distance(
        tracks: list, detections: list, metric: str = "cosine"
    ) -> np.ndarray

Compute distance between tracks and detections based on embeddings.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`tracks` | `list[STrack]` | List of tracks, where each track contains embedding features. | required
`detections` | `list[BaseTrack]` | List of detections, where each detection contains embedding features. | required
`metric` | `str` | Metric for distance computation. Supported metrics include 'cosine', 'euclidean', etc. | `'cosine'`

Returns:

Type | Description
--- | ---
`ndarray` | Cost matrix computed based on embeddings with shape (N, M), where N is the number of tracks and M is the number of detections.

Examples:

Compute the embedding distance between tracks and detections using cosine metric

    >>> tracks = [STrack(...), STrack(...)]  # List of track objects with embedding features
    >>> detections = [BaseTrack(...), BaseTrack(...)]  # List of detection objects with embedding features
    >>> cost_matrix = embedding_distance(tracks, detections, metric="cosine")

Source code in `ultralytics/trackers/utils/matching.py`

    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
    def embedding_distance(tracks: list, detections: list, metric: str = "cosine") -> np.ndarray:
        """
        Compute distance between tracks and detections based on embeddings.

        Args:
            tracks (list[STrack]): List of tracks, where each track contains embedding features.
            detections (list[BaseTrack]): List of detections, where each detection contains embedding features.
            metric (str): Metric for distance computation. Supported metrics include 'cosine', 'euclidean', etc.

        Returns:
            (np.ndarray): Cost matrix computed based on embeddings with shape (N, M), where N is the number of tracks
                and M is the number of detections.

        Examples:
            Compute the embedding distance between tracks and detections using cosine metric
            >>> tracks = [STrack(...), STrack(...)]  # List of track objects with embedding features
            >>> detections = [BaseTrack(...), BaseTrack(...)]  # List of detection objects with embedding features
            >>> cost_matrix = embedding_distance(tracks, detections, metric="cosine")
        """
        cost_matrix = np.zeros((len(tracks), len(detections)), dtype=np.float32)
        if cost_matrix.size == 0:
            return cost_matrix
        det_features = np.asarray([track.curr_feat for track in detections], dtype=np.float32)
        # for i, track in enumerate(tracks):
        # cost_matrix[i, :] = np.maximum(0.0, cdist(track.smooth_feat.reshape(1,-1), det_features, metric))
        track_features = np.asarray([track.smooth_feat for track in tracks], dtype=np.float32)
        cost_matrix = np.maximum(0.0, cdist(track_features, det_features, metric))  # Normalized features
        return cost_matrix

## `` ultralytics.trackers.utils.matching.fuse_score

    fuse_score(cost_matrix: ndarray, detections: list) -> np.ndarray

Fuse cost matrix with detection scores to produce a single similarity matrix.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`cost_matrix` | `ndarray` | The matrix containing cost values for assignments, with shape (N, M). | required
`detections` | `list[BaseTrack]` | List of detections, each containing a score attribute. | required

Returns:

Type | Description
--- | ---
`ndarray` | Fused similarity matrix with shape (N, M).

Examples:

Fuse a cost matrix with detection scores

    >>> cost_matrix = np.random.rand(5, 10)  # 5 tracks and 10 detections
    >>> detections = [BaseTrack(score=np.random.rand()) for _ in range(10)]
    >>> fused_matrix = fuse_score(cost_matrix, detections)

Source code in `ultralytics/trackers/utils/matching.py`

    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    def fuse_score(cost_matrix: np.ndarray, detections: list) -> np.ndarray:
        """
        Fuse cost matrix with detection scores to produce a single similarity matrix.

        Args:
            cost_matrix (np.ndarray): The matrix containing cost values for assignments, with shape (N, M).
            detections (list[BaseTrack]): List of detections, each containing a score attribute.

        Returns:
            (np.ndarray): Fused similarity matrix with shape (N, M).

        Examples:
            Fuse a cost matrix with detection scores
            >>> cost_matrix = np.random.rand(5, 10)  # 5 tracks and 10 detections
            >>> detections = [BaseTrack(score=np.random.rand()) for _ in range(10)]
            >>> fused_matrix = fuse_score(cost_matrix, detections)
        """
        if cost_matrix.size == 0:
            return cost_matrix
        iou_sim = 1 - cost_matrix
        det_scores = np.array([det.score for det in detections])
        det_scores = np.expand_dims(det_scores, axis=0).repeat(cost_matrix.shape[0], axis=0)
        fuse_sim = iou_sim * det_scores
        return 1 - fuse_sim  # fuse_cost

📅 Created 1 year ago ✏️ Updated 1 year ago