Ultralytics YOLO27: Trag dich in die Warteliste ein.
Get Started
Ultralytics YOLO in der Produktion·Tracking und Pipelines erstellen·Lektion 6/9
LektionFortgeschrittene

Zählungen, Wärmekarten und Geschwindigkeitsschätzung

Erstelle auf Basis von Tracking Linienzählungen, Belegungsmessungen für Zonen, Wärmekarten und Geschwindigkeitsschätzungen und kalibriere sie zuverlässig.

Die meisten Bildverarbeitungssysteme im Produktiveinsatz bestehen nicht nur aus model.predict(). Sie kombinieren einen Tracker mit etwas Geometrie: einer Linie, die Objekte überqueren, einer Zone, in der sie sich aufhalten, und einem Maßstab, der Pixel in Meter umrechnet. Bei der Geometrie passieren die Fehler: Im Democlip dieser Lektion schätzt ein Geschwindigkeitssystem die Gehgeschwindigkeit von Fußgängern auf 1 km/h, also ein Viertel ihres tatsächlichen Tempos, weil eine Zahl geraten wurde.

Lernergebnis

Erstelle anhand eines echten Videos eine Linienzählung, eine Zonenbelegung, eine Wärmekarte und eine Geschwindigkeitsschätzung und kalibriere die Geschwindigkeit anhand eines bekannten Referenzwerts.

Schnellkurs
Wenn du dich bereits auskennst, findest du hier die Kurzfassung.
  1. Linienüberquerung: solutions.ObjectCounter(region=[p1, p2]); überquert der Mittelpunkt eines Tracks die Linie, wird IN oder OUT hochgezählt.

  2. Zonenbelegung: Mit einem Test pro verfolgter Box wird ermittelt, welche Objekte sich gerade innerhalb des Polygons befinden.

  3. Wärmekarte: solutions.Heatmap sammelt, an welchen Stellen sich verfolgte Objekte aufhalten.

  4. Geschwindigkeit: Pixelverschiebung × Meter pro Pixel ÷ Zeit. Miss den Maßstab in der Szene und überprüfe die tatsächliche FPS.

Praxisnah

Tracking und Geometrie#

Jede Lösungsklasse in ultralytics.solutions führt intern den Tracking-Modus aus und wendet dann ihre eigene Geometrie auf den Track-Verlauf an. Installiere die Zusatzpakete einmalig mit pip install "ultralytics[solutions]".

KlasseGeometrieWichtige Ergebnisse
ObjectCounter2 Punkte = Linie, 3+ Punkte = Polygonin_count, out_count, classwise_count
RegionCounterEin oder mehrere benannte Polygoneregion_counts
HeatmapSammelt Boxflächen im Zeitverlaufplot_im (zuzüglich Zählungen, wenn eine Region angegeben ist)
SpeedEstimatorTrack-Verschiebung × meter_per_pixelspeed_dict (km/h pro Track-ID)

Lösungen verwenden standardmäßig tracker="tracktrack.yaml"; übergib den Tracker, den du in der vorherigen Lektion validiert hast.

Linienüberquerungen zählen#

