3.7 KiB
3.7 KiB
SPEC — ByteTrack Counter C++
§G — Goal
Real-time object detection + tracking + line-cross counting pipeline on RK3588 NPU. Two-class (ayam|talenan). Ayam crossing → increment batch count. Talenan crossing → close batch. Persist to SQLite + JSON.
§C — Constraints
- RK3588 ARM64, RKNN NPU (librknnrt)
- Model
zenai_apc_cicalengka_20260609.rknn— dynamic input shape - C++17, OpenCV 4, Eigen3, SQLite3
- systemd service, root,
ProtectSystem=full - 2 classes:
ayam_class_id=0,talenan_class_id=1 - Single counting line, crossing direction
rtl|ltr|both - Shift-based counting: day boundary at
DAILY_CUTOFF_TIME
§I — Interfaces
env: .env KEY=VALUE file — all config per config.env.example
env: LIVE_STREAM_FRAME_PATH → JPEG snapshot path (/dev/shm/bytetrack-counter/live_frame.jpg)
cmd: bytetrack-counter <config.env>
db: SQLite batches(id,counting_date,batch_number,camera_name,object_label,count,start_time,end_time)
db: SQLite daily_summaries(id,counting_date,camera_name,object_label,total_count,total_batches)
file: JSON state — batch_number,count,start_time,last_detection_time,counted_event_ids[]
file: CSV crossing log — batch,frame,timestamp,chicken_id
sig: SIGTERM|SIGINT → shutdown_requested → graceful exit
§V — Invariants
V1: ∀ detection → score ≥ conf threshold before tracking
V2: rknn_set_input_shapes called before rknn_inputs_set (dynamic shape model !)
V3: ∀ track_id → counted ≤ once per batch (dedup via counted_event_ids)
V4: talenan crossing → close batch only after ignore_batch_label_timeout (30s) from batch start
V5: batch (count < min_object | duration < min_duration) → discard ⊥ persist
V6: batch inactivity ≥ batch_timeout (300s) → auto-close
V7: counting_date rolls at DAILY_CUTOFF_TIME → cutoff_watcher thread
V8: ayam & talenan → separate ByteTrack instances (match_thresh_0=0.8, match_thresh_1=0.6)
V9: live_stream_enabled → parent dir of LIVE_STREAM_FRAME_PATH created @ startup
V10: stale track (> TRACKED_PRUNE_SEC unseen) → prune from external tracking maps
V11: DIRECTORY for LIVE_STREAM_FRAME_PATH must exist — created by code and by tmpfiles.d on boot (/dev/shm is volatile)
§T — Tasks
id|status|task|cites
T1|x|YOLO inference via RKNN NPU (letterbox, BGR2RGB, coordinate rescale)|V1,V2
T2|x|ByteTrack Kalman filter + two-stage IoU matching|V8
T3|x|Line-cross detection (rtl|ltr|both) with prev→current cx|V3
T4|x|Batch persistence: SQLite + JSON state file|V5,V6
T5|x|Batch dedup via counted_event_ids[]|V3
T6|x|Talenan-closes-batch logic with ignore-on-start timeout|V4
T7|x|Batch timeout thread (generational counter, detached)|V6
T8|x|Daily cutoff watcher thread|V7
T9|x|Optional motion detection skip-inference|C.motion
T10|x|Config from .env file (get_env with defaults)|I.env
T11|x|Drawing: HUD, hero count, line, boxes, popups, skeleton|-
T12|x|CsvLogger for crossing events|I.file
T13|x|Live JPEG snapshot to shared memory|V9
T14|x|systemd service with ProtectSystem + ReadWritePaths|I.cmd
T15|x|Graceful shutdown: SIGTERM → close batch → save state|I.sig
T16|x|Video per batch recording (config exists, impl ⊥ in C++) to shared memory for speed reason. To identify batch is between talenan (index=1), save with different name. Once completed, spawn new process (non blocking) to move recorded file to final destination (from config env). To identify first batch, record and wait X seconds (X default is 10 seconds, can be defined in config env), until first ayam (index=0) detected|-
T17|~?|Python DEBUG_TRACKING equivalent (per-frame score/track dump)|-
§B — Bugs
id|date|cause|fix
(base empty — distilled from code, no bug history)