Ultralytics YOLO27: Iscriviti alla lista d'attesa.
Get Started
Ultralytics YOLO in produzione·Traccia e crea pipeline·Lezione 6/9
Lezioneintermedio

Conteggi, mappe di calore e stima della velocità

Crea contatori di attraversamento, conteggi di occupazione delle zone, mappe di calore e stime della velocità basati sul tracciamento, e calibrali in modo rigoroso.

La maggior parte dei sistemi di visione in produzione non è model.predict(). È composta da un tracker e un po' di geometria: una linea che gli oggetti attraversano, una zona che occupano, una scala che converte i pixel in metri. È nella geometria che i risultati diventano errati: lo stimatore di velocità sulla clip dimostrativa di questa lezione indica 1 km/h per i pedoni che camminano, un quarto della loro velocità reale, a causa di un solo valore ipotizzato.

Esito

Crea un contatore di attraversamento, un conteggio di zona, una mappa di calore e una stima della velocità su un video reale, quindi calibra la velocità rispetto a un riferimento noto.

Percorso rapido
Se sai già come muoverti, ecco la versione breve.
  1. Attraversamento di una linea: solutions.ObjectCounter(region=[p1, p2]); il centro di una traccia che attraversa la linea incrementa IN o OUT.

  2. Occupazione di una zona: un test di inclusione in un poligono per ogni riquadro tracciato indica quali oggetti si trovano all'interno in questo momento.

  3. Mappa di calore: solutions.Heatmap accumula i punti in cui gli oggetti tracciati trascorrono del tempo.

  4. Velocità: spostamento in pixel × metri per pixel ÷ tempo. Misura la scala nella scena e verifica gli FPS effettivi.

Pratico

Tracciamento e geometria#

Ogni classe di soluzione in ultralytics.solutions esegue internamente la modalità di tracciamento, quindi applica la propria geometria alla cronologia della traccia. Installa una sola volta i pacchetti aggiuntivi con pip install "ultralytics[solutions]".

ClasseGeometriaRisultati principali
ObjectCounter2 punti = linea, 3 o più punti = poligonoin_count, out_count, classwise_count
RegionCounterUno o più poligoni denominatiregion_counts
HeatmapAccumula nel tempo le aree dei riquadriplot_im (più i conteggi se viene specificata una regione)
SpeedEstimatorSpostamento della traccia × meter_per_pixelspeed_dict (km/h per ID della traccia)

Le soluzioni usano tracker="tracktrack.yaml" per impostazione predefinita; passa il tracker che hai convalidato nella lezione precedente.

Conta gli attraversamenti della linea#

Ultralytics YOLO conta i pacchi su un nastro trasportatore di magazzino: ogni scatola ha un ID di tracciamento (da #013 a #018) e il contatore IN segna 12 nel punto in cui le scatole attraversano la linea di conteggio ciano

Una linea di conteggio è un segmento. Un oggetto viene contato una sola volta, quando il centro del suo riquadro attraversa la linea tra due fotogrammi; il lato da cui proviene determina se viene conteggiato come IN o OUT. Ecco perché il nastro trasportatore qui sopra richiede il tracciamento: senza l'ID, la scatola #015 verrebbe conteggiata di nuovo a ogni fotogramma in cui tocca la linea.

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}}

Nei due secondi della clip, quattro persone hanno attraversato il centro della piazza: due in ciascuna direzione. Con la configurazione predefinita di TrackTrack, lo stesso script restituisce in=0 out=1, perché un numero inferiore di tracce comporta meno attraversamenti. Apri counted.mp4 e verifica a vista ogni attraversamento prima di fidarti del conteggio.

Contatore di attraversamenti di linea con Ultralytics YOLO su una strada divisa: auto e camion tracciati attraversano una linea ciano, le frecce indicano la direzione e il conteggio dei veicoli è di 24 in entrata e 18 in uscita

Su una strada, la stessa linea separa il traffico per direzione: i veicoli che attraversano verso l'alto vengono conteggiati come IN, quelli diretti verso il basso come OUT. Posiziona la linea dove gli oggetti la attraversano nettamente e ad angolo retto, lontano dai bordi del fotogramma, dagli ostacoli e dall'estremità lontana della strada, dove i riquadri sono minuscoli.

Conta l'occupazione di una zona#

Una zona poligonale risponde alla domanda «quanti oggetti si trovano all'interno in questo momento?». La guida al conteggio nelle regioni include un RegionCounter già pronto; alla base c'è un test di inclusione in un poligono per ogni riquadro tracciato:

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] è il centro di ogni riquadro. Per le code e i tempi di permanenza, conserva per ogni ID il timestamp del suo ingresso nella zona.

Stima la velocità#

Stima della velocità su un'autostrada: ogni auto e camion tracciato è etichettato con la velocità stimata in km/h (da 48 a 65) e una breve linea mostra il percorso recente

