Фаза 04 · урок 27

Отслеживание нескольких объектов и видеопамять

Цель урока: Детектор сообщает, где находятся объекты в одном кадре. Трекер сообщает, какое обнаружение в кадре t соответствует тому же объекту, что и обнаружение в кадре t-1 . Без этого нельзя считать объекты, пересекающие линию, следить за мячом…

Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.

Курс
AI Engineering с нуля
Фаза
Компьютерное зрение
Чтение
12 мин.
Проверено
Содержание урока
  1. Цели обучения
  2. Проблема
  3. Концепция
  4. Отслеживание через обнаружение
  5. Фильтр Калмана в одном абзаце
  6. Венгерский алгоритм
  7. Ключевая идея ByteTrack
  8. Отслеживание SAM 2 на основе памяти
  9. SAM 3.1 Object Multiplex
  10. Три метрики, которые нужно знать
  11. Соберите
  12. Шаг 1: матрица стоимостей на основе IoU
  13. Шаг 2: минимальный трекер в стиле SORT
  14. Шаг 3: тест синтетических траекторий
  15. Шаг 4: метрика переключений ID
  16. Используйте
  17. Поставьте в продакшен
  18. Упражнения
  19. Ключевые термины
  20. Дополнительные материалы

Отслеживание — это обнаружение плюс сопоставление. Обнаруживайте объекты в каждом кадре. Сопоставляйте обнаружения текущего кадра с треками прошлого кадра по ID.

Тип: Соберите Языки: Python Предварительные требования: Фаза 4, урок 06 (обнаружение YOLO), фаза 4, урок 08 (Mask R-CNN), фаза 4, урок 24 (SAM 3) Время: ~60 минут

Цели обучения

  • Различать отслеживание через обнаружение (tracking-by-detection) и отслеживание на основе запросов, а также называть семейства алгоритмов (SORT, DeepSORT, ByteTrack, BoT-SORT, трекер памяти SAM 2, SAM 3.1 Object Multiplex)
  • Реализовать с нуля IoU + венгерское назначение для классического отслеживания через обнаружение
  • Объяснить банк памяти SAM 2 и то, почему он лучше справляется с окклюзиями, чем сопоставление на основе IoU
  • Читать три метрики отслеживания (MOTA, IDF1, HOTA) и выбирать важную для конкретного сценария

Проблема

Детектор сообщает, где находятся объекты в одном кадре. Трекер сообщает, какое обнаружение в кадре t соответствует тому же объекту, что и обнаружение в кадре t-1. Без этого нельзя считать объекты, пересекающие линию, следить за мячом во время окклюзии или знать, что «автомобиль № 4 находится в полосе уже 8 секунд».

Отслеживание необходимо каждому продукту, работающему с видео: спортивной аналитике, видеонаблюдению, автономному вождению, анализу медицинских видео, мониторингу дикой природы, подсчёту словесных знаков. Основные строительные блоки общие: детектор для каждого кадра, модель движения (фильтр Калмана или нечто более сложное), шаг сопоставления (венгерский алгоритм по IoU / косинусной мере / обученным признакам) и жизненный цикл трека (рождение, обновление, завершение).

2026 год принёс два новых паттерна: отслеживание SAM 2 на основе памяти (память признаков вместо сопоставления моделью движения) и SAM 3.1 Object Multiplex (общая память для многих экземпляров одного концепта). В этом уроке сначала разобран классический стек, а затем подход на основе памяти.

Концепция

Отслеживание через обнаружение

Диаграмма к уроку «Отслеживание нескольких объектов и видеопамять»

Каждый трекер, с которым вы столкнётесь в 2026 году, — вариация этого цикла. Различия таковы:

  • SORT (2016): фильтр Калмана + IoU + венгерский алгоритм. Прост, быстр, не имеет модели внешнего вида.
  • DeepSORT (2017): SORT + признак внешнего вида на основе CNN для каждого трека (ReID-эмбеддинг). Лучше обрабатывает пересечения.
  • ByteTrack (2021): на втором этапе сопоставляет обнаружения с низкой уверенностью; признаки внешнего вида не требуются, но это лидер на MOT17.
  • BoT-SORT (2022): Byte + компенсация движения камеры + ReID.
  • StrongSORT / OC-SORT — потомки ByteTrack с улучшенным движением и внешним видом.

