Phase 04 - Lesson 27
Rastreamento de Multiplos Objetos e Memoria de Video
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Rastreamento e deteccao mais associacao. Detecte cada quadro. Associe as deteccoes deste quadro as faixas do quadro anterior por ID.
Tipo: Build Linguagens: Python Pre-requisitos: Fase 4 Licao 06 (Deteccao com YOLO), Fase 4 Licao 08 (Mask R-CNN), Fase 4 Licao 24 (SAM 3) Tempo: ~60 minutos
Objetivos de Aprendizagem
- Distinguir o rastreamento por deteccao do rastreamento baseado em consultas e nomear as familias de algoritmos (SORT, DeepSORT, ByteTrack, BoT-SORT, rastreador por memoria SAM 2, SAM 3.1 Object Multiplex)
- Implementar a atribuicao IoU + Hungaro do zero para o classico rastreamento por deteccao
- Explicar o banco de memoria do SAM 2 e por que ele lida melhor com oclusao do que a associacao baseada em IoU
- Ler as tres metricas de rastreamento (MOTA, IDF1, HOTA) e escolher qual delas importa para um determinado caso de uso
O Problema
Um detector diz onde os objetos estao em um unico quadro. Um rastreador diz qual deteccao no quadro t e o mesmo objeto que uma deteccao no quadro t-1. Sem isso, voce nao consegue contar objetos cruzando uma linha, seguir uma bola atraves de uma oclusao ou saber que "o carro #4 esta na faixa ha 8 segundos".
O rastreamento e essencial para todo produto voltado a video: analise esportiva, vigilancia, direcao autonoma, analise de video medico, monitoramento de fauna, contagem de marcas. Os blocos de construcao centrais sao compartilhados: um detector por quadro, um modelo de movimento (filtro de Kalman ou algo mais sofisticado), uma etapa de associacao (algoritmo Hungaro sobre IoU / cosseno / atributos aprendidos) e um ciclo de vida da faixa (nascimento, atualizacao, morte).
2026 trouxe dois novos padroes: rastreamento baseado em memoria do SAM 2 (associacao por memoria de atributos em vez de modelo de movimento) e SAM 3.1 Object Multiplex (memoria compartilhada para muitas instancias do mesmo conceito). Esta licao percorre primeiro a pilha classica e depois a abordagem baseada em memoria.
O Conceito
Rastreamento por deteccao
flowchart LR
F1["Quadro t"] --> DET["Detector"] --> D1["Deteccoes em t"]
PREV["Faixas ate t-1"] --> PREDICT["Previsao de movimento<br/>(Kalman)"]
PREDICT --> PRED["Faixas previstas em t"]
D1 --> ASSOC["Atribuicao Hungara<br/>(IoU / cosseno / movimento)"]
PRED --> ASSOC
ASSOC --> UPDATE["Atualizar faixas associadas"]
ASSOC --> NEW["Criar novas faixas"]
ASSOC --> DEAD["Envelhecer faixas sem associacao; excluir apos N"]
UPDATE --> NEXT["Faixas em t"]
NEW --> NEXT
DEAD --> NEXT
style DET fill:#dbeafe,stroke:#2563eb
style ASSOC fill:#fef3c7,stroke:#d97706
style NEXT fill:#dcfce7,stroke:#16a34a
Todo rastreador que voce encontrara em 2026 e uma variacao desse loop. As diferencas:
- SORT (2016): filtro de Kalman + IoU Hungaro. Simples, rapido, sem modelo de aparencia.
- DeepSORT (2017): SORT + um atributo de aparencia baseado em CNN por faixa (embedding de ReID). Lida melhor com cruzamentos.
- ByteTrack (2021): associa deteccoes de baixa confianca como uma segunda etapa; nao precisa de atributos de aparencia, mas e o melhor no MOT17.
- BoT-SORT (2022): Byte + compensacao de movimento de camera + ReID.
- StrongSORT / OC-SORT — descendentes do ByteTrack com melhor movimento e aparencia.
Filtro de Kalman em um paragrafo
Um filtro de Kalman mantem um estado por faixa (x, y, w, h, dx, dy, dw, dh) com uma covariancia. A cada quadro, preve o estado usando um modelo de velocidade constante e depois atualiza com a deteccao associada. A atualizacao confia mais na deteccao quando a incerteza da previsao e alta. Isso da trajetorias suaves e a capacidade de continuar uma faixa atraves de uma curta oclusao (1-5 quadros).
Todo rastreador classico usa um filtro de Kalman na etapa de previsao de movimento.
O algoritmo Hungaro
Dada uma matriz de custo M x N (faixas x deteccoes), encontre a atribuicao um-para-um que minimiza o custo total. O custo geralmente e 1 - IoU(track_bbox, detection_bbox) ou a similaridade de cosseno negativa de atributos de aparencia. O tempo de execucao e O((M+N)^3); para M, N de ate ~1000 e rapido o suficiente em Python via scipy.optimize.linear_sum_assignment.
A ideia central do ByteTrack
Rastreadores padrao descartam deteccoes de baixa confianca (< 0.5). O ByteTrack as mantem por perto como candidatas de segunda etapa: depois de associar as faixas as deteccoes de alta confianca, as faixas sem associacao tentam casar com deteccoes de baixa confianca usando um limiar de IoU um pouco mais frouxo. Recupera oclusoes curtas e trocas de ID perto de multidoes.
Rastreamento baseado em memoria do SAM 2
O SAM 2 lida com video mantendo um banco de memoria de atributos espaco-temporais por instancia. Dado um prompt (clique, caixa, texto) em um quadro, ele codifica a instancia na memoria. Nos quadros seguintes, a memoria recebe atencao cruzada contra os atributos do novo quadro, e o decodificador produz uma mascara para a mesma instancia no novo quadro.
Sem filtro de Kalman, sem atribuicao Hungara. A associacao e implicita na operacao de atencao a memoria.
Pros:
- Robusto a grandes oclusoes (a memoria carrega a identidade da instancia atraves de muitos quadros).
- Vocabulario aberto quando combinado com os prompts de texto do SAM 3.
- Funciona sem um modelo de movimento separado.
Contras:
- Mais lento que o ByteTrack para rastreamento de muitos objetos.
- O banco de memoria cresce; limita a janela de contexto.
SAM 3.1 Object Multiplex
O rastreamento anterior do SAM 2 / SAM 3 mantem um banco de memoria separado por instancia. Para 50 objetos, 50 bancos de memoria. O Object Multiplex (marco de 2026) os colapsa em uma unica memoria compartilhada com tokens de consulta por instancia. O custo escala de forma sub-linear no numero de instancias.
O Multiplex e o novo padrao para rastreamento de multidoes em 2026: multidoes em shows, trabalhadores de armazem, cruzamentos de transito.
Tres metricas para conhecer
- MOTA (Multi-Object Tracking Accuracy) — 1 - (FN + FP + trocas de ID) / GT. Ponderada por tipo de erro; uma metrica unica que mistura falhas de deteccao e de associacao.
- IDF1 (ID F1) — media harmonica entre precisao e revocacao de ID. Foca especificamente em quao bem cada faixa de referencia mantem seu ID ao longo do tempo. Melhor que MOTA para tarefas sensiveis a troca de ID.
- HOTA (Higher Order Tracking Accuracy) — decompoe em precisao de deteccao (DetA) e precisao de associacao (AssA). O padrao da comunidade desde 2020; o mais abrangente.
Para vigilancia (quem e quem): IDF1 e o que voce reporta. Para analise esportiva (contar passes): HOTA. Para comparacao academica geral: HOTA.
Construa
Passo 1: Matriz de custo baseada em 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)
Passo 2: Rastreador minimo no estilo SORT
O Kalman de velocidade constante fixa foi omitido por brevidade — usamos aqui uma associacao simples por IoU; em producao a previsao de Kalman e essencial. O pacote Python sort fornece a versao completa.
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 linhas. Recebe deteccoes por quadro, retorna IDs de faixa por quadro. Sistemas reais adicionam a previsao de Kalman, a re-associacao de segunda etapa do ByteTrack e atributos de aparencia.
Passo 3: Teste de trajetoria sintetica
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)
Tres objetos movendo-se em linha reta devem manter seus IDs ao longo dos 20 quadros.
Passo 4: Metrica de troca de 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
Esta e uma metrica simplificada proxima do IDF1: conta quantas vezes um objeto de referencia muda o ID da faixa prevista atribuida a ele. As ferramentas reais de MOTA / IDF1 / HOTA vivem em py-motmetrics e TrackEval.
Use
Rastreadores de producao em 2026:
ultralytics— YOLOv8 + ByteTrack / BoT-SORT integrados.results = model.track(source, tracker="bytetrack.yaml"). O padrao.supervision(Roboflow) — wrappers de ByteTrack mais utilitarios de anotacao.- SAM 2 / SAM 3.1 — rastreamento baseado em memoria via
processor.track(). - Pilha personalizada: detector (YOLOv8 / RT-DETR) +
sort-tracker/OC-SORT/StrongSORT.
Escolhendo:
- Pedestres / carros / caixas a 30+ fps: ByteTrack com ultralytics.
- Muitas instancias de uma classe em uma multidao: SAM 3.1 Object Multiplex.
- Oclusoes pesadas com aparencia identificavel: DeepSORT / StrongSORT (atributos de ReID).
- Esportes / interacoes complexas: BoT-SORT ou rastreadores aprendidos (MOTRv3).
Entregue
Esta licao produz:
outputs/prompt-tracker-picker.md— escolhe SORT / ByteTrack / BoT-SORT / SAM 2 / SAM 3.1 dado o tipo de cena, os padroes de oclusao e o orcamento de latencia.outputs/skill-mot-evaluator.md— escreve um harness de avaliacao completo para MOTA / IDF1 / HOTA contra faixas de referencia.
Exercicios
- (Facil) Execute o rastreador sintetico acima com 3, 10 e 30 objetos. Reporte a contagem de trocas de ID em cada caso. Identifique onde a associacao simples apenas por IoU comeca a falhar.
- (Medio) Adicione um passo de previsao de Kalman de velocidade constante antes da associacao. Mostre que oclusoes curtas (2-3 quadros) nao causam mais trocas de ID.
- (Dificil) Integre o rastreador baseado em memoria do SAM 2 (via
transformers) como um backend de rastreamento alternativo. Execute tanto o SimpleTracker quanto o SAM 2 em um clipe de 30 segundos de uma multidao e compare as contagens de trocas de ID, rotulando manualmente os IDs de referencia de 5 pessoas em destaque.
Termos-chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| Rastreamento por deteccao | "Detectar e depois associar" | Detector por quadro + atribuicao Hungara sobre IoU / aparencia |
| Filtro de Kalman | "Previsao de movimento" | Dinamica linear + covariancia para previsoes suaves de faixa e tratamento de oclusao |
| Algoritmo Hungaro | "Atribuicao otima" | Resolve o problema de correspondencia bipartida de custo minimo; scipy.optimize.linear_sum_assignment |
| ByteTrack | "Segunda passagem de baixa confianca" | Re-associa faixas sem correspondencia a deteccoes de baixa confianca para recuperar oclusoes curtas |
| DeepSORT | "SORT + aparencia" | Adiciona um atributo de ReID para correspondencia entre quadros; melhor para preservacao de ID |
| Banco de memoria | "Truque do SAM 2" | Atributos espaco-temporais por instancia armazenados ao longo dos quadros; a atencao cruzada substitui a associacao explicita |
| Object Multiplex | "Memoria compartilhada do SAM 3.1" | Memoria unica compartilhada com consultas por instancia para rastreamento rapido de muitos objetos |
| HOTA | "Metrica moderna de rastreamento" | Decompoe em precisao de deteccao e de associacao; padrao da comunidade |
Leitura Adicional
- SORT (Bewley et al., 2016) — o artigo minimo de rastreamento por deteccao
- DeepSORT (Wojke et al., 2017) — adiciona atributo de aparencia
- ByteTrack (Zhang et al., 2022) — segunda passagem de baixa confianca
- BoT-SORT (Aharon et al., 2022) — compensacao de movimento de camera
- HOTA (Luiten et al., 2020) — metrica de rastreamento decomposta
- Segmentacao de video SAM 2 (Meta, 2024) — rastreador baseado em memoria
- SAM 3.1 Object Multiplex (Meta, marco de 2026)