Ultralytics YOLO27: Inscreve-te na lista de espera.
Get Started
Ultralytics YOLO em produção·Rastrear e criar pipelines·Aula 6/9
Aulaintermediário

Contagem, mapas de calor e estimativa de velocidade

Cria contadores de linhas, ocupação de zonas, mapas de calor e estimativas de velocidade com base no rastreamento, e calibra-os de forma rigorosa.

A maioria dos sistemas de visão em produção não é model.predict(). São um rastreador e um pouco de geometria: uma linha que os objetos atravessam, uma zona que ocupam, uma escala que converte píxeis em metros. É na geometria que os resultados falham: um estimador de velocidade aplicado ao clipe de demonstração desta lição indica que peões a caminhar se deslocam a 1 km/h, um quarto da velocidade real, devido a um único valor estimado.

Resultado

Cria um contador de linhas, uma contagem por zona, um mapa de calor e uma estimativa de velocidade com vídeo real, e calibra a velocidade com uma referência conhecida.

Via rápida
Se já sabes orientar-te, aqui está a versão resumida.
  1. Passagem por linha: solutions.ObjectCounter(region=[p1, p2]); quando o centro de um rasto atravessa a linha, incrementa IN ou OUT.

  2. Ocupação de zona: um teste de ponto no polígono por cada caixa rastreada indica quem está dentro neste momento.

  3. Mapa de calor: solutions.Heatmap acumula as localizações onde os objetos rastreados passam mais tempo.

  4. Velocidade: deslocamento em píxeis × metros por píxel ÷ tempo. Mede a escala na cena e confirma os FPS reais.

Prático

Rastreamento e geometria#

Cada classe de solução em ultralytics.solutions executa internamente o modo de rastreamento e, em seguida, aplica a sua própria geometria ao histórico dos rastos. Instala as dependências adicionais uma vez com pip install "ultralytics[solutions]".

ClasseGeometriaPrincipais resultados
ObjectCounter2 pontos = linha, 3 ou mais pontos = polígonoin_count, out_count, classwise_count
RegionCounterUm ou mais polígonos nomeadosregion_counts
HeatmapAcumula as áreas das caixas ao longo do tempoplot_im (mais contagens, se for indicada uma região)
SpeedEstimatorDeslocamento do rasto × meter_per_pixelspeed_dict (km/h por ID de rasto)

As soluções usam tracker="tracktrack.yaml" por predefinição; passa o rastreador que validaste na lição anterior.

Conta as passagens por linha#