Ultralytics YOLO zählt Pakete auf einem Lagerförderband: Jede Box hat eine Track-ID (#013 bis #018), und der IN-Zähler zeigt 12 an der Stelle, an der die Boxen die zyanfarbene Zähllinie überqueren

Eine Zähllinie ist ein Liniensegment. Ein Objekt wird einmal gezählt, wenn der Mittelpunkt seiner Box zwischen zwei Bildern die Linie überquert. Die Seite, von der es kommt, bestimmt IN oder OUT. Deshalb ist Tracking beim Förderband oben nötig: Ohne ID würde Box #015 in jedem Bild erneut gezählt, in dem sie die Linie berührt.

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

In den zwei Sekunden des Clips überquerten vier Menschen die Mitte des Platzes, zwei in jede Richtung. Mit der TrackTrack-Standardkonfiguration meldet dasselbe Skript in=0 out=1, denn weniger Tracks bedeuten weniger Überquerungen. Öffne counted.mp4 und überprüfe jede Überquerung mit eigenen Augen, bevor du einer Zahl vertraust.

Ultralytics YOLO zählt Linienüberquerungen auf einer geteilten Straße: Verfolgte Autos und ein Lkw überqueren eine zyanfarbene Linie, Pfeile zeigen die Richtung an und die Fahrzeugzählung beträgt 24 hinein und 18 hinaus

Auf einer Straße teilt dieselbe Linie den Verkehr nach Fahrtrichtung auf: Aufwärts fahrende Fahrzeuge werden als IN gezählt, abwärts fahrende als OUT. Platziere die Linie dort, wo Objekte sie sauber und im rechten Winkel überqueren, weit entfernt von Bildrändern, Verdeckungen und dem hinteren Straßenabschnitt, wo die Boxen sehr klein sind.

Belegung einer Zone zählen#

Eine Polygonzone beantwortet die Frage: „Wie viele Objekte befinden sich gerade darin?“ Die Anleitung zur Bereichszählung enthält eine fertige RegionCounter; im Kern wird für jede verfolgte Box geprüft, ob ein Punkt innerhalb des Polygons liegt:

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] ist der Mittelpunkt jeder Box. Für Warteschlangen und Verweildauer speicherst du pro ID einen Zeitstempel dafür, wann das Objekt die Zone betreten hat.

Geschwindigkeit schätzen#

Geschwindigkeitsschätzung auf einer Autobahn: Jedes verfolgte Auto und jeder Lkw ist mit der geschätzten Geschwindigkeit in km/h beschriftet (48 bis 65) und hinterlässt eine kurze Bewegungsspur

Jede Beschriftung oben ergibt sich aus der Spur hinter dem Fahrzeug: der Pixelstrecke, die sein Track über eine bekannte Anzahl von Bildern zurückgelegt hat. Geschwindigkeit = Pixelstrecke × Meter pro Pixel ÷ verstrichene Zeit. Zwei Eingaben bestimmen, ob das Ergebnis stimmt:

  1. Meter pro Pixel. Miss eine bekannte Länge in der Szene (eine Bodenfliese, eine Fahrbahnmarkierung, einen Parkplatz) in der Tiefe, in der sich die Objekte bewegen. Bei schräg ausgerichteten Kameras kannst du stattdessen die Bodenebene mit einer Homografie aus vier Punkten abbilden.
  2. Die tatsächliche Aufnahmebildrate. Die Geschwindigkeit skaliert mit der FPS; neu kodiertes oder verlangsamtes Videomaterial skaliert jede Geschwindigkeit um denselben Faktor.

Diese Schleife misst den unteren Mittelpunkt jeder Box (dort, wo das Objekt den Boden berührt) und mittelt jede Spur über 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

Menschen gehen mit 4–5 km/h. Die 2,1 km/h zeigen also, dass die beispielhafte Skalierung für diese Szene falsch ist: Der Wert pixels_per_meter wurde hier nicht gemessen. Genau diese Plausibilitätsprüfung ist entscheidend: den Median mit einer Geschwindigkeit vergleichen, die du kennst.

solutions.SpeedEstimator verpackt dieselbe Idee für den Produktionseinsatz: Es misst den Mittelpunkt der Box jeder Spur über max_hist Frames, rechnet mit meter_per_pixel um und legt pro ID eine Geschwindigkeit fest (begrenzt auf 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

Wieder um etwa den Faktor 4 daneben, aus demselben Grund: Die Skalierung wurde anhand der Körpergröße geschätzt. Der Boxmittelpunkt eignet sich für Kameras mit Blick von oben wie diese; bei schräg ausgerichteten Kameras solltest du die Schleife mit dem unteren Mittelpunkt oder eine Homografie bevorzugen.

Eine Heatmap erstellen#

Ultralytics YOLO-Heatmap einer Stadtautobahn: Eine blau-rote Überlagerung ist auf den meistbefahrenen Spuren am hellsten; Boxen markieren Autos und Lastwagen, daneben steht die laufende Zählung von 12 Autos und 2 Lastwagen

Eine Heatmap fügt in jedem Frame unter jeder verfolgten Box Wärme hinzu, sodass stark frequentierte Fahrspuren oder Gänge am hellsten leuchten. Gib ihr region, und sie zählt auch Objekte – wie oben auf der Autobahn:

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)  # letzter Frame mit der akkumulierten Wärmeüberlagerung