Ogni etichetta qui sopra deriva dalla traccia dietro al veicolo: la distanza percorsa dalla sua traccia in pixel in un numero noto di fotogrammi. Velocità = distanza in pixel × metri per pixel ÷ tempo trascorso. Due valori determinano se il risultato è corretto:

  1. Metri per pixel. Misura una lunghezza nota nella scena (una piastrella, la segnaletica di una corsia, un posto auto) alla profondità in cui si muovono gli oggetti. Per le telecamere inclinate, mappa invece il piano stradale con un'omografia a quattro punti.
  2. La frequenza di acquisizione effettiva. La velocità varia in proporzione agli FPS; se il filmato viene ricodificato o rallentato, tutte le velocità variano dello stesso fattore.

Questo ciclo misura dal centro inferiore di ogni riquadro (dove l'oggetto tocca il suolo) e calcola la media di ogni traccia su 10 frame:

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

Le persone camminano a 4–5 km/h, quindi 2.1 km/h indica che la scala d'esempio è errata per questa scena: il valore pixels_per_meter non è stato misurato qui. Questa verifica di buon senso, che confronta la mediana con una velocità che conosci, è l'abitudine che conta.

solutions.SpeedEstimator integra la stessa idea per la produzione: misura il centro del riquadro di ogni traccia su max_hist frame, converte con meter_per_pixel e assegna una sola velocità per ID (con un limite di 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

Di nuovo, una differenza di circa 4×, per lo stesso motivo: una scala stimata in base alle dimensioni del corpo. Il centro del riquadro va bene per telecamere dall'alto come questa; con telecamere inclinate, preferisci il ciclo basato sul centro inferiore o un'omografia.

Crea una mappa di calore#

Mappa di calore Ultralytics YOLO su un'autostrada cittadina: una sovrapposizione dal blu al rosso è più intensa sulle corsie più trafficate, con riquadri per auto e camion e un conteggio aggiornato di 12 auto e 2 camion

Una mappa di calore aggiunge intensità sotto ogni riquadro tracciato a ogni frame, così le corsie o i corridoi più trafficati risaltano maggiormente. Assegna un region e conterà anche gli oggetti, come nell'autostrada qui sopra:

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)  # ultimo frame con la sovrapposizione di calore accumulata

Su ore di filmato, la stessa sovrapposizione mostra i corridoi congestionati, le zone inutilizzate e i percorsi effettivamente seguiti dalle persone. Il monitoraggio degli allenamenti e un'interfaccia Streamlit per l'inferenza in tempo reale sono realizzati con gli stessi componenti.

Insidie#

Eseguire il modello due volte per fotogramma

Sintomo: una pipeline di conteggio funziona alla metà degli FPS previsti. Soluzione: ObjectCounter, Heatmap e SpeedEstimator eseguono internamente rilevamento e tracking. Non inserirli in un ciclo model.track() personalizzato passando r.orig_img; fornisci loro i fotogrammi grezzi.

Fidarsi di una velocità non calibrata

Sintomo: pedoni a 1 km/h o auto a 300 km/h. Soluzione: misura meter_per_pixel su una lunghezza nota lungo il percorso degli oggetti, verifica gli FPS effettivi della telecamera e confronta la mediana con una velocità nota (passo di camminata o limite indicato).

Usa il centro inferiore del riquadro con le telecamere angolate

Il centro geometrico si sposta verso l'alto quando un oggetto si avvicina alla telecamera e il suo riquadro si ingrandisce. Il centro inferiore approssima il punto in cui l'oggetto tocca terra, quindi le distanze misurate da lì seguono molto più fedelmente il movimento reale.

Provalo

Su un video del tuo dominio (o sulla clip dimostrativa), crea il contatore di attraversamenti e verifica a occhio ogni attraversamento in counted.mp4. Poi misura una lunghezza reale nella scena, calcola la scala con lo script della soluzione, esegui di nuovo speed_loop.py e confronta la velocità mediana con un riferimento noto. Consegna i conteggi, la scala e le velocità mediane prima e dopo.

Criteri di completamento
La lezione è completata quando tutte le seguenti condizioni sono soddisfatte.
  • I valori IN e OUT del tuo contatore corrispondono al conteggio manuale della stessa clip.

  • La stima della velocità usa una scala misurata nella scena e la mediana si discosta meno del 25% da una velocità di riferimento nota.

  • Sai spiegare perché, con una telecamera inclinata, il punto corretto per misurare la velocità è il centro inferiore del riquadro.

Mostra la soluzione

Calibra usando qualcosa che puoi misurare nel frame, poi verifica che il risultato sia plausibile:

# 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

Inserisci pixels_per_meter in speed_loop.py (oppure meter_per_pixel in SpeedEstimator) e riesegui. Se le velocità dei pedoni si discostano ancora molto da 4–5 km/h, la scala è stata misurata a una profondità errata oppure gli FPS non sono corretti: una clip rallentata a metà velocità dimezza ogni valore. Nella clip dimostrativa, entrambi gli script hanno usato la stessa scala stimata (0.03 m per pixel equivale a 33 pixel per metro) e hanno rilevato velocità inferiori di 2–4×; quindi un pixel qui potrebbe coprire più spazio del previsto oppure la frequenza di acquisizione effettiva della clip potrebbe non essere 30 FPS. Solo una misura nella scena può chiarire quale delle due ipotesi è corretta.

Prossimi passi

Un modello e uno stream sono gestibili. Prossimo argomento: più stream in parallelo e le pratiche di inferenza thread-safe necessarie.

Accedi per salvare i progressi