Фильтр Калмана в одном абзаце

Фильтр Калмана поддерживает для каждого трека состояние (x, y, w, h, dx, dy, dw, dh) с ковариацией. В каждом кадре он предсказывает состояние с помощью модели постоянной скорости, затем обновляет его сопоставленным обнаружением. Когда неопределённость предсказания высока, обновление больше доверяет обнаружению. Это даёт гладкие траектории и возможность продолжать трек во время короткой окклюзии (1–5 кадров).

Каждый классический трекер использует фильтр Калмана на шаге предсказания движения.

Венгерский алгоритм

Для матрицы стоимостей M x N (треки × обнаружения) найдите взаимно однозначное назначение, минимизирующее суммарную стоимость. Обычно стоимость равна 1 - IoU(track_bbox, detection_bbox) или отрицательному косинусному сходству признаков внешнего вида. Время работы — O((M+N)^3); при M, N до ~1000 алгоритм достаточно быстр в Python через scipy.optimize.linear_sum_assignment.

Ключевая идея ByteTrack

Стандартные трекеры отбрасывают обнаружения с низкой уверенностью (< 0.5). ByteTrack сохраняет их как кандидатов второго этапа: после сопоставления треков с обнаружениями высокой уверенности несопоставленные треки пытаются сопоставиться с обнаружениями низкой уверенности при немного более мягком пороге IoU. Это восстанавливает короткие окклюзии и переключения ID рядом с толпами.

Отслеживание SAM 2 на основе памяти

SAM 2 обрабатывает видео, сохраняя банк памяти пространственно-временных признаков для каждого экземпляра. Получив запрос (щелчок, рамку, текст) на одном кадре, он кодирует экземпляр в память. На последующих кадрах память участвует в cross-attention с признаками нового кадра, а декодер создаёт маску того же экземпляра в новом кадре.

Нет ни фильтра Калмана, ни венгерского назначения. Сопоставление неявно выполняется в операции внимания к памяти.

Плюсы:

  • Устойчивость к большим окклюзиям (память переносит идентичность экземпляра через множество кадров).
  • Открытый словарь в сочетании с текстовыми запросами SAM 3.
  • Работа без отдельной модели движения.

Минусы:

  • Медленнее ByteTrack при отслеживании множества объектов.
  • Банк памяти растёт и ограничивает окно контекста.

SAM 3.1 Object Multiplex

Предыдущее отслеживание SAM 2 / SAM 3 хранит отдельный банк памяти для каждого экземпляра. Для 50 объектов — 50 банков памяти. Object Multiplex (март 2026 года) объединяет их в одну общую память с токенами запросов для каждого экземпляра. Стоимость масштабируется сублинейно относительно числа экземпляров.

Multiplex — новый вариант по умолчанию для отслеживания толп в 2026 году: толпы на концертах, работники склада, дорожные перекрёстки.

Три метрики, которые нужно знать

  • MOTA (Multi-Object Tracking Accuracy) — 1 - (FN + FP + переключения ID) / GT. Взвешивается по типу ошибки; одна метрика, смешивающая ошибки обнаружения и сопоставления.
  • IDF1 (ID F1) — гармоническое среднее точности и полноты ID. Фокусируется на том, насколько хорошо каждый трек истинных данных сохраняет свой ID во времени. Лучше MOTA для задач, чувствительных к переключениям ID.
  • HOTA (Higher Order Tracking Accuracy) — раскладывается на точность обнаружения (DetA) и точность сопоставления (AssA). Стандарт сообщества с 2020 года; наиболее полная метрика.

Для видеонаблюдения (кто есть кто) сообщайте IDF1. Для спортивной аналитики (подсчёт передач) — HOTA. Для общего академического сравнения — HOTA.

Соберите

Шаг 1: матрица стоимостей на основе IoU

import numpy as np