Contagem de encomendas num tapete rolante de armazém com Ultralytics YOLO: cada caixa tem um ID de rasto (#013 a #018) e o contador IN indica 12 no ponto em que as caixas atravessam a linha de contagem ciano

Uma linha de contagem é um segmento. Um objeto é contado uma vez, quando o centro da sua caixa atravessa a linha entre dois fotogramas; o lado de onde vem determina se é IN ou OUT. Por isso, o tapete rolante acima precisa de rastreamento: sem o ID, a caixa #015 seria contada de novo em cada fotograma em que tocasse na linha.

import cv2

from ultralytics import solutions
from ultralytics.utils.downloads import safe_download

video = safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4")
cap = cv2.VideoCapture(str(video))  # 640x360, 30 FPS, 62 frames
w, h, fps = (int(cap.get(p)) for p in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
writer = cv2.VideoWriter("counted.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))

counter = solutions.ObjectCounter(
    model="yolo26n.pt",
    region=[(0, 180), (640, 180)],  # two points: a horizontal line across the middle of the frame
    classes=[0],  # person
    tracker="bytetrack.yaml",
    verbose=False,
)

while True:
    ok, frame = cap.read()
    if not ok:
        break
    results = counter(frame)  # detect + track + count in one call
    writer.write(results.plot_im)

cap.release()
writer.release()
print(f"in={results.in_count} out={results.out_count} classwise={results.classwise_count}")
in=2 out=2 classwise={'person': {'IN': 2, 'OUT': 2}}

Quatro pessoas atravessaram o centro da praça nos dois segundos do clipe: duas em cada direção. Com a configuração predefinida do TrackTrack, o mesmo script indica in=0 out=1, porque menos rastos significam menos passagens. Abre counted.mp4 e verifica visualmente cada passagem antes de confiares num número.

Contador de passagens por linha com Ultralytics YOLO numa estrada dividida: carros e um camião rastreados atravessam uma linha ciano, com setas a indicar a direção e uma contagem de veículos de 24 a entrar e 18 a sair

Numa estrada, a mesma linha separa o tráfego por direção: os veículos que a atravessam para cima contam como IN; os que a atravessam para baixo, como OUT. Coloca a linha onde os objetos a atravessem claramente e em ângulo reto, longe dos limites do fotograma, de oclusores e do extremo da estrada, onde as caixas são pequenas.

Conta a ocupação de uma zona#

Uma zona poligonal responde à pergunta «quantos objetos estão dentro neste momento?». O guia de contagem por região inclui um RegionCounter pronto a usar; por baixo, é um teste de ponto no polígono por cada caixa rastreada:

from shapely.geometry import Point, Polygon

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
zone = Polygon([(100, 100), (500, 100), (500, 300), (100, 300)])  # pixel coordinates

peak = 0
for r in model.track(video, stream=True, tracker="bytetrack.yaml", classes=[0], verbose=False):
    if not r.boxes.is_track:
        continue
    inside = sum(zone.contains(Point(cx, cy)) for cx, cy in r.boxes.xywh[:, :2].tolist())
    peak = max(peak, inside)
print(f"peak occupancy: {peak} people inside the zone")
peak occupancy: 10 people inside the zone

xywh[:, :2] é o centro de cada caixa. Para filas e tempos de permanência, mantém um registo temporal por ID da hora a que entrou na zona.

Estima a velocidade#

Estimativa de velocidade numa autoestrada: cada carro e camião rastreado tem uma etiqueta com a velocidade estimada em km/h (48 a 65) e deixa um rasto de movimento curto

Cada etiqueta acima é calculada com base no rasto atrás do veículo: a distância percorrida pelo rasto em píxeis durante um número conhecido de fotogramas. Velocidade = distância em píxeis × metros por píxel ÷ tempo decorrido. Dois valores determinam se o resultado está correto:

  1. Metros por píxel. Mede um comprimento conhecido na cena (um ladrilho, uma marcação de faixa, um lugar de estacionamento) à profundidade a que os objetos se deslocam. Para câmaras inclinadas, mapeia antes o plano do solo com uma homografia de quatro pontos.
  2. A taxa real de captura. A velocidade varia com o FPS; se o vídeo for recodificado ou desacelerado, todas as velocidades variam pelo mesmo fator.

Este loop mede a partir do centro inferior de cada caixa (onde o objeto toca o chão) e calcula a média de cada trajetória ao longo de 10 frames:

from collections import defaultdict, deque

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
fps = 30  # the real capture rate, not the playback rate
pixels_per_meter = 33  # example: a 1.2 m floor feature spans 40 px; measure your own scene

prev = {}  # track ID -> last bottom-center point
window = defaultdict(lambda: deque(maxlen=10))  # about 0.3 s of speeds per track at 30 FPS
for r in model.track(video, stream=True, tracker="bytetrack.yaml", classes=[0], verbose=False):
    if not r.boxes.is_track:
        continue
    for (x1, y1, x2, y2), tid in zip(r.boxes.xyxy.tolist(), r.boxes.id.int().tolist()):
        cx, cy = (x1 + x2) / 2, y2  # bottom-center sits on the ground plane
        if tid in prev:
            px, py = prev[tid]
            meters = ((cx - px) ** 2 + (cy - py) ** 2) ** 0.5 / pixels_per_meter
            window[tid].append(meters * fps * 3.6)  # km/h
        prev[tid] = (cx, cy)

smoothed = sorted(sum(s) / len(s) for s in window.values() if len(s) == s.maxlen)
print(f"{len(smoothed)} tracks; median {smoothed[len(smoothed) // 2]:.1f} km/h")
29 tracks; median 2.1 km/h

As pessoas caminham a 4–5 km/h, então 2,1 km/h indica que a escala de exemplo está errada para esta cena: o valor pixels_per_meter não foi medido aqui. Essa verificação de plausibilidade, comparando a mediana com uma velocidade conhecida, é o hábito que importa.

solutions.SpeedEstimator empacota a mesma ideia para produção: mede o centro da caixa de cada trajetória ao longo de max_hist frames, converte com meter_per_pixel e fixa uma velocidade por ID (com limite de max_speed):

import cv2

from ultralytics import solutions
from ultralytics.utils.downloads import safe_download

video = safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4")
cap = cv2.VideoCapture(str(video))
speed = solutions.SpeedEstimator(
    model="yolo26n.pt",
    classes=[0],
    tracker="bytetrack.yaml",
    fps=30,  # the real capture rate, not the playback rate
    meter_per_pixel=0.03,  # a guess: a ~0.45 m shoulder width spans ~15 px here
    max_hist=15,  # 0.5 s of history before a speed is locked
    verbose=False,
)

speeds = {}
while True:
    ok, frame = cap.read()
    if not ok:
        break
    speeds.update(speed(frame).speed_dict)  # {track_id: km/h} once locked
cap.release()

kmh = sorted(speeds.values())
print(f"{len(kmh)} tracks; median {kmh[len(kmh) // 2]} km/h, range {kmh[0]}-{kmh[-1]} km/h")
25 tracks; median 1 km/h, range 0-4 km/h

Novamente, cerca de 4× de diferença, pelo mesmo motivo: uma escala estimada pelo tamanho do corpo. O centro da caixa funciona bem para câmeras aéreas como esta; em câmeras inclinadas, prefira o loop do centro inferior ou uma homografia.

Acumule um mapa de calor#

Mapa de calor do Ultralytics YOLO numa rodovia urbana: uma sobreposição de azul a vermelho é mais intensa nas faixas mais movimentadas, com caixas de carros e caminhões e uma contagem contínua de 12 carros e 2 caminhões

Um mapa de calor adiciona calor sob cada caixa rastreada em cada frame, fazendo com que as faixas ou corredores com mais tráfego brilhem mais. Forneça um region e ele também contará objetos, como na rodovia acima:

import cv2

from ultralytics import solutions
from ultralytics.utils.downloads import safe_download

video = safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4")
cap = cv2.VideoCapture(str(video))
heatmap = solutions.Heatmap(
    model="yolo26n.pt",
    classes=[0],
    tracker="bytetrack.yaml",
    colormap=cv2.COLORMAP_PARULA,
    verbose=False,
)

while True:
    ok, frame = cap.read()
    if not ok:
        break
    results = heatmap(frame)
cap.release()
cv2.imwrite("heatmap.jpg", results.plot_im)  # último frame com a sobreposição de calor acumulada

Ao longo de horas de vídeo, a mesma sobreposição mostra corredores congestionados, zonas não utilizadas e os caminhos que as pessoas realmente percorrem. O monitoramento de treinos e uma interface de inferência ao vivo com Streamlit são construídos com os mesmos componentes.

Armadilhas#

Executar o modelo duas vezes por quadro

Sintoma: um pipeline de contagem funciona com metade do FPS esperado. Solução: ObjectCounter, Heatmap e SpeedEstimator fazem a detecção e o rastreamento internamente. Não os envolva no seu próprio loop model.track() nem passe r.orig_img; forneça quadros brutos.

Confiar em uma velocidade não calibrada

Sintoma: pedestres a 1 km/h ou carros a 300 km/h. Solução: meça meter_per_pixel usando um comprimento conhecido no local onde os objetos se movem, confirme o FPS real da câmera e compare a mediana com uma velocidade conhecida (ritmo de caminhada ou limite de velocidade sinalizado).

Use a parte inferior central da caixa em câmeras inclinadas

O centro geométrico sobe à medida que um objeto se aproxima da câmera e sua caixa aumenta. A parte inferior central aproxima o ponto em que o objeto toca o chão, então as distâncias medidas a partir dela acompanham o movimento real com muito mais precisão.

Experimente

Num vídeo do teu próprio domínio (ou no clipe de demonstração), cria o contador de linhas e verifica visualmente cada cruzamento em counted.mp4. Depois, mede um comprimento real na cena, calcula a escala com o script de solução, executa novamente speed_loop.py e compara a velocidade mediana com uma referência conhecida. Apresenta as contagens, a escala e as velocidades medianas antes e depois.

Concluído quando
Você concluiu a aula quando todos estes critérios forem atendidos.
  • As contagens IN e OUT do teu contador de linhas correspondem à contagem manual do mesmo clipe.

  • A tua estimativa de velocidade usa uma escala medida na cena, e a mediana está a menos de 25% de uma velocidade de referência conhecida.

  • Consegues explicar por que o centro inferior da caixa é o ponto certo para calcular a velocidade numa câmera inclinada.

Mostrar solução

Calibra com algo que possas medir no frame e, depois, verifica a plausibilidade:

# Two points along a floor feature whose real length you know (read them off a saved frame)
(x1, y1), (x2, y2) = (100, 300), (140, 300)
real_length_m = 1.2  # e.g. measured tile width or lane marking

pixels = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
print(f"pixels_per_meter = {pixels / real_length_m:.1f}, meter_per_pixel = {real_length_m / pixels:.4f}")
pixels_per_meter = 33.3, meter_per_pixel = 0.0300

Coloca pixels_per_meter em speed_loop.py (ou meter_per_pixel em SpeedEstimator) e executa novamente. Se as pessoas ainda aparecerem com velocidades muito diferentes de 4–5 km/h, a escala foi medida à profundidade errada ou o FPS está incorreto: um clipe desacelerado para metade da velocidade reduz todas as leituras pela metade. No clipe de demonstração, ambos os scripts usaram a mesma escala estimada (0,03 m por pixel são 33 pixels por metro) e indicaram velocidades 2–4× inferiores; portanto, ou cada pixel aqui cobre mais terreno do que o estimado ou a taxa real de captura do clipe não é de 30 FPS. Só uma medição na cena permite saber qual das hipóteses está correta.

Próximos passos

Um modelo, um fluxo: isso é viável. A seguir: vários fluxos em paralelo e as práticas de inferência segura para threads que isso exige.

Entre para salvar seu progresso