From c6c87be045b873a3f7aa36ca11bc1926c1aefbd8 Mon Sep 17 00:00:00 2001 From: Joachim Hummel Date: Mon, 1 Jun 2026 19:19:33 +0200 Subject: [PATCH] Fix: Zaehlen verhungert nach Stunden (unbegrenzte Zustaende) track_positions/counted_ids wuchsen im 24/7-Betrieb unbegrenzt: -> Speicherleck/Slowdown -> Bildrate bricht ein -> Linienueberquerungen werden nicht mehr erfasst (Einzel-Erkennung lief weiter). - Track-IDs altern jetzt (last-seen-Frame) und werden nach COUNT_FORGET_FRAMES aus track_positions UND counted_ids entfernt - bounded State -> kein Leck; loest auch Unterdrueckung bei spaeterer Track-ID-Wiederverwendung - Langzeit-Simulation (5000 Frames): 100/100 gezaehlt, State-Peak 4 Co-Authored-By: Claude Opus 4.8 (1M context) --- .env.example | 3 +++ app.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index be937b5..9fa9260 100644 --- a/.env.example +++ b/.env.example @@ -30,6 +30,9 @@ export COUNT_BAND_PX=45 # Entprellung gegen Doppelzaehlung bei Track-ID-Wechseln (Pixel-Radius / Frames). export COUNT_DEDUP_PX=60 export COUNT_DEDUP_FRAMES=12 +# 24/7-Betrieb: Track-IDs nach so vielen Frames ohne Sichtung vergessen +# (verhindert Speicherleck / "Zaehlen verhungert nach Stunden"). +export COUNT_FORGET_FRAMES=150 # --- MQTT (optional) ------------------------------------------------------- # Komplett abschaltbar: "false" -> App laeuft ohne Broker, sendet keine Events. diff --git a/app.py b/app.py index 2a108e5..e55b640 100644 --- a/app.py +++ b/app.py @@ -77,6 +77,11 @@ COUNT_BAND_PX = int(os.environ.get("COUNT_BAND_PX", "45")) # letzten COUNT_DEDUP_FRAMES Frames gezaehlten Punkt liegt. COUNT_DEDUP_PX = int(os.environ.get("COUNT_DEDUP_PX", "60")) COUNT_DEDUP_FRAMES = int(os.environ.get("COUNT_DEDUP_FRAMES", "12")) +# 24/7-Betrieb: Track-IDs, die so viele Frames nicht mehr gesehen wurden, +# werden aus track_positions UND counted_ids entfernt. Verhindert das +# unbegrenzte Wachsen der Zustaende (Speicherleck) und falsches Unterdruecken +# bei spaeterer Track-ID-Wiederverwendung. Wert > Verweildauer eines Fahrzeugs. +COUNT_FORGET_FRAMES = int(os.environ.get("COUNT_FORGET_FRAMES", "150")) # --- MQTT: ein Event pro Linienueberquerung (fuer n8n -> NocoDB) ----------- # Komplett abschaltbar: MQTT_ENABLED=false -> App laeuft ohne Broker/Events. @@ -341,6 +346,16 @@ def process_frame(frame, det_model, det_names, line_start, line_end, state, sour c for c in recent_counts if frame_idx - c[2] <= COUNT_DEDUP_FRAMES ] + # Garbage Collection: laengst verschwundene Track-IDs vergessen, damit + # track_positions/counted_ids im 24/7-Betrieb nicht unbegrenzt wachsen. + stale = [ + tid for tid, pos in track_positions.items() + if frame_idx - pos[2] > COUNT_FORGET_FRAMES + ] + for tid in stale: + track_positions.pop(tid, None) + counted_ids.discard(tid) + if results and results[0].boxes is not None and results[0].boxes.id is not None: boxes = results[0].boxes.xyxy.int().cpu().tolist() class_ids = results[0].boxes.cls.int().cpu().tolist() @@ -358,7 +373,7 @@ def process_frame(frame, det_model, det_names, line_start, line_end, state, sour # (a) Klassischer Segment-Schnitt: Punkt davor UND dahinter. if track_id in track_positions: - prev_x, prev_y = track_positions[track_id] + prev_x, prev_y, _ = track_positions[track_id] cv2.line(frame, (prev_x, prev_y), (center_x, center_y), (255, 100, 0), 2) if crossed_line((prev_x, prev_y), (center_x, center_y), line_start, line_end): crossed = True @@ -381,7 +396,7 @@ def process_frame(frame, det_model, det_names, line_start, line_end, state, sour publish_crossing(label_name, track_id, source) cv2.circle(frame, (center_x, center_y), 25, (0, 255, 0), 5) - track_positions[track_id] = (center_x, center_y) + track_positions[track_id] = (center_x, center_y, frame_idx) box_color = (0, 255, 0) if label_name in VEHICLE_CLASSES else (255, 0, 0) cv2.rectangle(frame, (x1, y1), (x2, y2), box_color, 2)