def bbox_iou(a, b):
    """
    a, b: (N, 4) arrays of [x1, y1, x2, y2].
    Returns (N_a, N_b) IoU matrix.
    """
    ax1, ay1, ax2, ay2 = a[:, 0], a[:, 1], a[:, 2], a[:, 3]
    bx1, by1, bx2, by2 = b[:, 0], b[:, 1], b[:, 2], b[:, 3]
    inter_x1 = np.maximum(ax1[:, None], bx1[None, :])
    inter_y1 = np.maximum(ay1[:, None], by1[None, :])
    inter_x2 = np.minimum(ax2[:, None], bx2[None, :])
    inter_y2 = np.minimum(ay2[:, None], by2[None, :])
    inter = np.clip(inter_x2 - inter_x1, 0, None) * np.clip(inter_y2 - inter_y1, 0, None)
    area_a = (ax2 - ax1) * (ay2 - ay1)
    area_b = (bx2 - bx1) * (by2 - by1)
    union = area_a[:, None] + area_b[None, :] - inter
    return inter / np.clip(union, 1e-8, None)

Шаг 2: минимальный трекер в стиле SORT

Полный фильтр Калмана с постоянной скоростью для краткости опущен — здесь используется простое сопоставление по IoU; в продакшене предсказание Калмана необходимо. Полную версию предоставляет Python-пакет sort.

from scipy.optimize import linear_sum_assignment


class Track:
    def __init__(self, tid, bbox, frame):
        self.id = tid
        self.bbox = bbox
        self.last_frame = frame
        self.hits = 1

    def update(self, bbox, frame):
        self.bbox = bbox
        self.last_frame = frame
        self.hits += 1


class SimpleTracker:
    def __init__(self, iou_threshold=0.3, max_age=5):
        self.tracks = []
        self.next_id = 1
        self.iou_threshold = iou_threshold
        self.max_age = max_age

    def step(self, detections, frame):
        if not self.tracks:
            for d in detections:
                self.tracks.append(Track(self.next_id, d, frame))
                self.next_id += 1
            return [(t.id, t.bbox) for t in self.tracks]

        track_boxes = np.array([t.bbox for t in self.tracks])
        det_boxes = np.array(detections) if len(detections) else np.empty((0, 4))

        iou = bbox_iou(track_boxes, det_boxes) if len(det_boxes) else np.zeros((len(track_boxes), 0))
        cost = 1 - iou
        cost[iou < self.iou_threshold] = 1e6

        matched_track = set()
        matched_det = set()
        if cost.size > 0:
            row, col = linear_sum_assignment(cost)
            for r, c in zip(row, col):
                if cost[r, c] < 1.0:
                    self.tracks[r].update(det_boxes[c], frame)
                    matched_track.add(r); matched_det.add(c)

        for i, d in enumerate(det_boxes):
            if i not in matched_det:
                self.tracks.append(Track(self.next_id, d, frame))
                self.next_id += 1

        self.tracks = [t for t in self.tracks if frame - t.last_frame <= self.max_age]
        return [(t.id, t.bbox) for t in self.tracks]

60 строк. Получает обнаружения по кадрам, возвращает ID треков по кадрам. В реальные системы добавляются предсказание Калмана, повторное сопоставление второго этапа ByteTrack и признаки внешнего вида.

Шаг 3: тест синтетических траекторий

def synthetic_frames(num_frames=20, num_objects=3, H=240, W=320, seed=0):
    rng = np.random.default_rng(seed)
    starts = rng.uniform(20, 200, size=(num_objects, 2))
    velocities = rng.uniform(-5, 5, size=(num_objects, 2))
    frames = []
    for f in range(num_frames):
        dets = []
        for i in range(num_objects):
            cx, cy = starts[i] + f * velocities[i]
            dets.append([cx - 10, cy - 10, cx + 10, cy + 10])
        frames.append(dets)
    return frames


tracker = SimpleTracker()
for f, dets in enumerate(synthetic_frames()):
    tracks = tracker.step(dets, f)

Три объекта, движущиеся по прямым, должны сохранять свои ID на всех 20 кадрах.

Шаг 4: метрика переключений ID

def count_id_switches(tracks_per_frame, gt_per_frame):
    """
    tracks_per_frame:  list of list of (track_id, bbox)
    gt_per_frame:      list of list of (gt_id, bbox)
    Returns number of ID switches.
    """
    prev_assignment = {}
    switches = 0
    for tracks, gts in zip(tracks_per_frame, gt_per_frame):
        if not tracks or not gts:
            continue
        t_boxes = np.array([b for _, b in tracks])
        g_boxes = np.array([b for _, b in gts])
        iou = bbox_iou(g_boxes, t_boxes)
        for g_idx, (gt_id, _) in enumerate(gts):
            j = iou[g_idx].argmax()
            if iou[g_idx, j] > 0.5:
                t_id = tracks[j][0]
                if gt_id in prev_assignment and prev_assignment[gt_id] != t_id:
                    switches += 1
                prev_assignment[gt_id] = t_id
    return switches

