# ============================================================================= # ZenAI KTC — Edge RK3588 sack feeder zone counter + dashboard # Shared config for: counter_live_rknn.py + counter_dashboard.py # Copy to .env on device: cp config.env.example .env && nano .env # ============================================================================= # --- Core paths --- # Root output directory (logs, DB, video, CSV) OUTPUT_DIR=/opt/zenai-ktc-counter # SQLite database path for daily counter records & zone-entry logs DB_PATH=/opt/zenai-ktc-counter/ktc_counter.db # JSON file persisting the current active counting day state STATE_FILE=/opt/zenai-ktc-counter/current_counter.json # --- Input source --- # RTSP / HTTP live stream, or a local video file path SOURCE=rtsp://user:pass@192.168.0.100:554/stream1 # FFmpeg capture options passed to cv2.VideoCapture (RTSP low-latency flags) OPENCV_FFMPEG_CAPTURE_OPTIONS=rtsp_transport;tcp|fflags;nobuffer|flags;low_delay # --- RKNN model --- # Path to exported .rknn model (YOLO format, e.g. yolo11n.rknn) MODEL_PATH=/opt/models/yolo9t.rknn # Input image size for the model (square, e.g. 320 → 320×320) IMGSZ=320 # Use FP16 inference on NPU (true/false); currently unused in ByteTrack variant HALF=false # NPU core mask: 1=core0, 2=core1, 3=core0+core1, 7=all three CORE_MASK=7 # Compute device index (reserved; not used at runtime) DEVICE=0 # --- YOLO decoder --- # Number of object classes the model outputs NUM_CLASSES=2 # Apply sigmoid to raw class scores (true/false); set true if model head uses BCE logits SCORE_SIGMOID=false # --- Detection --- # Confidence threshold – detections below this are discarded before NMS CONF=0.3 # NMS IoU threshold (0-1): boxes overlapping the top box by more than this are # suppressed. RAISE it (e.g. 0.6-0.7) if two close/overlapping objects are being # merged into one and their separate boxes get suppressed. Lower = more aggressive # merging. Default 0.45. NMS_IOU=0.45 # --- ByteTrack tracking --- # Detections with score >= this get priority matching in the first association stage TRACK_HIGH_THRESH=0.5 # Detections with score between this and TRACK_HIGH_THRESH are matched in the second stage TRACK_LOW_THRESH=0.1 # IoU threshold for the first-stage association (0–1). Higher = stricter overlap required TRACK_MATCH_THRESH=0.8 # Frames a track survives without a match before being permanently removed TRACK_BUFFER=30 # Minimum consecutive (or total) hits needed before a track is considered confirmed TRACK_MIN_HITS=3 # --- ID-switch counting guards --- # When a track's ID changes right at the counting line, one physical object can be # counted twice (two IDs cross) or missed (neither ID sees the full transition). # These two guards correct for that. # # Dedup guard (prevents double counting): after a zone entry, a second entry in # the SAME feeder within DEDUP_FRAMES frames and DEDUP_PX centroid pixels is # ignored (treated as the same object under a new ID). DEDUP_FRAMES=15 DEDUP_PX=60 # To DISABLE the dedup guard, set DEDUP_PX=-1 (distance check can never match). # # Inheritance guard (prevents missed counting): when a brand-new track appears, it # inherits the last position of a recently-seen nearby track (within INHERIT_SEC # seconds and INHERIT_PX horizontal pixels) so zone entry is still detected # across the ID switch. INHERIT_SEC=1.0 INHERIT_PX=60 # To DISABLE the inheritance guard, set INHERIT_PX=-1 (distance check can never match). # Per-zone cooldown / dwell are set on ZONE_i_COOLDOWN_SEC and ZONE_i_DWELL_SEC below. # --- Display --- # Site name shown on the dashboard header SITE_NAME=ZenAi # --- Object class names --- # Camera / location identifier shown in HUD and stored in DB CAMERA_NAME=ZenAi # Label used for batch grouping in the database OBJECT_LABEL=karung # Class name for the counted object (must match model class order) CLASS_OBJECT=karung # Model class ID for the object being counted (default 0) OBJECT_CLASS_ID=0 # Feeder / zone display names come from ZONE_i_NAME (see zones below). # Verbose tracking debug (inherit / dup / cooldown / per-frame dets) DEBUG_TRACKING=false # --- Counting zones (1…N) --- # Rectangular zones. Count once after dwell. Absolute pixels win over fracs. ZONE_COUNT=1 ZONE_1_NAME=kandang_bawah_feeder ZONE_1_X1= ZONE_1_Y1= ZONE_1_X2= ZONE_1_Y2= ZONE_1_X1_FRAC=0.10 ZONE_1_Y1_FRAC=0.10 ZONE_1_X2_FRAC=0.90 ZONE_1_Y2_FRAC=1.00 ZONE_1_COOLDOWN_SEC=3 ZONE_1_DWELL_SEC=2 # Example two-zone setup (uncomment + set ZONE_COUNT=2): # ZONE_COUNT=2 # ZONE_1_NAME=kandang_bawah_feeder_kiri # ZONE_1_X1_FRAC=0.02 # ZONE_1_Y1_FRAC=0.10 # ZONE_1_X2_FRAC=0.440 # ZONE_1_Y2_FRAC=1.00 # ZONE_1_COOLDOWN_SEC=3 # ZONE_1_DWELL_SEC=2 # ZONE_2_NAME=kandang_bawah_feeder_kanan # ZONE_2_X1= # ZONE_2_Y1= # ZONE_2_X2= # ZONE_2_Y2= # ZONE_2_X1_FRAC=0.490 # ZONE_2_Y1_FRAC=0.10 # ZONE_2_X2_FRAC=0.98 # ZONE_2_Y2_FRAC=1.00 # ZONE_2_COOLDOWN_SEC=3 # ZONE_2_DWELL_SEC=2 # --- Counting day management --- # Daily cutoff time (HH:MM) – a new counting day starts after this time and the # previous day's zone totals are finalized in the database. # CUTOFF_TIME is an alias used by the dashboard; DAILY_CUTOFF_TIME takes priority in counter_live_rknn.py. DAILY_CUTOFF_TIME=17:00 CUTOFF_TIME=17:00 # --- CSV export --- # Write per-crossing events to a CSV file (true/false) EXPORT_CSV=true # Path where the crossing CSV is written CROSS_CSV=/opt/batch-counter/crossings.csv # --- Crossing snapshots --- # Save an annotated frame image every time an object enters a feeder zone and the # counter increases (true/false, default: false). Written to /cross/ # (filename: __id_f.jpg) SAVE_CROSS_SNAPSHOT=false # Also save one snapshot the first time each object is detected, before it enters # a zone (true/false, default: false). Written to /detect/ with the same track id so # it can be correlated with the zone-entry snapshot # (filename: _detect_id_f.jpg) SAVE_DETECT_SNAPSHOT=false # Base directory for snapshots (detect/ and cross/ subfolders are created inside). # The dashboard reads this same path to display the snapshot gallery, so keep it # identical for both the counter and the dashboard. CROSS_SNAPSHOT_DIR=/opt/batch-counter/snapshots # JPEG quality for snapshots (1-100) CROSS_SNAPSHOT_QUALITY=85 # Retention: keep at most this many snapshot files (detect + cross combined); # oldest are deleted first (0 = unlimited) CROSS_SNAPSHOT_MAX_FILES=1000 # Retention: delete snapshots older than this many days (0 = never by age) CROSS_SNAPSHOT_MAX_AGE_DAYS=7 # Run the cleanup sweep at most once every N seconds CROSS_SNAPSHOT_CLEANUP_SEC=60 # --- Rate / performance --- # Enable motion detection pre-filter: skip inference on frames with no movement # (true/false, default: false), saving NPU/CPU load. Motion is measured by the # fraction of pixels that changed (localized-motion aware), NOT the whole-frame # average, so an object entering the edge of the frame is detected immediately. MOTION_DETECTION_ENABLED=false # Per-pixel intensity change (0-255) for a pixel to count as "moved". Lower = more # sensitive to subtle movement. Default 25. MOTION_PIXEL_DELTA=25 # Fraction of frame pixels (0-1) that must change to trigger inference. Lower = # more sensitive / detects smaller or farther objects sooner. Default 0.002 (0.2%). MOTION_MIN_AREA_FRAC=0.002 # Heartbeat: always run inference at least every N frames even with no detected # motion, so a slow or barely-moving object is never missed for long. Default 15. MOTION_HEARTBEAT_FRAMES=15 # (Deprecated) old whole-frame mean-difference threshold; no longer used. MOTION_THRESHOLD=5.0 # --- Runtime control (start/stop counting on the fly) --- # When true, the counter watches a JSON control file and pauses/resumes object # detection & counting based on its "counting" flag. The dashboard shows a # COUNTING ON/OFF toggle button that writes this file. When false, the counter # always counts and the dashboard hides the toggle. (true/false, default: false) CONTROL_ENABLED=false # Path to the shared control file. MUST be identical for the counter and the # dashboard so the toggle takes effect. Contents: {"counting": true|false} CONTROL_FILE=/opt/bytetrack-counter/control.json # Counting state to assume on startup / when the control file does not exist yet. CONTROL_DEFAULT_COUNTING=true # How often (seconds) the counter re-reads the control file. Default 1.0. CONTROL_POLL_SEC=1.0 # Optional TCP control socket (requires CONTROL_ENABLED=true). Lets you toggle # counting over the network with line commands. It updates the same control file, # so changes apply within CONTROL_POLL_SEC. (true/false, default: false) # Commands (newline-terminated): START|RESUME|ON, STOP|PAUSE|OFF, TOGGLE, STATUS # e.g. printf 'STOP\n' | nc 127.0.0.1 5090 CONTROL_SOCKET_ENABLED=false # Bind address for the control socket. Use 127.0.0.1 for local-only, 0.0.0.0 to # allow remote clients (no auth — protect with firewall / trusted network). CONTROL_SOCKET_HOST=127.0.0.1 # Control socket TCP port. CONTROL_SOCKET_PORT=5090 # Sliding window in seconds for computing the crossing rate (objects/minute) RATE_WINDOW_SEC=60 # Number of frames to discard at startup to let the stream buffer stabilise WARMUP_FRAMES=30 # Delay in seconds between stream reconnection attempts RECONNECT_DELAY_SEC=3 # Maximum reconnection attempts (0 = infinite) MAX_RECONNECT_ATTEMPTS=0 # Max stream-error log lines per outage (Cannot open / Stream dropped). # After this, further errors are silent until the stream recovers; then the # budget resets for the next outage. 0 = unlimited. STREAM_ERROR_LOG_MAX=3 # Seconds after which a tracked but unseen object is pruned from the active set TRACKED_PRUNE_SEC=300 # --- Video recording --- # Save annotated frames to segmented MP4 files (true/false) RECORD_VIDEO=false # Duration in seconds of each video segment file VIDEO_SEGMENT_SEC=3600 # Output video FPS (fallback if source FPS is unknown or ≤ 1) OUTPUT_FPS=15 # --- Live stream snapshot --- # Periodically write the latest annotated frame as JPEG for an external web server LIVE_STREAM_ENABLED=false # Path to the shared-memory snapshot file (served by nginx / lighttpd) LIVE_STREAM_FRAME_PATH=/dev/shm/zenai-ktc-counter/live_frame.jpg # JPEG quality (1–100) LIVE_STREAM_QUALITY=75 # Write the snapshot every N frames (lower = more frequent updates) LIVE_STREAM_EVERY_N=2 # --- Dashboard (counter_dashboard.py) --- # Flask secret key for session/cookie signing — change in production! SECRET_KEY=change-me-in-production # Bind address for the Flask web server DASHBOARD_HOST=0.0.0.0 # Listen port for the dashboard web UI DASHBOARD_PORT=5000 # Enable Flask debug mode (true/false) — auto-reloads on code changes; disable in production FLASK_DEBUG=false # Fallback name for the active counting-day JSON state file used by the dashboard CURRENT_COUNTER_PATH=/opt/zenai-ktc-counter/current_counter.json