forked from dsutanto/zenai-kpc-python
Add Snapshot
This commit is contained in:
1 parent
3b8fb0c312
commit
210df4a038
5 files changed
+359
-4
No files matched your search
@@ -72,6 +72,20 @@ DAILY_CUTOFF_TIME = os.getenv("DAILY_CUTOFF_TIME", "20:00")
|
||||
EXPORT_CSV = os.getenv("EXPORT_CSV", "true").lower() == "true"
|
||||
CROSS_CSV = os.getenv("CROSS_CSV", f"{OUTPUT_DIR}/crossings.csv")
|
||||
|
||||
# Save an annotated frame snapshot each time an object crosses a line and the
|
||||
# counter increases.
|
||||
SAVE_CROSS_SNAPSHOT = os.getenv("SAVE_CROSS_SNAPSHOT", "false").lower() == "true"
|
||||
# Also save one snapshot the first time each object is detected (before it crosses),
|
||||
# named with the same track id so it can be correlated with the crossing snapshot.
|
||||
SAVE_DETECT_SNAPSHOT = os.getenv("SAVE_DETECT_SNAPSHOT", "false").lower() == "true"
|
||||
CROSS_SNAPSHOT_DIR = os.getenv("CROSS_SNAPSHOT_DIR", f"{OUTPUT_DIR}/snapshots")
|
||||
CROSS_SNAPSHOT_QUALITY = int(os.getenv("CROSS_SNAPSHOT_QUALITY", "85"))
|
||||
# Retention: delete oldest snapshots when either limit is exceeded (0 = disabled).
|
||||
CROSS_SNAPSHOT_MAX_FILES = int(os.getenv("CROSS_SNAPSHOT_MAX_FILES", "1000"))
|
||||
CROSS_SNAPSHOT_MAX_AGE_DAYS = float(os.getenv("CROSS_SNAPSHOT_MAX_AGE_DAYS", "7"))
|
||||
# Run the cleanup sweep at most every N seconds to limit filesystem scans.
|
||||
CROSS_SNAPSHOT_CLEANUP_SEC = int(os.getenv("CROSS_SNAPSHOT_CLEANUP_SEC", "60"))
|
||||
|
||||
RATE_WINDOW_SEC = int(os.getenv("RATE_WINDOW_SEC", "60"))
|
||||
WARMUP_FRAMES = int(os.getenv("WARMUP_FRAMES", "30"))
|
||||
RECONNECT_DELAY_SEC = int(os.getenv("RECONNECT_DELAY_SEC", "3"))
|
||||
@@ -756,6 +770,23 @@ def prune_stale_tracks(tracked, now_mono):
|
||||
del tracked[tid]
|
||||
|
||||
|
||||
def cleanup_snapshots(snapshot_dir, max_files, max_age_days):
|
||||
"""Delete oldest / expired crossing snapshots to bound disk usage."""
|
||||
d = Path(snapshot_dir)
|
||||
if not d.is_dir():
|
||||
return
|
||||
files = sorted(d.rglob("*.jpg"), key=lambda p: p.stat().st_mtime)
|
||||
if max_age_days > 0:
|
||||
cutoff = time.time() - max_age_days * 86400
|
||||
for p in list(files):
|
||||
if p.stat().st_mtime < cutoff:
|
||||
p.unlink(missing_ok=True)
|
||||
files.remove(p)
|
||||
if max_files > 0 and len(files) > max_files:
|
||||
for p in files[: len(files) - max_files]:
|
||||
p.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def overlay_rect(img, x1, y1, x2, y2, color, alpha=0.65):
|
||||
x1, y1 = max(0, x1), max(0, y1)
|
||||
x2, y2 = min(img.shape[1], x2), min(img.shape[0], y2)
|
||||
@@ -1036,6 +1067,8 @@ def run():
|
||||
recent_cross_in = deque()
|
||||
recent_cross_out = deque()
|
||||
|
||||
detect_snapshot_ids = set()
|
||||
|
||||
object_cross_flash1 = {}
|
||||
object_cross_flash2 = {}
|
||||
line_pulse = count_in_pulse = count_out_pulse = 0
|
||||
@@ -1049,6 +1082,7 @@ def run():
|
||||
crossing_times = deque()
|
||||
counter_in = 0
|
||||
counter_out = 0
|
||||
last_snapshot_cleanup = 0.0
|
||||
|
||||
cap, w, h, fps = connect_stream(SOURCE)
|
||||
if cap is None:
|
||||
@@ -1096,6 +1130,8 @@ def run():
|
||||
elapsed = now - session_start
|
||||
mono = time.monotonic()
|
||||
object_crossed_frame = False
|
||||
cross_events_frame = []
|
||||
detect_events_frame = []
|
||||
|
||||
skip_inference = False
|
||||
if MOTION_DETECTION_ENABLED:
|
||||
@@ -1179,6 +1215,10 @@ def run():
|
||||
cx = object_cx_list[di]
|
||||
cy = object_cy_list[di]
|
||||
|
||||
if tid not in detect_snapshot_ids:
|
||||
detect_snapshot_ids.add(tid)
|
||||
detect_events_frame.append(tid)
|
||||
|
||||
if tid not in object_tracked:
|
||||
inherited = _inherit_prev(
|
||||
object_tracked, tid, cx, cy, mono, INHERIT_SEC, INHERIT_PX
|
||||
@@ -1243,6 +1283,7 @@ def run():
|
||||
]
|
||||
)
|
||||
object_crossed_frame = True
|
||||
cross_events_frame.append((tid, direction))
|
||||
crossing_times.append(mono)
|
||||
object_cross_flash1[tid] = CROSS_FLASH_FRAMES
|
||||
object_cross_flash2[tid] = CROSS_FLASH_FRAMES
|
||||
@@ -1334,6 +1375,41 @@ def run():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if (SAVE_DETECT_SNAPSHOT and detect_events_frame) or (
|
||||
SAVE_CROSS_SNAPSHOT and cross_events_frame
|
||||
):
|
||||
try:
|
||||
ts = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
|
||||
if SAVE_DETECT_SNAPSHOT and detect_events_frame:
|
||||
detect_dir = Path(CROSS_SNAPSHOT_DIR) / "detect"
|
||||
detect_dir.mkdir(parents=True, exist_ok=True)
|
||||
for tid in detect_events_frame:
|
||||
fname = f"{ts}_detect_id{tid}_f{frame_idx}.jpg"
|
||||
cv2.imwrite(
|
||||
str(detect_dir / fname),
|
||||
frame,
|
||||
[cv2.IMWRITE_JPEG_QUALITY, CROSS_SNAPSHOT_QUALITY],
|
||||
)
|
||||
if SAVE_CROSS_SNAPSHOT and cross_events_frame:
|
||||
cross_dir = Path(CROSS_SNAPSHOT_DIR) / "cross"
|
||||
cross_dir.mkdir(parents=True, exist_ok=True)
|
||||
for tid, direction in cross_events_frame:
|
||||
fname = f"{ts}_{direction}_id{tid}_f{frame_idx}.jpg"
|
||||
cv2.imwrite(
|
||||
str(cross_dir / fname),
|
||||
frame,
|
||||
[cv2.IMWRITE_JPEG_QUALITY, CROSS_SNAPSHOT_QUALITY],
|
||||
)
|
||||
if now - last_snapshot_cleanup >= CROSS_SNAPSHOT_CLEANUP_SEC:
|
||||
cleanup_snapshots(
|
||||
CROSS_SNAPSHOT_DIR,
|
||||
CROSS_SNAPSHOT_MAX_FILES,
|
||||
CROSS_SNAPSHOT_MAX_AGE_DAYS,
|
||||
)
|
||||
last_snapshot_cleanup = now
|
||||
except Exception as exc:
|
||||
print(f"[{now_str()}] Failed to save snapshot: {exc}")
|
||||
|
||||
frame_idx += 1
|
||||
prune_stale_tracks(object_tracked, mono)
|
||||
|
||||
|
||||
Reference in new issue
Block a user