Это упрощённая метрика, близкая к IDF1: она считает, сколько раз объект истинных данных меняет назначенный ему ID предсказанного трека. Настоящие инструменты MOTA / IDF1 / HOTA находятся в py-motmetrics и TrackEval.

Используйте

Продакшен-трекеры в 2026 году:

  • ultralytics — встроенные YOLOv8 + ByteTrack / BoT-SORT. results = model.track(source, tracker="bytetrack.yaml"). Вариант по умолчанию.
  • supervision (Roboflow) — обёртки ByteTrack и утилиты аннотирования.
  • SAM 2 / SAM 3.1 — отслеживание на основе памяти через processor.track().
  • Пользовательский стек: детектор (YOLOv8 / RT-DETR) + sort-tracker / OC-SORT / StrongSORT.

Выбор:

  • Пешеходы / автомобили / коробки при 30+ fps: ByteTrack с ultralytics.
  • Много экземпляров одного класса в толпе: SAM 3.1 Object Multiplex.
  • Тяжёлые окклюзии при различимом внешнем виде: DeepSORT / StrongSORT (ReID-признаки).
  • Спорт / сложные взаимодействия: BoT-SORT или обученные трекеры (MOTRv3).

Поставьте в продакшен

Этот урок создаёт:

  • outputs/prompt-tracker-picker.md — выбирает SORT / ByteTrack / BoT-SORT / SAM 2 / SAM 3.1 с учётом типа сцены, паттернов окклюзий и бюджета задержки.
  • outputs/skill-mot-evaluator.md — пишет полный набор оценки MOTA / IDF1 / HOTA по трекам истинных данных.

Упражнения

  1. (Легко) Запустите приведённый выше синтетический трекер с 3, 10 и 30 объектами. Сообщите число переключений ID в каждом случае. Определите, где начинает давать сбои простое сопоставление только по IoU.
  2. (Средне) Добавьте шаг предсказания фильтром Калмана с постоянной скоростью перед сопоставлением. Покажите, что короткие окклюзии (2–3 кадра) больше не вызывают переключений ID.
  3. (Сложно) Интегрируйте трекер SAM 2 на основе памяти (через transformers) как альтернативный бэкенд трекера. Запустите и SimpleTracker, и SAM 2 на 30-секундном клипе толпы и сравните числа переключений ID, вручную разметив истинные ID для 5 заметных людей.

Ключевые термины

ТерминКак обычно говорятЧто это на самом деле означает
Отслеживание через обнаружение«Сначала обнаружить, затем сопоставить»Детектор по кадрам + венгерское назначение по IoU / внешнему виду
Фильтр Калмана«Предсказание движения»Линейная динамика + ковариация для гладких предсказаний треков и обработки окклюзий
Венгерский алгоритм«Оптимальное назначение»Решает задачу двудольного сопоставления с минимальной стоимостью; scipy.optimize.linear_sum_assignment
ByteTrack«Второй проход с низкой уверенностью»Повторно сопоставляет несопоставленные треки с обнаружениями низкой уверенности, чтобы восстановить короткие окклюзии
DeepSORT«SORT + внешний вид»Добавляет ReID-признак для сопоставления между кадрами; лучше сохраняет ID
Банк памяти«Приём SAM 2»Пространственно-временные признаки экземпляра, сохраняемые между кадрами; cross-attention заменяет явное сопоставление
Object Multiplex«Общая память SAM 3.1»Единая общая память с запросами для каждого экземпляра для быстрого отслеживания множества объектов
HOTA«Современная метрика отслеживания»Раскладывается на точность обнаружения и сопоставления; стандарт сообщества

Дополнительные материалы


Источник: Multi-Object Tracking & Video Memory 04.26 — Монокулярная оценка глубины · Фаза 04 — Компьютерное зрение · 04.28 — Мировые модели и диффузия видео · Полный каталог