Фаза 04 · урок 27

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

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

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

Курс
AI Engineering from Scratch
Фаза
Компьютерное зрение
Чтение
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 — Мировые модели и диффузия видео · Полный каталог