Bei stundenlangem Videomaterial zeigt dieselbe Überlagerung überfüllte Gänge, ungenutzte Bereiche und die Wege, die Menschen tatsächlich nehmen. Trainingsüberwachung und eine Streamlit-Oberfläche für Live-Inferenz basieren auf denselben Bausteinen.

Stolperfallen#

Das Modell zweimal pro Frame ausführen

Symptom: Eine Zähler-Pipeline läuft mit der Hälfte der erwarteten FPS. Lösung: ObjectCounter, Heatmap und SpeedEstimator erkennen und verfolgen Objekte intern. Verpacke sie nicht in eine eigene model.track()-Schleife und übergib r.orig_img; übergib ihnen stattdessen rohe Frames.

Einer unkalibrierten Geschwindigkeit vertrauen

Symptom: Fußgänger mit 1 km/h oder Autos mit 300 km/h. Lösung: Miss meter_per_pixel anhand einer bekannten Länge, an der sich Objekte bewegen, bestätige die tatsächliche FPS-Zahl der Kamera und vergleiche den Median mit einer bekannten Geschwindigkeit (Gehgeschwindigkeit oder ausgeschriebenes Tempolimit).

Bei schräg ausgerichteten Kameras den unteren Mittelpunkt der Box verwenden

Der geometrische Mittelpunkt steigt, wenn sich ein Objekt der Kamera nähert und seine Box größer wird. Der untere Mittelpunkt entspricht annähernd dem Punkt, an dem das Objekt den Boden berührt, sodass die von dort gemessenen Entfernungen die tatsächliche Bewegung viel genauer wiedergeben.

Ausprobieren

Verwende ein Video aus deinem eigenen Anwendungsbereich (oder den Demo-Clip), erstelle den Linienzähler und überprüfe jeden Übertritt in counted.mp4 visuell. Miss dann eine reale Länge in der Szene, berechne die Skalierung mit dem Lösungsskript, führe speed_loop.py erneut aus und vergleiche die Median-Geschwindigkeit mit einem bekannten Referenzwert. Gib die Zählungen, die Skalierung sowie die Median-Geschwindigkeiten vor und nach der Anpassung an.

Abgeschlossen, wenn
Du hast die Lektion abgeschlossen, wenn alle folgenden Punkte zutreffen.
  • Die IN- und OUT-Zählungen deines Linienzählers stimmen mit einer manuellen Zählung desselben Clips überein.

  • Deine Geschwindigkeitsschätzung basiert auf einer in der Szene gemessenen Skalierung, und ihr Median liegt höchstens 25 % von einer bekannten Referenzgeschwindigkeit entfernt.

  • Du kannst erklären, warum bei einer schräg ausgerichteten Kamera der untere Mittelpunkt der Box der richtige Punkt für die Geschwindigkeitsmessung ist.

Lösung anzeigen

Kalibriere anhand eines Objekts, dessen Größe du im Bild messen kannst, und prüfe das Ergebnis anschließend auf Plausibilität:

# 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

Trage pixels_per_meter in speed_loop.py ein (oder meter_per_pixel in SpeedEstimator) und führe das Skript erneut aus. Wenn die angezeigten Gehgeschwindigkeiten weiterhin deutlich von 4–5 km/h abweichen, wurde die Skalierung in der falschen Tiefe gemessen oder die FPS sind falsch: Ein auf halbe Geschwindigkeit verlangsamter Clip halbiert jeden Messwert. Beim Demo-Clip verwendeten beide Skripte dieselbe geschätzte Skalierung (0,03 m pro Pixel entsprechen 33 Pixeln pro Meter) und zeigten um den Faktor 2–4 zu niedrige Werte. Entweder deckt ein Pixel hier mehr Fläche ab als angenommen, oder die tatsächliche Aufnahmebildrate des Clips beträgt nicht 30 FPS. Nur eine Messung in der Szene kann klären, was zutrifft.

Wie es weitergeht

Ein Modell und ein Stream lassen sich gut handhaben. Als Nächstes: viele Streams parallel und die damit einhergehende Disziplin der threadsicheren Inferenz.