Meet YOLO26: next-gen vision AI.

Link to this sectionReference for ultralytics/trackers/utils/matching.py#

Improvements

This page is sourced from https://github.com/ultralytics/ultralytics/blob/main/ultralytics/trackers/utils/matching.py. Have an improvement or example to add? Open a Pull Request — thank you! 🙏


Summary

Link to this sectionFunction ultralytics.trackers.utils.matching.linear_assignment#

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

Perform linear assignment using either lap.lapjv or the built-in NumPy solver.

Args

NameTypeDescriptionDefault
cost_matrixnp.ndarrayThe matrix containing cost values for assignments, with shape (N, M).required
threshfloatThreshold for considering an assignment valid.required
use_lapboolUse lap.lapjv for the assignment. If False, ops.linear_sum_assignment is used.True

Returns

TypeDescription
`matched_indices (list[list[int]]np.ndarray)`
`unmatched_a (tuplelist
`unmatched_b (tuplelist

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

View on GitHub

def linear_assignment(cost_matrix: np.ndarray, thresh: float, use_lap: bool = True):
    """Perform linear assignment using either lap.lapjv or the built-in NumPy solver.

    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, ops.linear_sum_assignment is used.

    Returns:
        matched_indices (list[list[int]] | np.ndarray): Matched indices of shape (K, 2), where K is the number of
            matches.
        unmatched_a (tuple | list | np.ndarray): Unmatched indices from the first set.
        unmatched_b (tuple | list | np.ndarray): Unmatched indices from the second set.

    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:
        from ultralytics.utils.ops import linear_sum_assignment

        x, y = 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





Link to this sectionFunction ultralytics.trackers.utils.matching.iou_distance#

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

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

Args

NameTypeDescriptionDefault
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

TypeDescription
np.ndarrayCost 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

View on GitHub

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





Link to this sectionFunction ultralytics.trackers.utils.matching.embedding_distance#

def embedding_distance(tracks: list, detections: list) -> np.ndarray

Compute cosine distance between tracks and detections based on embeddings.

Args

NameTypeDescriptionDefault
trackslist[BOTrack]List of tracks, where each track contains embedding features.required
detectionslist[BOTrack]List of detections, where each detection contains embedding features.required

Returns

TypeDescription
np.ndarrayCost matrix computed based on embeddings with shape (N, M), where N is the number of tracks and M

Examples

Compute the embedding distance between tracks and detections using cosine metric
>>> tracks = [BOTrack(...), BOTrack(...)]  # List of track objects with embedding features
>>> detections = [BOTrack(...), BOTrack(...)]  # List of detection objects with embedding features
>>> cost_matrix = embedding_distance(tracks, detections)
Source code in ultralytics/trackers/utils/matching.py

View on GitHub

def embedding_distance(tracks: list, detections: list) -> np.ndarray:
    """Compute cosine distance between tracks and detections based on embeddings.

    Args:
        tracks (list[BOTrack]): List of tracks, where each track contains embedding features.
        detections (list[BOTrack]): List of detections, where each detection contains embedding features.

    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 = [BOTrack(...), BOTrack(...)]  # List of track objects with embedding features
        >>> detections = [BOTrack(...), BOTrack(...)]  # List of detection objects with embedding features
        >>> cost_matrix = embedding_distance(tracks, detections)
    """
    cost_matrix = np.zeros((len(tracks), len(detections)), dtype=np.float32)
    if cost_matrix.size == 0:
        return cost_matrix
    # A zero-norm embedding is skipped upstream, leaving curr_feat/smooth_feat None. Stack a zero placeholder so the
    # array isn't ragged, then (below) force any missing-feature pair to the maximum distance so callers ignore
    # appearance and fall back to motion/IoU rather than crashing or treating it as a partial match.
    track_feats = [t.smooth_feat for t in tracks]
    det_feats = [d.curr_feat for d in detections]
    feat_dim = next((len(f) for f in (*track_feats, *det_feats) if f is not None), 0)
    zeros = np.zeros(feat_dim, dtype=np.float32)
    track_features = np.asarray([f if f is not None else zeros for f in track_feats], dtype=np.float32)
    det_features = np.asarray([f if f is not None else zeros for f in det_feats], dtype=np.float32)
    track_norm = np.linalg.norm(track_features, axis=1, keepdims=True)
    det_norm = np.linalg.norm(det_features, axis=1, keepdims=True).T
    cost_matrix = 1 - track_features @ det_features.T / np.maximum(track_norm * det_norm, np.finfo(float).eps)
    cost_matrix = np.maximum(0.0, cost_matrix)  # Normalized features
    missing_t = [i for i, f in enumerate(track_feats) if f is None]
    missing_d = [j for j, f in enumerate(det_feats) if f is None]
    if missing_t:
        cost_matrix[missing_t] = 2.0  # max cosine distance -> caller's /2 then appearance gate ignores the pair
    if missing_d:
        cost_matrix[:, missing_d] = 2.0
    return cost_matrix





Link to this sectionFunction ultralytics.trackers.utils.matching.fuse_score#

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

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

Args

NameTypeDescriptionDefault
cost_matrixnp.ndarrayThe matrix containing cost values for assignments, with shape (N, M).required
detectionslist[BaseTrack]List of detections, each containing a score attribute.required

Returns

TypeDescription
np.ndarrayFused cost 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

View on GitHub

def fuse_score(cost_matrix: np.ndarray, detections: list) -> np.ndarray:
    """Fuse cost matrix with detection scores to produce a single cost 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 cost 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 = det_scores[None].repeat(cost_matrix.shape[0], axis=0)
    fuse_sim = iou_sim * det_scores
    return 1 - fuse_sim  # fuse_cost