7.2 KiB
Architecture
Pipeline overview (v3, src/main.py)
Frame → TruckDetect → ROI → SackTrack → Stabilize → Filter → Count → Overlay/CSV
run() processes every frame (src/main.py:111). Truck detection is the exception:
it runs every 15 frames (TRUCK_DET_INTERVAL, src/main.py:130) because the truck
moves slowly and that model is heavy. All components are wired by dependency injection
in build_pipeline() (src/main.py:27) and communicate through the DTOs/protocols in
src/interfaces.py (Detection, FrameResult, StreamSource, Detector, Tracker,
Counter, BatchManager).
Per-frame sequence (src/main.py:132-202):
- Truck detect (every 15th frame) —
TruckDetector.detect(frame)→TruckROITracker.update(trucks)returns smoothedTruckROI(or holds last ROI). - Sync counting line to ROI —
counter.line_y/x_start/x_end = roi.*, so the line follows the truck instead of staying fixed. - Batch update (every 15th frame) —
batch_mgr.update(truck_present, …). - Track → Stabilize → Filter → Count (only while a batch is active):
ByteTrackTracker.update→BboxStabilizer.update→_filter_sacks_in_roi(keep sacks whose centroid-X is inside ROI) →LineCrossCounter.update→CSVLogger.log_eventper crossing. - Overlay —
DashboardOverlay.draw(...)→cv2.imshow.
Modules
Streaming (src/streaming.py)
VideoFileSource(path)— offline testing / validation.RTSPSource(url)— live camera via FFmpeg backend (cv2.CAP_FFMPEG).- Common interface:
open() / read() / release(),fps,frame_size.
Detection (src/detection.py)
SackDetector— YOLO segmentation model;_parsekeeps onlyclass_name == "sack"and attaches the mask. (Docstring notes persons are visible to the model but dropped.)TruckDetector— YOLO detection model; keeps all classes (single-classtruckmodel).- Both are single-responsibility; new model types are added as new classes.
Tracking (src/tracking.py)
ByteTrackTracker—YOLO.track(persist=True, tracker=cfg/tracker.yaml)(Ultralytics FastTrack, occlusion-aware: Kalman rollback on occlusion, enlarged search region, re-ID)._parsekeeps classes("sack", "truck")and attachestrack_id(boxes.id).reset()reloads the model weights — called on batch boundaries.
Stabilizer (src/stabilizer.py)
Per-track-ID fixes for worker occlusion / flicker:
- Jitter (10–50 px jumps) → EMA on bbox (
ema_alpha=0.35). - Dropout at the line (worker blocks sack 5–10 frames) → hold last smoothed bbox
for
max_hold_frames=10, with 0.85 confidence decay. - Height expansion spike (worker body merges into sack box) → clamp to
smooth_h * 1.5. - Height shrinkage (worker covers sack bottom) → clamp to
smooth_h * 0.70.
Truck ROI (src/truck_roi.py)
_pick_main_truck: largest detection whose center-X falls in the lane (0.35–0.80of frame width).- EMA smoothing (
alpha=0.15); holds last ROI for 5 missed updates (~3 s), then clears. - Counting line = truck top edge +
LINE_OFFSET_PX(= 0).
Counting (src/counting.py)
LineCrossCounter tracks the top edge (y1) of each stabilized sack bbox against a
zone band [line_y − margin, line_y + margin] (default margin 20 px):
- Track states per ID:
above(y1 < upper) /below(y1 > lower) / hold in band. - History is latched forever (
has_been_above/below) so a crossing is caught even if the track jumps over the line between low-FPS frames — no exact crossing frame needed. - Loading = ever-above now below; Unloading = ever-below now above.
- 3-layer dedup: (1) must have been on the opposite side first, (2) spatial radius 30 px / 3 s window, (3) one count per track-ID per direction.
- Detections with centroid-X outside
[line_x_start, line_x_end]are skipped. reset()on every new batch.
Batch lifecycle (src/batch.py)
4-state machine (BatchState):
IDLE ──truck in area──▶ TRUCK_STABILIZING ──stable 5 s──▶ COUNTING_SACKS
▲ │ truck gone 3 s │ ▲
│ └────────▶ IDLE │ │ sacks resume
│ │ │
│ 10 s no sack activity ▼ │
│ WAITING_FOR_ACTIVITY
│ (batch OPEN)
└────────────────── truck leaves ─────────────────────────────────┘
(batch finalized → BatchRecord → history)
update_truck(truck_detected, centroid, ts)drives IDLE / STABILIZING / WAITING.update_sacks(crossing_event, sacks_in_area, ts, …)drives COUNTING / WAITING; activity resumes the same batch instead of opening a new one.update(...)is a backward-compatible shim combining both.on_batch_start(id, ts)/on_batch_end(BatchRecord)callbacks —main.pyuses them to reset counter/stabilizer/ROI and to write the CSV row.- Tunables:
stabilize_seconds=5,stabilize_threshold_px=15,sack_idle_timeout=10,min_batch_duration=30,truck_gone_tolerance=3.
Dashboard overlay (src/dashboard.py)
Draws onto the frame: truck ROI box + confidence, counting-zone band + center line,
sack boxes with track_id + confidence and a green top-edge (y1) trigger marker,
stats panel (Loading / Unloading / Net / last-3-batch history), and a bottom state bar
(IDLE / stabilizing + progress / counting + duration-idle / waiting).
Config & logging
src/config.py— frozenConfigdataclass loaded from.env(load_config). Note: its variable names (LOCAL_RTSP,MODEL_SACK_PATH, …) differ from the production.envkeys (RTSP_URL, …) used bypredict.py— seeconfiguration.md.src/logger.py—CSVLoggerappendsbatch_summary.csv(batch_id,start,end,duration,loading,unloading,net) andsack_events.csv(timestamp,batch_id,sack_crossed,T-<id>,direction).
Production pipeline (predict.py)
Same stage order as v3 but implemented with shapely polygons (zones.json:
pallet/truck/counting), visual-similarity + Re-ID registries, static-frame debounce,
SQLite persistence, and live-frame publishing to /dev/shm for the dashboard. Uses the
repo src/ modules (imports at predict.py). Zero CLI flags = systemd behaviour;
dev flags (--source, --no-dashboard, …) are additive overrides — see
predict.py --help and docs/scripts.md. (init_db() and the http.server imports
are dead code — persistence goes through finalize_batch/save_active_batch_state,
dashboard integration is file-based.)
Retired (archive/, not imported)
predict_new.py— duo-model state machine (WAITING_FOR_TRUCK → COUNTING_SACKS → TRUCK_LEAVING) with hardcoded default zones, perspective filtering and HUD.rpo_iki/— alternate geometric engine (count.py:LineCounterwith approach depth, burst/cooldown dedup, blind-truck overlay) +predict_rpo_iki.pyrunner.