Latest Leveling with python Fix!
This commit is contained in:
1 parent
5483837769
commit
9ca1f2914e
11 files changed
+561
-172
No files matched your search
@@ -1,17 +1,20 @@
|
|||||||
# Edge RK3588 production counter — copy to .env on device
|
# Edge RK3588 production counter — copy to .env on device
|
||||||
# cp .env.example .env && nano .env
|
# cp config.env.example .env && nano .env
|
||||||
|
|
||||||
OUTPUT_DIR=/opt/bytetrack-counter-cpp
|
#DEBUG_TRACKING=true
|
||||||
DB_PATH=/opt/bytetrack-counter-cpp/bytetrack_counter.db
|
SITE_NAME=Salatiga
|
||||||
|
|
||||||
|
OUTPUT_DIR=/opt/bytetrack-counter
|
||||||
|
DB_PATH=/opt/bytetrack-counter/bytetrack_counter.db
|
||||||
STATE_FILE=/tmp/bytetrack_current_batch.json
|
STATE_FILE=/tmp/bytetrack_current_batch.json
|
||||||
|
|
||||||
# Direct LAN camera RTSP (low latency)
|
# Direct LAN camera RTSP (low latency)
|
||||||
#SOURCE=rtsp://frigate:zenai@192.168.192.16:8554/camera_stream_640
|
#SOURCE=rtsp://frigate:zenai@127.0.0.1:8554/camera_stream_640
|
||||||
SOURCE=rtsp://192.168.192.53:8554/my_stream
|
SOURCE=rtsp://10.38.30.64:8554/my_stream
|
||||||
OPENCV_FFMPEG_CAPTURE_OPTIONS=rtsp_transport;tcp|fflags;nobuffer|flags;low_delay
|
OPENCV_FFMPEG_CAPTURE_OPTIONS=rtsp_transport;tcp|fflags;nobuffer|flags;low_delay
|
||||||
|
|
||||||
# RKNN model — export'd from YOLO with imgsz=320
|
# RKNN model — export'd from YOLO9t with imgsz=320
|
||||||
MODEL_PATH=/opt/models/zenai_apc_cicalengka_20260609.rknn
|
MODEL_PATH=/opt/models/zenai_apc_salatiga_20260703.rknn
|
||||||
IMGSZ=320
|
IMGSZ=320
|
||||||
HALF=false
|
HALF=false
|
||||||
CONF=0.3
|
CONF=0.3
|
||||||
@@ -21,24 +24,31 @@ CORE_MASK=7
|
|||||||
|
|
||||||
# YOLO decoder params (must match model export)
|
# YOLO decoder params (must match model export)
|
||||||
NUM_CLASSES=2
|
NUM_CLASSES=2
|
||||||
SCORE_SIGMOID=false
|
NUM_KEYPOINTS=9
|
||||||
|
REG_MAX=16
|
||||||
|
STRIDES=8,16,32
|
||||||
|
|
||||||
CAMERA_NAME=CC1
|
CAMERA_NAME=ZenAi
|
||||||
OBJECT_LABEL=ayam-potong
|
OBJECT_LABEL=ayam-potong
|
||||||
CLASS_AYAM=ayam
|
CLASS_AYAM=ayam
|
||||||
CLASS_TALENAN=telenan
|
CLASS_TALENAN=talenan
|
||||||
|
|
||||||
# Line crossing: rtl (default) | ltr | both
|
# Line crossing: rtl (default) | ltr | both
|
||||||
CROSS_DIRECTION=ltr
|
CROSS_DIRECTION=rtl
|
||||||
LINE_X=
|
LINE_X=
|
||||||
LINE_X_FRAC=0.5
|
LINE_X_FRAC=0.5
|
||||||
|
|
||||||
DAILY_CUTOFF_TIME=17:00
|
DAILY_CUTOFF_TIME=20:00
|
||||||
|
CUTOFF_TIME=20:00
|
||||||
BATCH_TIMEOUT_SECONDS=300
|
BATCH_TIMEOUT_SECONDS=300
|
||||||
IGNORE_BATCH_LABEL_TIMEOUT_SECONDS=30
|
IGNORE_BATCH_LABEL_TIMEOUT_SECONDS=30
|
||||||
MIN_OBJECT_PER_BATCH=60
|
MIN_OBJECT_PER_BATCH=60
|
||||||
MIN_DURATION_PER_BATCH=60
|
MIN_DURATION_PER_BATCH=60
|
||||||
|
|
||||||
|
DASHBOARD_HOST=0.0.0.0
|
||||||
|
DASHBOARD_PORT=5001
|
||||||
|
SECRET_KEY=change-me-in-production
|
||||||
|
|
||||||
EXPORT_CSV=true
|
EXPORT_CSV=true
|
||||||
RECORD_VIDEO=false
|
RECORD_VIDEO=false
|
||||||
|
|
||||||
@@ -47,6 +57,14 @@ LIVE_STREAM_FRAME_PATH=/dev/shm/bytetrack-counter/live_frame.jpg
|
|||||||
LIVE_STREAM_QUALITY=75
|
LIVE_STREAM_QUALITY=75
|
||||||
LIVE_STREAM_EVERY_N=2
|
LIVE_STREAM_EVERY_N=2
|
||||||
|
|
||||||
LOG_LEVEL=info
|
# Lower high_thresh to match CONF so all valid detections create tracks
|
||||||
|
TRACK_HIGH_THRESH_0=0.3
|
||||||
|
TRACK_HIGH_THRESH_1=0.3
|
||||||
|
|
||||||
DASHBOARD_PORT=5001
|
# Relax match_thresh for ayam (0.8 is too strict, IoU must be > 0.8 to keep identity)
|
||||||
|
TRACK_MATCH_THRESH_0=0.5
|
||||||
|
TRACK_MATCH_THRESH_1=0.5
|
||||||
|
|
||||||
|
# Lower low_thresh below CONF so filtering doesn't interfere
|
||||||
|
TRACK_LOW_THRESH_0=0.1
|
||||||
|
TRACK_LOW_THRESH_1=0.1
|
||||||
+1
-1
@@ -10,7 +10,7 @@ set(Eigen3_DIR /usr/share/eigen3/cmake)
|
|||||||
find_package(Eigen3 REQUIRED NO_MODULE)
|
find_package(Eigen3 REQUIRED NO_MODULE)
|
||||||
find_package(SQLite3 REQUIRED)
|
find_package(SQLite3 REQUIRED)
|
||||||
|
|
||||||
option(HAS_RKNN "Build with RKNN NPU support" OFF)
|
option(HAS_RKNN "Build with RKNN NPU support" ON)
|
||||||
|
|
||||||
if(HAS_RKNN)
|
if(HAS_RKNN)
|
||||||
add_definitions(-DHAS_RKNN)
|
add_definitions(-DHAS_RKNN)
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# ByteTrack Counter C++ — How to Run
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/bytetrack-counter-cpp
|
||||||
|
mkdir -p build && cd build
|
||||||
|
cmake .. -DHAS_RKNN=ON
|
||||||
|
make -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
Run from the project root (looks for `.env` in CWD by default):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/bytetrack-counter-cpp
|
||||||
|
./build/bytetrack-counter
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specify a custom env file
|
||||||
|
|
||||||
|
Pass the path as the first argument:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build/bytetrack-counter /path/to/custom.env
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via environment variable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ENV_FILE=/path/to/custom.env ./build/bytetrack-counter
|
||||||
|
```
|
||||||
|
|
||||||
|
### Priority
|
||||||
|
|
||||||
|
1. CLI argument (if provided)
|
||||||
|
2. `ENV_FILE` environment variable (if set)
|
||||||
|
3. `./.env` in the working directory
|
||||||
|
|
||||||
|
## Service (systemd)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart bytetrack-counter
|
||||||
|
journalctl -u bytetrack-counter -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
See `.env.example` for a full reference. Key variables:
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `SOURCE` | `rtsp://user:pass@192.168.0.100:554/stream1` | RTSP camera URL |
|
||||||
|
| `MODEL_PATH` | `/opt/models/yolo.rknn` | RKNN model file |
|
||||||
|
| `IMGSZ` | `320` | Model input size |
|
||||||
|
| `CONF` | `0.3` | Detection confidence threshold |
|
||||||
|
| `CORE_MASK` | `1` | RKNN NPU core mask (1, 2, 3, 7) |
|
||||||
|
| `CAMERA_NAME` | `CC1` | Camera ID label |
|
||||||
|
| `CROSS_DIRECTION` | `rtl` | Line crossing direction: `rtl`, `ltr`, `both` |
|
||||||
|
| `LINE_X_FRAC` | `0.5` | Counting line position (fraction of frame width) |
|
||||||
|
| `TRACK_HIGH_THRESH_0` | `0.5` | ByteTrack high threshold (ayam) |
|
||||||
|
| `TRACK_MATCH_THRESH_0` | `0.8` | ByteTrack match threshold (ayam) |
|
||||||
|
| `TRACK_HIGH_THRESH_1` | `0.5` | ByteTrack high threshold (talenan) |
|
||||||
|
| `TRACK_MATCH_THRESH_1` | `0.6` | ByteTrack match threshold (talenan) |
|
||||||
|
| `AYAM_CLASS_ID` | `0` | YOLO class ID for ayam |
|
||||||
|
| `TALENAN_CLASS_ID` | `1` | YOLO class ID for talenan |
|
||||||
|
| `RATE_WINDOW_SEC` | `60` | Rate calculation sliding window (seconds) |
|
||||||
|
| `RECONNECT_DELAY_SEC` | `3` | Reconnect delay on stream drop / initial connect |
|
||||||
|
| `MAX_RECONNECT_ATTEMPTS` | `0` | Max reconnect attempts (0 = unlimited) |
|
||||||
|
| `LIVE_STREAM_ENABLED` | `false` | Write JPEG snapshot for web dashboard |
|
||||||
|
| `RECORD_VIDEO` | `false` | Record MP4 video segments |
|
||||||
|
| `OUTPUT_DIR` | `/opt/bytetrack-counter-cpp` | Data output directory |
|
||||||
|
| `DB_PATH` | `{OUTPUT_DIR}/bytetrack_counter.db` | SQLite database path |
|
||||||
Binary file not shown.
@@ -0,0 +1,152 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Edge RK3588 production counter + dashboard
|
||||||
|
# Shared config for: counter_live_rknn_bytetrack.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/bytetrack-counter
|
||||||
|
# SQLite database path for batch entries & crossing logs
|
||||||
|
DB_PATH=/opt/bytetrack-counter/bytetrack_counter.db
|
||||||
|
# JSON file persisting the current active batch state
|
||||||
|
STATE_FILE=/tmp/bytetrack_current_batch.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 (e.g. 2 = ayam + talenan)
|
||||||
|
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
|
||||||
|
|
||||||
|
# --- ByteTrack tracking ---
|
||||||
|
# General for ayam, index 0
|
||||||
|
# Detections with score ≥ this get priority matching in the first association stage
|
||||||
|
TRACK_HIGH_THRESH_0=0.5
|
||||||
|
# Detections with score between this and TRACK_HIGH_THRESH are matched in the second stage
|
||||||
|
TRACK_LOW_THRESH_0=0.1
|
||||||
|
# IoU threshold for the first-stage association (0–1). Higher = stricter overlap required
|
||||||
|
TRACK_MATCH_THRESH_0=0.8
|
||||||
|
# Frames a track survives without a match before being permanently removed
|
||||||
|
TRACK_BUFFER_0=30
|
||||||
|
# Minimum consecutive (or total) hits needed before a track is considered confirmed
|
||||||
|
TRACK_MIN_HITS_0=3
|
||||||
|
|
||||||
|
# For talenan, index 1
|
||||||
|
# Detections with score ≥ this get priority matching in the first association stage
|
||||||
|
TRACK_HIGH_THRESH_1=0.5
|
||||||
|
# Detections with score between this and TRACK_HIGH_THRESH are matched in the second stage
|
||||||
|
TRACK_LOW_THRESH_1=0.1
|
||||||
|
# IoU threshold for the first-stage association (0–1). Higher = stricter overlap required
|
||||||
|
TRACK_MATCH_THRESH_1=0.6
|
||||||
|
# Frames a track survives without a match before being permanently removed
|
||||||
|
TRACK_BUFFER_1=30
|
||||||
|
# Minimum consecutive (or total) hits needed before a track is considered confirmed
|
||||||
|
TRACK_MIN_HITS_1=3
|
||||||
|
|
||||||
|
# --- Display ---
|
||||||
|
# Site name shown on the dashboard header (top-right)
|
||||||
|
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=ayam-potong
|
||||||
|
# Class name for the counted object (must match NUM_CLASSES order, index 0)
|
||||||
|
CLASS_AYAM=ayam
|
||||||
|
# Class name for the batch-closing trigger object (must match NUM_CLASSES order, index 1)
|
||||||
|
CLASS_TALENAN=talenan
|
||||||
|
|
||||||
|
# --- Line crossing ---
|
||||||
|
# Direction for counting: rtl (right-to-left, default) | ltr (left-to-right) | both
|
||||||
|
CROSS_DIRECTION=rtl
|
||||||
|
# Fixed x-coordinate for the counting line (overrides LINE_X_FRAC if set)
|
||||||
|
LINE_X=
|
||||||
|
# Fraction of frame width where the counting line is drawn (default 0.5 = centre)
|
||||||
|
LINE_X_FRAC=0.5
|
||||||
|
|
||||||
|
# --- Batch management ---
|
||||||
|
# Daily cutoff time (HH:MM) – a new day's batch numbering starts after this time.
|
||||||
|
# CUTOFF_TIME is an alias used by the dashboard; DAILY_CUTOFF_TIME takes priority in counter_live_rknn_bytetrack.py.
|
||||||
|
DAILY_CUTOFF_TIME=20:00
|
||||||
|
CUTOFF_TIME=20:00
|
||||||
|
# Seconds of inactivity after which the current batch is auto-closed
|
||||||
|
BATCH_TIMEOUT_SECONDS=300
|
||||||
|
# Seconds a newly-opened batch ignores the talenan label before accepting a close trigger
|
||||||
|
IGNORE_BATCH_LABEL_TIMEOUT_SECONDS=30
|
||||||
|
# Minimum number of objects required for a batch to be saved as valid
|
||||||
|
MIN_OBJECT_PER_BATCH=60
|
||||||
|
# Minimum duration in seconds a batch must be open to be saved as valid
|
||||||
|
MIN_DURATION_PER_BATCH=60
|
||||||
|
|
||||||
|
# --- 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/batch_crossings.csv
|
||||||
|
|
||||||
|
# --- Rate / performance ---
|
||||||
|
# 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
|
||||||
|
# Print status log every N processed frames
|
||||||
|
FLUSH_EVERY_N_FRAMES=100
|
||||||
|
# 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/byetrack-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-batch JSON state file used by the dashboard
|
||||||
|
CURRENT_BATCH_PATH=/tmp/bytetrack_current_batch.json
|
||||||
+142
-73
@@ -10,6 +10,7 @@ import csv
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
|
from collections import deque
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -48,11 +49,18 @@ NUM_CLASSES = int(os.getenv("NUM_CLASSES", "2"))
|
|||||||
SCORE_SIGMOID = os.getenv("SCORE_SIGMOID", "false").lower() == "true"
|
SCORE_SIGMOID = os.getenv("SCORE_SIGMOID", "false").lower() == "true"
|
||||||
|
|
||||||
# ByteTrack settings
|
# ByteTrack settings
|
||||||
TRACK_HIGH_THRESH = float(os.getenv("TRACK_HIGH_THRESH", "0.5"))
|
# Ayam index 0
|
||||||
TRACK_LOW_THRESH = float(os.getenv("TRACK_LOW_THRESH", "0.1"))
|
TRACK_HIGH_THRESH_0 = float(os.getenv("TRACK_HIGH_THRESH_0", "0.5"))
|
||||||
TRACK_MATCH_THRESH = float(os.getenv("TRACK_MATCH_THRESH", "0.8"))
|
TRACK_LOW_THRESH_0 = float(os.getenv("TRACK_LOW_THRESH_0", "0.1"))
|
||||||
TRACK_BUFFER = int(os.getenv("TRACK_BUFFER", "30"))
|
TRACK_MATCH_THRESH_0 = float(os.getenv("TRACK_MATCH_THRESH_0", "0.8"))
|
||||||
TRACK_MIN_HITS = int(os.getenv("TRACK_MIN_HITS", "3"))
|
TRACK_BUFFER_0 = int(os.getenv("TRACK_BUFFER_0", "30"))
|
||||||
|
TRACK_MIN_HITS_0 = int(os.getenv("TRACK_MIN_HITS_0", "3"))
|
||||||
|
# Talenan index 1
|
||||||
|
TRACK_HIGH_THRESH_1 = float(os.getenv("TRACK_HIGH_THRESH_1", "0.5"))
|
||||||
|
TRACK_LOW_THRESH_1 = float(os.getenv("TRACK_LOW_THRESH_1", "0.1"))
|
||||||
|
TRACK_MATCH_THRESH_1 = float(os.getenv("TRACK_MATCH_THRESH_1", "0.6"))
|
||||||
|
TRACK_BUFFER_1 = int(os.getenv("TRACK_BUFFER_1", "30"))
|
||||||
|
TRACK_MIN_HITS_1 = int(os.getenv("TRACK_MIN_HITS_1", "3"))
|
||||||
|
|
||||||
DAILY_CUTOFF_TIME = os.getenv("DAILY_CUTOFF_TIME", "20:00")
|
DAILY_CUTOFF_TIME = os.getenv("DAILY_CUTOFF_TIME", "20:00")
|
||||||
BATCH_TIMEOUT_SECONDS = float(os.getenv("BATCH_TIMEOUT_SECONDS", "300"))
|
BATCH_TIMEOUT_SECONDS = float(os.getenv("BATCH_TIMEOUT_SECONDS", "300"))
|
||||||
@@ -65,6 +73,7 @@ MIN_DURATION_PER_BATCH = int(os.getenv("MIN_DURATION_PER_BATCH", "60"))
|
|||||||
EXPORT_CSV = os.getenv("EXPORT_CSV", "true").lower() == "true"
|
EXPORT_CSV = os.getenv("EXPORT_CSV", "true").lower() == "true"
|
||||||
CROSS_CSV = os.getenv("CROSS_CSV", f"{OUTPUT_DIR}/batch_crossings.csv")
|
CROSS_CSV = os.getenv("CROSS_CSV", f"{OUTPUT_DIR}/batch_crossings.csv")
|
||||||
|
|
||||||
|
RATE_WINDOW_SEC = int(os.getenv("RATE_WINDOW_SEC", "60"))
|
||||||
WARMUP_FRAMES = int(os.getenv("WARMUP_FRAMES", "30"))
|
WARMUP_FRAMES = int(os.getenv("WARMUP_FRAMES", "30"))
|
||||||
RECONNECT_DELAY_SEC = int(os.getenv("RECONNECT_DELAY_SEC", "3"))
|
RECONNECT_DELAY_SEC = int(os.getenv("RECONNECT_DELAY_SEC", "3"))
|
||||||
MAX_RECONNECT_ATTEMPTS = int(os.getenv("MAX_RECONNECT_ATTEMPTS", "0"))
|
MAX_RECONNECT_ATTEMPTS = int(os.getenv("MAX_RECONNECT_ATTEMPTS", "0"))
|
||||||
@@ -345,11 +354,13 @@ class ByteTracker:
|
|||||||
# --- separate detections by score ---
|
# --- separate detections by score ---
|
||||||
if len(boxes_xyxy) > 0:
|
if len(boxes_xyxy) > 0:
|
||||||
remain = scores > self.low_thresh
|
remain = scores > self.low_thresh
|
||||||
|
remain_orig_idx = np.where(remain)[0]
|
||||||
dets = boxes_xyxy[remain]
|
dets = boxes_xyxy[remain]
|
||||||
det_scores = scores[remain]
|
det_scores = scores[remain]
|
||||||
is_high = det_scores > self.high_thresh
|
is_high = det_scores > self.high_thresh
|
||||||
is_low = ~is_high
|
is_low = ~is_high
|
||||||
else:
|
else:
|
||||||
|
remain_orig_idx = np.zeros(0, dtype=np.int64)
|
||||||
dets = np.zeros((0, 4), dtype=np.float32)
|
dets = np.zeros((0, 4), dtype=np.float32)
|
||||||
det_scores = np.zeros(0, dtype=np.float32)
|
det_scores = np.zeros(0, dtype=np.float32)
|
||||||
is_high = np.zeros(0, dtype=bool)
|
is_high = np.zeros(0, dtype=bool)
|
||||||
@@ -389,10 +400,11 @@ class ByteTracker:
|
|||||||
|
|
||||||
for di, ti in matches:
|
for di, ti in matches:
|
||||||
det_global = int(high_idx[di])
|
det_global = int(high_idx[di])
|
||||||
|
orig_idx = int(remain_orig_idx[det_global])
|
||||||
track_pool[ti].update(dets[det_global])
|
track_pool[ti].update(dets[det_global])
|
||||||
track_pool[ti].hit_streak = max(1, track_pool[ti].hit_streak)
|
track_pool[ti].hit_streak = max(1, track_pool[ti].hit_streak)
|
||||||
matched_track_idx.add(ti)
|
matched_track_idx.add(ti)
|
||||||
det_to_track[det_global] = track_pool[ti].track_id
|
det_to_track[orig_idx] = track_pool[ti].track_id
|
||||||
tracked_map[track_pool[ti].track_id] = track_pool[ti].get_cx()
|
tracked_map[track_pool[ti].track_id] = track_pool[ti].get_cx()
|
||||||
match_pairs_high.append((det_global, ti))
|
match_pairs_high.append((det_global, ti))
|
||||||
|
|
||||||
@@ -408,17 +420,20 @@ class ByteTracker:
|
|||||||
unmatched_boxes = track_boxes[unmatched_tracks]
|
unmatched_boxes = track_boxes[unmatched_tracks]
|
||||||
iou_mat = _ious_xyxy(low_dets, unmatched_boxes)
|
iou_mat = _ious_xyxy(low_dets, unmatched_boxes)
|
||||||
cost_mat = 1.0 - iou_mat
|
cost_mat = 1.0 - iou_mat
|
||||||
matches2 = _greedy_match(cost_mat, threshold=0.5)
|
matches2 = _greedy_match(
|
||||||
|
cost_mat, threshold=1.0 - self.match_thresh
|
||||||
|
)
|
||||||
|
|
||||||
for di, uti in matches2:
|
for di, uti in matches2:
|
||||||
det_global = int(low_idx[di])
|
det_global = int(low_idx[di])
|
||||||
pool_idx = unmatched_tracks[uti]
|
pool_idx = unmatched_tracks[uti]
|
||||||
|
orig_idx = int(remain_orig_idx[det_global])
|
||||||
track_pool[pool_idx].update(dets[det_global])
|
track_pool[pool_idx].update(dets[det_global])
|
||||||
track_pool[pool_idx].hit_streak = max(
|
track_pool[pool_idx].hit_streak = max(
|
||||||
1, track_pool[pool_idx].hit_streak
|
1, track_pool[pool_idx].hit_streak
|
||||||
)
|
)
|
||||||
matched_track_idx.add(pool_idx)
|
matched_track_idx.add(pool_idx)
|
||||||
det_to_track[det_global] = track_pool[pool_idx].track_id
|
det_to_track[orig_idx] = track_pool[pool_idx].track_id
|
||||||
tracked_map[track_pool[pool_idx].track_id] = track_pool[
|
tracked_map[track_pool[pool_idx].track_id] = track_pool[
|
||||||
pool_idx
|
pool_idx
|
||||||
].get_cx()
|
].get_cx()
|
||||||
@@ -454,10 +469,11 @@ class ByteTracker:
|
|||||||
high_all = np.where(is_high)[0]
|
high_all = np.where(is_high)[0]
|
||||||
matched_det_ids = set(det_to_track.keys())
|
matched_det_ids = set(det_to_track.keys())
|
||||||
for dg in high_all:
|
for dg in high_all:
|
||||||
if int(dg) not in matched_det_ids:
|
orig_idx = int(remain_orig_idx[int(dg)])
|
||||||
trk = KalmanBoxTracker(dets[dg])
|
if orig_idx not in matched_det_ids:
|
||||||
|
trk = KalmanBoxTracker(dets[int(dg)])
|
||||||
self.tracked_tracks.append(trk)
|
self.tracked_tracks.append(trk)
|
||||||
det_to_track[int(dg)] = trk.track_id
|
det_to_track[orig_idx] = trk.track_id
|
||||||
tracked_map[trk.track_id] = trk.get_cx()
|
tracked_map[trk.track_id] = trk.get_cx()
|
||||||
|
|
||||||
return tracked_map, det_to_track, lost_map
|
return tracked_map, det_to_track, lost_map
|
||||||
@@ -749,61 +765,59 @@ def draw_hero_count(img, line_x, h, count, pulse_remaining=0):
|
|||||||
text = str(count)
|
text = str(count)
|
||||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||||
boost = 0.35 * (pulse_remaining / max(COUNT_PULSE_FRAMES, 1))
|
boost = 0.35 * (pulse_remaining / max(COUNT_PULSE_FRAMES, 1))
|
||||||
font_scale, thickness = 1.6 + boost, 3
|
font_scale, thickness = 1.4 + boost, 3
|
||||||
(tw, th), _ = cv2.getTextSize(text, font, font_scale, thickness)
|
(tw, th), _ = cv2.getTextSize(text, font, font_scale, thickness)
|
||||||
pad = 14
|
pad = 14
|
||||||
tx, ty = line_x - tw // 2, h // 2 + th // 2
|
tx, ty = line_x - tw // 2, h // 3 + th // 2
|
||||||
overlay_rect(
|
overlay_rect(
|
||||||
img, tx - pad, ty - th - pad, tx + tw + pad, ty + pad // 2, C_PANEL, alpha=0.78
|
img, tx - pad, ty - th - pad, tx + tw + pad, ty + pad, C_PANEL, alpha=0.78
|
||||||
)
|
)
|
||||||
cv2.rectangle(
|
cv2.rectangle(
|
||||||
img, (tx - pad, ty - th - pad), (tx + tw + pad, ty + pad // 2), C_LINE_CORE, 2
|
img, (tx - pad, ty - th - pad), (tx + tw + pad, ty + pad), C_LINE_CORE, 2
|
||||||
)
|
)
|
||||||
cv2.putText(img, text, (tx, ty), font, font_scale, C_GREEN, thickness, cv2.LINE_AA)
|
cv2.putText(img, text, (tx, ty), font, font_scale, C_GREEN, thickness, cv2.LINE_AA)
|
||||||
|
|
||||||
|
|
||||||
def draw_hud(
|
def draw_hud(img, w, batch_num, batch_count, total_ayam, elapsed_sec, rate):
|
||||||
img, w, batch_num, batch_count, total_ayam, elapsed_sec, rate, camera_id, clock
|
bar_h = 40
|
||||||
):
|
|
||||||
bar_h = 52
|
|
||||||
overlay_rect(img, 0, 0, w, bar_h, C_PANEL, alpha=0.72)
|
overlay_rect(img, 0, 0, w, bar_h, C_PANEL, alpha=0.72)
|
||||||
cv2.line(img, (0, bar_h), (w, bar_h), C_BORDER, 1)
|
cv2.line(img, (0, bar_h), (w, bar_h), C_BORDER, 1)
|
||||||
cv2.putText(
|
cv2.putText(
|
||||||
img, "BATCH", (16, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv2.LINE_AA
|
img, "BATCH", (14, 14), cv2.FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv2.LINE_AA
|
||||||
)
|
)
|
||||||
batch_label = str(batch_num) if batch_num else "\u2014"
|
batch_label = str(batch_num) if batch_num else "--"
|
||||||
cv2.putText(
|
cv2.putText(
|
||||||
img,
|
img,
|
||||||
batch_label,
|
batch_label,
|
||||||
(16, 44),
|
(14, 32),
|
||||||
cv2.FONT_HERSHEY_SIMPLEX,
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
0.9,
|
0.55,
|
||||||
C_ACCENT,
|
C_ACCENT,
|
||||||
2,
|
1,
|
||||||
cv2.LINE_AA,
|
cv2.LINE_AA,
|
||||||
)
|
)
|
||||||
cv2.putText(
|
cv2.putText(
|
||||||
img, "COUNT", (100, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv2.LINE_AA
|
img, "COUNT", (90, 14), cv2.FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv2.LINE_AA
|
||||||
)
|
)
|
||||||
cv2.putText(
|
cv2.putText(
|
||||||
img,
|
img,
|
||||||
str(batch_count),
|
str(batch_count),
|
||||||
(100, 44),
|
(90, 32),
|
||||||
cv2.FONT_HERSHEY_SIMPLEX,
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
0.9,
|
0.55,
|
||||||
C_GREEN,
|
C_GREEN,
|
||||||
2,
|
1,
|
||||||
cv2.LINE_AA,
|
cv2.LINE_AA,
|
||||||
)
|
)
|
||||||
cv2.putText(
|
cv2.putText(
|
||||||
img, "TOTAL", (190, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv2.LINE_AA
|
img, "TOTAL", (170, 14), cv2.FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv2.LINE_AA
|
||||||
)
|
)
|
||||||
cv2.putText(
|
cv2.putText(
|
||||||
img,
|
img,
|
||||||
str(total_ayam),
|
str(total_ayam),
|
||||||
(190, 44),
|
(170, 32),
|
||||||
cv2.FONT_HERSHEY_SIMPLEX,
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
0.7,
|
0.45,
|
||||||
C_TEXT,
|
C_TEXT,
|
||||||
1,
|
1,
|
||||||
cv2.LINE_AA,
|
cv2.LINE_AA,
|
||||||
@@ -811,9 +825,9 @@ def draw_hud(
|
|||||||
cv2.putText(
|
cv2.putText(
|
||||||
img,
|
img,
|
||||||
"UPTIME",
|
"UPTIME",
|
||||||
(280, 20),
|
(250, 14),
|
||||||
cv2.FONT_HERSHEY_SIMPLEX,
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
0.45,
|
0.32,
|
||||||
C_MUTED,
|
C_MUTED,
|
||||||
1,
|
1,
|
||||||
cv2.LINE_AA,
|
cv2.LINE_AA,
|
||||||
@@ -821,45 +835,34 @@ def draw_hud(
|
|||||||
cv2.putText(
|
cv2.putText(
|
||||||
img,
|
img,
|
||||||
f"{elapsed_sec / 3600:.1f}h",
|
f"{elapsed_sec / 3600:.1f}h",
|
||||||
(280, 44),
|
(250, 32),
|
||||||
cv2.FONT_HERSHEY_SIMPLEX,
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
0.7,
|
0.45,
|
||||||
C_TEXT,
|
C_TEXT,
|
||||||
1,
|
1,
|
||||||
cv2.LINE_AA,
|
cv2.LINE_AA,
|
||||||
)
|
)
|
||||||
cv2.putText(
|
cv2.putText(
|
||||||
img, "RATE", (380, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv2.LINE_AA
|
img, "RATE", (340, 14), cv2.FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv2.LINE_AA
|
||||||
)
|
)
|
||||||
cv2.putText(
|
cv2.putText(
|
||||||
img,
|
img,
|
||||||
f"{rate:.1f}/min",
|
f"{rate:.1f}/min",
|
||||||
(380, 44),
|
(340, 32),
|
||||||
cv2.FONT_HERSHEY_SIMPLEX,
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
0.7,
|
0.45,
|
||||||
C_ACCENT,
|
C_ACCENT,
|
||||||
1,
|
1,
|
||||||
cv2.LINE_AA,
|
cv2.LINE_AA,
|
||||||
)
|
)
|
||||||
# cv2.putText(img, clock, (w - 180, 36), cv2.FONT_HERSHEY_SIMPLEX, 0.55, C_TEXT, 1, cv2.LINE_AA)
|
|
||||||
cv2.putText(
|
|
||||||
img,
|
|
||||||
f"CAM {camera_id}",
|
|
||||||
(w - 180, 20),
|
|
||||||
cv2.FONT_HERSHEY_SIMPLEX,
|
|
||||||
0.45,
|
|
||||||
C_MUTED,
|
|
||||||
1,
|
|
||||||
cv2.LINE_AA,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def draw_footer(img, w, h, frame_idx, live_tag):
|
def draw_footer(img, w, h, frame_idx, live_tag, inf_ms=0.0, model_name=""):
|
||||||
bar_h = 28
|
bar_h = 28
|
||||||
overlay_rect(img, 0, h - bar_h, w, h, C_PANEL, alpha=0.55)
|
overlay_rect(img, 0, h - bar_h, w, h, C_PANEL, alpha=0.55)
|
||||||
cv2.putText(
|
cv2.putText(
|
||||||
img,
|
img,
|
||||||
f"{live_tag} | Frame {frame_idx}",
|
f"{live_tag} | {model_name} | Frame {frame_idx} | Inf {inf_ms:.1f}ms",
|
||||||
(12, h - 9),
|
(12, h - 9),
|
||||||
cv2.FONT_HERSHEY_SIMPLEX,
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
0.45,
|
0.45,
|
||||||
@@ -982,26 +985,22 @@ def run():
|
|||||||
score_sigmoid=SCORE_SIGMOID,
|
score_sigmoid=SCORE_SIGMOID,
|
||||||
)
|
)
|
||||||
|
|
||||||
CLASS_IDS = {
|
ayam_cls = int(os.getenv("AYAM_CLASS_ID", "0"))
|
||||||
os.getenv("CLASS_AYAM", "ayam"): 0,
|
talenan_cls = int(os.getenv("TALENAN_CLASS_ID", "1"))
|
||||||
os.getenv("CLASS_TALENAN", "talenan"): 1,
|
|
||||||
}
|
|
||||||
ayam_cls = CLASS_IDS[CLASS_AYAM]
|
|
||||||
talenan_cls = CLASS_IDS[CLASS_TALENAN]
|
|
||||||
|
|
||||||
ayam_tracker = ByteTracker(
|
ayam_tracker = ByteTracker(
|
||||||
track_high_thresh=TRACK_HIGH_THRESH,
|
track_high_thresh=TRACK_HIGH_THRESH_0,
|
||||||
track_low_thresh=TRACK_LOW_THRESH,
|
track_low_thresh=TRACK_LOW_THRESH_0,
|
||||||
match_thresh=TRACK_MATCH_THRESH,
|
match_thresh=TRACK_MATCH_THRESH_0,
|
||||||
track_buffer=TRACK_BUFFER,
|
track_buffer=TRACK_BUFFER_0,
|
||||||
min_hits=TRACK_MIN_HITS,
|
min_hits=TRACK_MIN_HITS_0,
|
||||||
)
|
)
|
||||||
talenan_tracker = ByteTracker(
|
talenan_tracker = ByteTracker(
|
||||||
track_high_thresh=TRACK_HIGH_THRESH,
|
track_high_thresh=TRACK_HIGH_THRESH_1,
|
||||||
track_low_thresh=TRACK_LOW_THRESH,
|
track_low_thresh=TRACK_LOW_THRESH_1,
|
||||||
match_thresh=TRACK_MATCH_THRESH,
|
match_thresh=TRACK_MATCH_THRESH_1,
|
||||||
track_buffer=TRACK_BUFFER,
|
track_buffer=TRACK_BUFFER_1,
|
||||||
min_hits=TRACK_MIN_HITS,
|
min_hits=TRACK_MIN_HITS_1,
|
||||||
)
|
)
|
||||||
|
|
||||||
ayam_line_crossed = set()
|
ayam_line_crossed = set()
|
||||||
@@ -1014,7 +1013,9 @@ def run():
|
|||||||
|
|
||||||
session_start = time.time()
|
session_start = time.time()
|
||||||
frame_idx = 0
|
frame_idx = 0
|
||||||
|
inf_ms = 0.0
|
||||||
video_writer = None
|
video_writer = None
|
||||||
|
crossing_times = deque()
|
||||||
|
|
||||||
cap, w, h, fps = connect_stream(SOURCE)
|
cap, w, h, fps = connect_stream(SOURCE)
|
||||||
if cap is None:
|
if cap is None:
|
||||||
@@ -1028,8 +1029,12 @@ def run():
|
|||||||
)
|
)
|
||||||
print(f"Model: {MODEL_PATH} | imgsz={IMGSZ} | core_mask={CORE_MASK}")
|
print(f"Model: {MODEL_PATH} | imgsz={IMGSZ} | core_mask={CORE_MASK}")
|
||||||
print(
|
print(
|
||||||
f"ByteTrack: high_thresh={TRACK_HIGH_THRESH} low_thresh={TRACK_LOW_THRESH} "
|
f"ByteTrack Index 0: high_thresh={TRACK_HIGH_THRESH_0} low_thresh={TRACK_LOW_THRESH_0} "
|
||||||
f"match_thresh={TRACK_MATCH_THRESH} buffer={TRACK_BUFFER}"
|
f"match_thresh={TRACK_MATCH_THRESH_0} buffer={TRACK_BUFFER_0}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"ByteTrack Index 1: high_thresh={TRACK_HIGH_THRESH_1} low_thresh={TRACK_LOW_THRESH_1} "
|
||||||
|
f"match_thresh={TRACK_MATCH_THRESH_1} buffer={TRACK_BUFFER_1}"
|
||||||
)
|
)
|
||||||
print(f"DB: {DB_PATH}")
|
print(f"DB: {DB_PATH}")
|
||||||
print(f"State: {STATE_FILE}")
|
print(f"State: {STATE_FILE}")
|
||||||
@@ -1061,7 +1066,9 @@ def run():
|
|||||||
mono = time.monotonic()
|
mono = time.monotonic()
|
||||||
ayam_crossed_frame = batch_closed_frame = batch_started_frame = False
|
ayam_crossed_frame = batch_closed_frame = batch_started_frame = False
|
||||||
|
|
||||||
|
inf_start = time.time()
|
||||||
detections = model(frame)
|
detections = model(frame)
|
||||||
|
inf_ms = inf_ms * 0.9 + (time.time() - inf_start) * 1000 * 0.1
|
||||||
|
|
||||||
if detections:
|
if detections:
|
||||||
ayam_boxes_xyxy = []
|
ayam_boxes_xyxy = []
|
||||||
@@ -1104,6 +1111,56 @@ def run():
|
|||||||
talenan_tracker.update(talenan_boxes_xyxy, talenan_scores)
|
talenan_tracker.update(talenan_boxes_xyxy, talenan_scores)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if os.getenv("DEBUG_TRACKING", "").lower() == "true":
|
||||||
|
if len(ayam_boxes_xyxy) > 0 or len(talenan_boxes_xyxy) > 0:
|
||||||
|
ayam_scores_str = (
|
||||||
|
f" ayam scores: {ayam_scores.round(3).tolist()}"
|
||||||
|
if len(ayam_scores) > 0
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
talenan_scores_str = (
|
||||||
|
f" talenan scores: {talenan_scores.round(3).tolist()}"
|
||||||
|
if len(talenan_scores) > 0
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
ayam_tracks_str = (
|
||||||
|
f" ayam det→track: {dict(ayam_det_to_track)}"
|
||||||
|
if ayam_det_to_track
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
talenan_tracks_str = (
|
||||||
|
f" talenan det→track: {dict(talenan_det_to_track)}"
|
||||||
|
if talenan_det_to_track
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
crossing_str = (
|
||||||
|
f" ayam_line_crossed: {sorted(ayam_line_crossed)}"
|
||||||
|
if ayam_line_crossed
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
# Show cx values for one tracked object to check movement
|
||||||
|
cx_sample = ""
|
||||||
|
if len(ayam_cx_list) > 0 and ayam_det_to_track:
|
||||||
|
sample_tid = list(ayam_det_to_track.values())[0]
|
||||||
|
sample_cx = ayam_cx_list[
|
||||||
|
list(ayam_det_to_track.keys())[0]
|
||||||
|
]
|
||||||
|
prev = (
|
||||||
|
ayam_tracked.get(sample_tid, (None,))[0]
|
||||||
|
if ayam_tracked.get(sample_tid)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
cx_sample = (
|
||||||
|
f" sample tid={sample_tid} prev_cx={prev} cx={sample_cx:.1f}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"[DEBUG F{frame_idx}] ayam_dets={len(ayam_boxes_xyxy)} "
|
||||||
|
f"talenan_dets={len(talenan_boxes_xyxy)} "
|
||||||
|
f"line_x={line_x}{ayam_scores_str}{talenan_scores_str}"
|
||||||
|
f"{ayam_tracks_str}{talenan_tracks_str}{crossing_str}"
|
||||||
|
f"{cx_sample}"
|
||||||
|
)
|
||||||
|
|
||||||
# Process talenan crossings
|
# Process talenan crossings
|
||||||
for di in range(len(talenan_boxes_xyxy)):
|
for di in range(len(talenan_boxes_xyxy)):
|
||||||
tid = talenan_det_to_track.get(di)
|
tid = talenan_det_to_track.get(di)
|
||||||
@@ -1145,6 +1202,11 @@ def run():
|
|||||||
crossed_line(prev_cx, cx, line_x)
|
crossed_line(prev_cx, cx, line_x)
|
||||||
and tid not in ayam_line_crossed
|
and tid not in ayam_line_crossed
|
||||||
):
|
):
|
||||||
|
if os.getenv("DEBUG_TRACKING", "").lower() == "true":
|
||||||
|
print(
|
||||||
|
f"[DEBUG F{frame_idx}] CROSS DETECTED: tid={tid} "
|
||||||
|
f"prev_cx={prev_cx:.1f} → cx={cx:.1f} line_x={line_x}"
|
||||||
|
)
|
||||||
ayam_line_crossed.add(tid)
|
ayam_line_crossed.add(tid)
|
||||||
_, started_new = store.record_ayam_crossing(tid)
|
_, started_new = store.record_ayam_crossing(tid)
|
||||||
if cross_logger:
|
if cross_logger:
|
||||||
@@ -1159,6 +1221,7 @@ def run():
|
|||||||
ayam_crossed_frame = True
|
ayam_crossed_frame = True
|
||||||
if started_new:
|
if started_new:
|
||||||
batch_started_frame = True
|
batch_started_frame = True
|
||||||
|
crossing_times.append(mono)
|
||||||
ayam_cross_flash[tid] = CROSS_FLASH_FRAMES
|
ayam_cross_flash[tid] = CROSS_FLASH_FRAMES
|
||||||
popups.append(
|
popups.append(
|
||||||
{
|
{
|
||||||
@@ -1216,7 +1279,9 @@ def run():
|
|||||||
batch_num = store.current_batch_number or 0
|
batch_num = store.current_batch_number or 0
|
||||||
batch_count = store.current_batch_count
|
batch_count = store.current_batch_count
|
||||||
display_total = store.display_total()
|
display_total = store.display_total()
|
||||||
rate = (display_total / elapsed * 60) if elapsed > 0 else 0.0
|
while crossing_times and mono - crossing_times[0] > RATE_WINDOW_SEC:
|
||||||
|
crossing_times.popleft()
|
||||||
|
rate = (len(crossing_times) / RATE_WINDOW_SEC * 60) if crossing_times else 0.0
|
||||||
|
|
||||||
draw_elegant_counting_line(frame, line_x, h, line_pulse)
|
draw_elegant_counting_line(frame, line_x, h, line_pulse)
|
||||||
draw_hero_count(frame, line_x, h, batch_count, count_pulse)
|
draw_hero_count(frame, line_x, h, batch_count, count_pulse)
|
||||||
@@ -1228,12 +1293,16 @@ def run():
|
|||||||
display_total,
|
display_total,
|
||||||
elapsed,
|
elapsed,
|
||||||
rate,
|
rate,
|
||||||
CAMERA_NAME,
|
|
||||||
now_str(),
|
|
||||||
)
|
)
|
||||||
draw_batch_banner(frame, w, batch_num, batch_pulse)
|
draw_batch_banner(frame, w, batch_num, batch_pulse)
|
||||||
draw_footer(
|
draw_footer(
|
||||||
frame, w, h, frame_idx, "LIVE-RKNN-BT" if IS_LIVE else "FILE-RKNN-BT"
|
frame,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
frame_idx,
|
||||||
|
"LIVE" if IS_LIVE else "FILE",
|
||||||
|
inf_ms,
|
||||||
|
Path(MODEL_PATH).name,
|
||||||
)
|
)
|
||||||
popups = draw_popups(frame, popups, frame_idx)
|
popups = draw_popups(frame, popups, frame_idx)
|
||||||
|
|
||||||
|
|||||||
+28
-10
@@ -73,17 +73,35 @@ void AppConfig::load_from_env(const char* env_path) {
|
|||||||
if (!nc_s.empty()) num_classes = std::stoi(nc_s);
|
if (!nc_s.empty()) num_classes = std::stoi(nc_s);
|
||||||
auto ss_s = get_env("SCORE_SIGMOID");
|
auto ss_s = get_env("SCORE_SIGMOID");
|
||||||
score_sigmoid = (ss_s == "true");
|
score_sigmoid = (ss_s == "true");
|
||||||
|
auto ac_s = get_env("AYAM_CLASS_ID");
|
||||||
|
if (!ac_s.empty()) ayam_class_id = std::stoi(ac_s);
|
||||||
|
auto tc_s = get_env("TALENAN_CLASS_ID");
|
||||||
|
if (!tc_s.empty()) talenan_class_id = std::stoi(tc_s);
|
||||||
|
|
||||||
auto th_s = get_env("TRACK_HIGH_THRESH");
|
auto th0_s = get_env("TRACK_HIGH_THRESH_0");
|
||||||
if (!th_s.empty()) track_high_thresh = std::stof(th_s);
|
if (!th0_s.empty()) track_high_thresh_0 = std::stof(th0_s);
|
||||||
auto tl_s = get_env("TRACK_LOW_THRESH");
|
auto tl0_s = get_env("TRACK_LOW_THRESH_0");
|
||||||
if (!tl_s.empty()) track_low_thresh = std::stof(tl_s);
|
if (!tl0_s.empty()) track_low_thresh_0 = std::stof(tl0_s);
|
||||||
auto tm_s = get_env("TRACK_MATCH_THRESH");
|
auto tm0_s = get_env("TRACK_MATCH_THRESH_0");
|
||||||
if (!tm_s.empty()) track_match_thresh = std::stof(tm_s);
|
if (!tm0_s.empty()) track_match_thresh_0 = std::stof(tm0_s);
|
||||||
auto tb_s = get_env("TRACK_BUFFER");
|
auto tb0_s = get_env("TRACK_BUFFER_0");
|
||||||
if (!tb_s.empty()) track_buffer = std::stoi(tb_s);
|
if (!tb0_s.empty()) track_buffer_0 = std::stoi(tb0_s);
|
||||||
auto tmh_s = get_env("TRACK_MIN_HITS");
|
auto tmh0_s = get_env("TRACK_MIN_HITS_0");
|
||||||
if (!tmh_s.empty()) track_min_hits = std::stoi(tmh_s);
|
if (!tmh0_s.empty()) track_min_hits_0 = std::stoi(tmh0_s);
|
||||||
|
|
||||||
|
auto th1_s = get_env("TRACK_HIGH_THRESH_1");
|
||||||
|
if (!th1_s.empty()) track_high_thresh_1 = std::stof(th1_s);
|
||||||
|
auto tl1_s = get_env("TRACK_LOW_THRESH_1");
|
||||||
|
if (!tl1_s.empty()) track_low_thresh_1 = std::stof(tl1_s);
|
||||||
|
auto tm1_s = get_env("TRACK_MATCH_THRESH_1");
|
||||||
|
if (!tm1_s.empty()) track_match_thresh_1 = std::stof(tm1_s);
|
||||||
|
auto tb1_s = get_env("TRACK_BUFFER_1");
|
||||||
|
if (!tb1_s.empty()) track_buffer_1 = std::stoi(tb1_s);
|
||||||
|
auto tmh1_s = get_env("TRACK_MIN_HITS_1");
|
||||||
|
if (!tmh1_s.empty()) track_min_hits_1 = std::stoi(tmh1_s);
|
||||||
|
|
||||||
|
auto rw_s = get_env("RATE_WINDOW_SEC");
|
||||||
|
if (!rw_s.empty()) rate_window_sec = std::stoi(rw_s);
|
||||||
|
|
||||||
daily_cutoff_time = get_env("DAILY_CUTOFF_TIME", daily_cutoff_time);
|
daily_cutoff_time = get_env("DAILY_CUTOFF_TIME", daily_cutoff_time);
|
||||||
auto bto_s = get_env("BATCH_TIMEOUT_SECONDS");
|
auto bto_s = get_env("BATCH_TIMEOUT_SECONDS");
|
||||||
|
|||||||
+15
-5
@@ -24,12 +24,22 @@ struct AppConfig {
|
|||||||
int core_mask = 1;
|
int core_mask = 1;
|
||||||
int num_classes = 2;
|
int num_classes = 2;
|
||||||
bool score_sigmoid = false;
|
bool score_sigmoid = false;
|
||||||
|
int ayam_class_id = 0;
|
||||||
|
int talenan_class_id = 1;
|
||||||
|
|
||||||
float track_high_thresh = 0.5f;
|
float track_high_thresh_0 = 0.5f;
|
||||||
float track_low_thresh = 0.1f;
|
float track_low_thresh_0 = 0.1f;
|
||||||
float track_match_thresh = 0.8f;
|
float track_match_thresh_0 = 0.8f;
|
||||||
int track_buffer = 30;
|
int track_buffer_0 = 30;
|
||||||
int track_min_hits = 3;
|
int track_min_hits_0 = 3;
|
||||||
|
|
||||||
|
float track_high_thresh_1 = 0.5f;
|
||||||
|
float track_low_thresh_1 = 0.1f;
|
||||||
|
float track_match_thresh_1 = 0.6f;
|
||||||
|
int track_buffer_1 = 30;
|
||||||
|
int track_min_hits_1 = 3;
|
||||||
|
|
||||||
|
int rate_window_sec = 60;
|
||||||
|
|
||||||
std::string daily_cutoff_time = "20:00";
|
std::string daily_cutoff_time = "20:00";
|
||||||
float batch_timeout_seconds = 300.0f;
|
float batch_timeout_seconds = 300.0f;
|
||||||
|
|||||||
+24
-25
@@ -9,8 +9,8 @@ const std::vector<std::pair<int, int>> SKELETON = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<cv::Scalar> SK_COLORS = {
|
const std::vector<cv::Scalar> SK_COLORS = {
|
||||||
{255, 255, 0}, {255, 255, 0}, {255, 0, 255}, {255, 0, 255},
|
{0, 255, 255}, {0, 255, 255}, {255, 0, 255}, {255, 0, 255},
|
||||||
{0, 255, 0}, {255, 255, 0}, {255, 0, 0}, {0, 200, 200}
|
{0, 255, 0}, {255, 255, 0}, {0, 0, 255}, {200, 200, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
int resolve_line_x(int frame_width, int line_x_opt, float line_x_frac) {
|
int resolve_line_x(int frame_width, int line_x_opt, float line_x_frac) {
|
||||||
@@ -68,54 +68,53 @@ void draw_elegant_counting_line(cv::Mat& img, int line_x, int h, int pulse_remai
|
|||||||
void draw_hero_count(cv::Mat& img, int line_x, int h, int count, int pulse_remaining) {
|
void draw_hero_count(cv::Mat& img, int line_x, int h, int count, int pulse_remaining) {
|
||||||
std::string text = std::to_string(count);
|
std::string text = std::to_string(count);
|
||||||
float boost = 0.35f * (pulse_remaining / std::max(15.0f, 1.0f));
|
float boost = 0.35f * (pulse_remaining / std::max(15.0f, 1.0f));
|
||||||
double font_scale = 1.6 + boost;
|
double font_scale = 1.4 + boost;
|
||||||
int thickness = static_cast<int>(3 + boost);
|
int thickness = static_cast<int>(3 + boost);
|
||||||
int baseline = 0;
|
int baseline = 0;
|
||||||
cv::Size ts = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, font_scale, thickness, &baseline);
|
cv::Size ts = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, font_scale, thickness, &baseline);
|
||||||
int pad = 14;
|
int pad = 14;
|
||||||
int tx = line_x - ts.width / 2;
|
int tx = line_x - ts.width / 2;
|
||||||
int ty = h / 2 + ts.height / 2;
|
int ty = h / 3 + ts.height / 2;
|
||||||
overlay_rect(img, tx - pad, ty - ts.height - pad, tx + ts.width + pad, ty + pad / 2, C_PANEL, 0.78f);
|
overlay_rect(img, tx - pad, ty - ts.height - pad, tx + ts.width + pad, ty + pad, C_PANEL, 0.78f);
|
||||||
cv::rectangle(img, cv::Rect(tx - pad, ty - ts.height - pad,
|
cv::rectangle(img, cv::Rect(tx - pad, ty - ts.height - pad,
|
||||||
ts.width + pad * 2, ts.height + pad + pad / 2),
|
ts.width + pad * 2, ts.height + pad * 2),
|
||||||
C_LINE_CORE, 2);
|
C_LINE_CORE, 2);
|
||||||
cv::putText(img, text, cv::Point(tx, ty), cv::FONT_HERSHEY_SIMPLEX, font_scale, C_GREEN, thickness, cv::LINE_AA);
|
cv::putText(img, text, cv::Point(tx, ty), cv::FONT_HERSHEY_SIMPLEX, font_scale, C_GREEN, thickness, cv::LINE_AA);
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_hud(cv::Mat& img, int w, int batch_num, int batch_count, int total_ayam,
|
void draw_hud(cv::Mat& img, int w, int batch_num, int batch_count, int total_ayam,
|
||||||
double elapsed_sec, double rate, const std::string& camera_id, const std::string& clock) {
|
double elapsed_sec, double rate) {
|
||||||
int bar_h = 52;
|
int bar_h = 40;
|
||||||
overlay_rect(img, 0, 0, w, bar_h, C_PANEL, 0.72f);
|
overlay_rect(img, 0, 0, w, bar_h, C_PANEL, 0.72f);
|
||||||
cv::line(img, cv::Point(0, bar_h), cv::Point(w, bar_h), C_BORDER, 1);
|
cv::line(img, cv::Point(0, bar_h), cv::Point(w, bar_h), C_BORDER, 1);
|
||||||
|
|
||||||
cv::putText(img, "BATCH", cv::Point(16, 20), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv::LINE_AA);
|
cv::putText(img, "BATCH", cv::Point(14, 14), cv::FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv::LINE_AA);
|
||||||
std::string batch_label = batch_num ? std::to_string(batch_num) : "\xe2\x80\x94";
|
std::string batch_label = batch_num ? std::to_string(batch_num) : "--";
|
||||||
cv::putText(img, batch_label, cv::Point(16, 44), cv::FONT_HERSHEY_SIMPLEX, 0.9, C_ACCENT, 2, cv::LINE_AA);
|
cv::putText(img, batch_label, cv::Point(14, 32), cv::FONT_HERSHEY_SIMPLEX, 0.55, C_ACCENT, 1, cv::LINE_AA);
|
||||||
|
|
||||||
cv::putText(img, "COUNT", cv::Point(100, 20), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv::LINE_AA);
|
cv::putText(img, "COUNT", cv::Point(90, 14), cv::FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv::LINE_AA);
|
||||||
cv::putText(img, std::to_string(batch_count), cv::Point(100, 44), cv::FONT_HERSHEY_SIMPLEX, 0.9, C_GREEN, 2, cv::LINE_AA);
|
cv::putText(img, std::to_string(batch_count), cv::Point(90, 32), cv::FONT_HERSHEY_SIMPLEX, 0.55, C_GREEN, 1, cv::LINE_AA);
|
||||||
|
|
||||||
cv::putText(img, "TOTAL", cv::Point(190, 20), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv::LINE_AA);
|
cv::putText(img, "TOTAL", cv::Point(170, 14), cv::FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv::LINE_AA);
|
||||||
cv::putText(img, std::to_string(total_ayam), cv::Point(190, 44), cv::FONT_HERSHEY_SIMPLEX, 0.7, C_TEXT, 1, cv::LINE_AA);
|
cv::putText(img, std::to_string(total_ayam), cv::Point(170, 32), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_TEXT, 1, cv::LINE_AA);
|
||||||
|
|
||||||
cv::putText(img, "UPTIME", cv::Point(280, 20), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv::LINE_AA);
|
cv::putText(img, "UPTIME", cv::Point(250, 14), cv::FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv::LINE_AA);
|
||||||
char buf[32];
|
char buf[32];
|
||||||
snprintf(buf, sizeof(buf), "%.1fh", elapsed_sec / 3600.0);
|
snprintf(buf, sizeof(buf), "%.1fh", elapsed_sec / 3600.0);
|
||||||
cv::putText(img, buf, cv::Point(280, 44), cv::FONT_HERSHEY_SIMPLEX, 0.7, C_TEXT, 1, cv::LINE_AA);
|
cv::putText(img, buf, cv::Point(250, 32), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_TEXT, 1, cv::LINE_AA);
|
||||||
|
|
||||||
cv::putText(img, "RATE", cv::Point(380, 20), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv::LINE_AA);
|
cv::putText(img, "RATE", cv::Point(340, 14), cv::FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv::LINE_AA);
|
||||||
snprintf(buf, sizeof(buf), "%.1f/min", rate);
|
snprintf(buf, sizeof(buf), "%.1f/min", rate);
|
||||||
cv::putText(img, buf, cv::Point(380, 44), cv::FONT_HERSHEY_SIMPLEX, 0.7, C_ACCENT, 1, cv::LINE_AA);
|
cv::putText(img, buf, cv::Point(340, 32), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_ACCENT, 1, cv::LINE_AA);
|
||||||
|
|
||||||
std::string cam_label = "CAM " + camera_id;
|
|
||||||
cv::putText(img, cam_label, cv::Point(w - 180, 20), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv::LINE_AA);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_footer(cv::Mat& img, int w, int h, int frame_idx, const std::string& live_tag) {
|
void draw_footer(cv::Mat& img, int w, int h, int frame_idx, const std::string& live_tag,
|
||||||
|
float inf_ms, const std::string& model_name) {
|
||||||
int bar_h = 28;
|
int bar_h = 28;
|
||||||
overlay_rect(img, 0, h - bar_h, w, h, C_PANEL, 0.55f);
|
overlay_rect(img, 0, h - bar_h, w, h, C_PANEL, 0.55f);
|
||||||
char buf[64];
|
char buf[128];
|
||||||
snprintf(buf, sizeof(buf), "%s | Frame %d", live_tag.c_str(), frame_idx);
|
snprintf(buf, sizeof(buf), "%s | %s | Frame %d | Inf %.1fms",
|
||||||
|
live_tag.c_str(), model_name.c_str(), frame_idx, inf_ms);
|
||||||
cv::putText(img, buf, cv::Point(12, h - 9), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv::LINE_AA);
|
cv::putText(img, buf, cv::Point(12, h - 9), cv::FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv::LINE_AA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-10
@@ -5,16 +5,16 @@
|
|||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
inline const cv::Scalar C_PANEL(18, 24, 28);
|
inline const cv::Scalar C_PANEL(28, 24, 18);
|
||||||
inline const cv::Scalar C_BORDER(75, 85, 90);
|
inline const cv::Scalar C_BORDER(90, 85, 75);
|
||||||
inline const cv::Scalar C_ACCENT(60, 200, 255);
|
inline const cv::Scalar C_ACCENT(255, 200, 60);
|
||||||
inline const cv::Scalar C_GREEN(100, 220, 80);
|
inline const cv::Scalar C_GREEN(80, 220, 100);
|
||||||
inline const cv::Scalar C_TEXT(235, 235, 235);
|
inline const cv::Scalar C_TEXT(235, 235, 235);
|
||||||
inline const cv::Scalar C_MUTED(150, 150, 150);
|
inline const cv::Scalar C_MUTED(150, 150, 150);
|
||||||
inline const cv::Scalar C_AYAM_BOX(255, 165, 0);
|
inline const cv::Scalar C_AYAM_BOX(255, 150, 60);
|
||||||
inline const cv::Scalar C_TALENAN_BOX(60, 120, 220);
|
inline const cv::Scalar C_TALENAN_BOX(60, 60, 255);
|
||||||
inline const cv::Scalar C_LINE_CORE(255, 220, 180);
|
inline const cv::Scalar C_LINE_CORE(180, 220, 255);
|
||||||
inline const cv::Scalar C_LINE_GLOW(220, 160, 100);
|
inline const cv::Scalar C_LINE_GLOW(100, 160, 220);
|
||||||
|
|
||||||
extern const std::vector<std::pair<int, int>> SKELETON;
|
extern const std::vector<std::pair<int, int>> SKELETON;
|
||||||
extern const std::vector<cv::Scalar> SK_COLORS;
|
extern const std::vector<cv::Scalar> SK_COLORS;
|
||||||
@@ -30,8 +30,9 @@ void draw_pill(cv::Mat& img, const std::string& text, int x, int y,
|
|||||||
void draw_elegant_counting_line(cv::Mat& img, int line_x, int h, int pulse_remaining = 0);
|
void draw_elegant_counting_line(cv::Mat& img, int line_x, int h, int pulse_remaining = 0);
|
||||||
void draw_hero_count(cv::Mat& img, int line_x, int h, int count, int pulse_remaining = 0);
|
void draw_hero_count(cv::Mat& img, int line_x, int h, int count, int pulse_remaining = 0);
|
||||||
void draw_hud(cv::Mat& img, int w, int batch_num, int batch_count, int total_ayam,
|
void draw_hud(cv::Mat& img, int w, int batch_num, int batch_count, int total_ayam,
|
||||||
double elapsed_sec, double rate, const std::string& camera_id, const std::string& clock);
|
double elapsed_sec, double rate);
|
||||||
void draw_footer(cv::Mat& img, int w, int h, int frame_idx, const std::string& live_tag);
|
void draw_footer(cv::Mat& img, int w, int h, int frame_idx, const std::string& live_tag,
|
||||||
|
float inf_ms = 0.0f, const std::string& model_name = "");
|
||||||
void draw_skeleton_bold(cv::Mat& img, const std::vector<std::vector<cv::Point2f>>& kpts);
|
void draw_skeleton_bold(cv::Mat& img, const std::vector<std::vector<cv::Point2f>>& kpts);
|
||||||
|
|
||||||
struct Popup {
|
struct Popup {
|
||||||
|
|||||||
+82
-34
@@ -50,6 +50,35 @@ static void warmup_stream(cv::VideoCapture& cap, int n) {
|
|||||||
std::cout << "Stream ready!" << std::endl;
|
std::cout << "Stream ready!" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool connect_stream(cv::VideoCapture& cap, int& w, int& h, double& fps) {
|
||||||
|
bool is_live = g_config.source.substr(0, 7) == "rtsp://" ||
|
||||||
|
g_config.source.substr(0, 7) == "http://";
|
||||||
|
int attempts = 0;
|
||||||
|
while (!shutdown_requested) {
|
||||||
|
cap = open_capture(g_config.source);
|
||||||
|
if (cap.isOpened()) break;
|
||||||
|
attempts++;
|
||||||
|
if (g_config.max_reconnect_attempts > 0 && attempts >= g_config.max_reconnect_attempts) {
|
||||||
|
std::cerr << "Cannot open source after " << attempts
|
||||||
|
<< " attempts: " << g_config.source << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::cout << "Cannot open source, retry in " << g_config.reconnect_delay_sec
|
||||||
|
<< "s..." << std::endl;
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(g_config.reconnect_delay_sec));
|
||||||
|
}
|
||||||
|
if (shutdown_requested) return false;
|
||||||
|
|
||||||
|
if (is_live && g_config.warmup_frames > 0)
|
||||||
|
warmup_stream(cap, g_config.warmup_frames);
|
||||||
|
|
||||||
|
w = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
|
||||||
|
h = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
|
||||||
|
fps = cap.get(cv::CAP_PROP_FPS);
|
||||||
|
if (fps <= 1) fps = g_config.output_fps;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
struct TrackedInfo {
|
struct TrackedInfo {
|
||||||
float cx;
|
float cx;
|
||||||
std::chrono::steady_clock::time_point ts;
|
std::chrono::steady_clock::time_point ts;
|
||||||
@@ -75,11 +104,23 @@ int main(int argc, char* argv[]) {
|
|||||||
std::cout << "Starting ByteTrack Counter (C++)" << std::endl;
|
std::cout << "Starting ByteTrack Counter (C++)" << std::endl;
|
||||||
std::cout << "Camera: " << g_config.camera_name << std::endl;
|
std::cout << "Camera: " << g_config.camera_name << std::endl;
|
||||||
std::cout << "Source: " << g_config.source << std::endl;
|
std::cout << "Source: " << g_config.source << std::endl;
|
||||||
std::cout << "Model: " << g_config.model_path << std::endl;
|
std::cout << "Model: " << g_config.model_path << " | imgsz=" << g_config.imgsz
|
||||||
|
<< " | core_mask=" << g_config.core_mask << std::endl;
|
||||||
std::cout << "DB: " << g_config.db_path << std::endl;
|
std::cout << "DB: " << g_config.db_path << std::endl;
|
||||||
|
std::cout << "State: " << g_config.state_file << std::endl;
|
||||||
|
std::cout << "ByteTrack Index 0: high_thresh=" << g_config.track_high_thresh_0
|
||||||
|
<< " low_thresh=" << g_config.track_low_thresh_0
|
||||||
|
<< " match_thresh=" << g_config.track_match_thresh_0
|
||||||
|
<< " buffer=" << g_config.track_buffer_0
|
||||||
|
<< " min_hits=" << g_config.track_min_hits_0 << std::endl;
|
||||||
|
std::cout << "ByteTrack Index 1: high_thresh=" << g_config.track_high_thresh_1
|
||||||
|
<< " low_thresh=" << g_config.track_low_thresh_1
|
||||||
|
<< " match_thresh=" << g_config.track_match_thresh_1
|
||||||
|
<< " buffer=" << g_config.track_buffer_1
|
||||||
|
<< " min_hits=" << g_config.track_min_hits_1 << std::endl;
|
||||||
|
|
||||||
int class_ayam = 0;
|
int class_ayam = g_config.ayam_class_id;
|
||||||
int class_talenan = 1;
|
int class_talenan = g_config.talenan_class_id;
|
||||||
|
|
||||||
auto logger = [](const std::string& msg) {
|
auto logger = [](const std::string& msg) {
|
||||||
std::cout << "[" << now_str() << "] " << msg << std::endl;
|
std::cout << "[" << now_str() << "] " << msg << std::endl;
|
||||||
@@ -105,12 +146,12 @@ int main(int argc, char* argv[]) {
|
|||||||
g_config.imgsz, g_config.conf, 0.45f,
|
g_config.imgsz, g_config.conf, 0.45f,
|
||||||
g_config.num_classes, 0, g_config.score_sigmoid);
|
g_config.num_classes, 0, g_config.score_sigmoid);
|
||||||
|
|
||||||
ByteTracker ayam_tracker(g_config.track_high_thresh, g_config.track_low_thresh,
|
ByteTracker ayam_tracker(g_config.track_high_thresh_0, g_config.track_low_thresh_0,
|
||||||
g_config.track_match_thresh, g_config.track_buffer,
|
g_config.track_match_thresh_0, g_config.track_buffer_0,
|
||||||
g_config.track_min_hits);
|
g_config.track_min_hits_0);
|
||||||
ByteTracker talenan_tracker(g_config.track_high_thresh, g_config.track_low_thresh,
|
ByteTracker talenan_tracker(g_config.track_high_thresh_1, g_config.track_low_thresh_1,
|
||||||
g_config.track_match_thresh, g_config.track_buffer,
|
g_config.track_match_thresh_1, g_config.track_buffer_1,
|
||||||
g_config.track_min_hits);
|
g_config.track_min_hits_1);
|
||||||
|
|
||||||
std::unordered_set<int> ayam_line_crossed;
|
std::unordered_set<int> ayam_line_crossed;
|
||||||
std::unordered_set<int> talenan_line_crossed;
|
std::unordered_set<int> talenan_line_crossed;
|
||||||
@@ -119,27 +160,27 @@ int main(int argc, char* argv[]) {
|
|||||||
std::unordered_map<int, int> talenan_cross_flash;
|
std::unordered_map<int, int> talenan_cross_flash;
|
||||||
int line_pulse = 0, count_pulse = 0, batch_pulse = 0;
|
int line_pulse = 0, count_pulse = 0, batch_pulse = 0;
|
||||||
std::vector<Popup> popups;
|
std::vector<Popup> popups;
|
||||||
|
std::deque<std::chrono::steady_clock::time_point> crossing_times;
|
||||||
|
|
||||||
auto session_start = std::chrono::steady_clock::now();
|
auto session_start = std::chrono::steady_clock::now();
|
||||||
int frame_idx = 0;
|
int frame_idx = 0;
|
||||||
|
float inf_ema = 0.0f;
|
||||||
|
|
||||||
cv::VideoCapture cap = open_capture(g_config.source);
|
std::string model_name = g_config.model_path;
|
||||||
|
auto slash = model_name.find_last_of("/\\");
|
||||||
|
if (slash != std::string::npos) model_name = model_name.substr(slash + 1);
|
||||||
|
|
||||||
|
cv::VideoCapture cap;
|
||||||
|
int w, h;
|
||||||
|
double fps_prop;
|
||||||
|
|
||||||
|
if (!connect_stream(cap, w, h, fps_prop)) {
|
||||||
|
store.shutdown();
|
||||||
|
return shutdown_requested ? 0 : 1;
|
||||||
|
}
|
||||||
bool is_live = g_config.source.substr(0, 7) == "rtsp://" ||
|
bool is_live = g_config.source.substr(0, 7) == "rtsp://" ||
|
||||||
g_config.source.substr(0, 7) == "http://";
|
g_config.source.substr(0, 7) == "http://";
|
||||||
|
|
||||||
if (!cap.isOpened()) {
|
|
||||||
std::cerr << "Cannot open source: " << g_config.source << std::endl;
|
|
||||||
store.shutdown();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (is_live && g_config.warmup_frames > 0)
|
|
||||||
warmup_stream(cap, g_config.warmup_frames);
|
|
||||||
|
|
||||||
int w = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
|
|
||||||
int h = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
|
|
||||||
double fps_prop = cap.get(cv::CAP_PROP_FPS);
|
|
||||||
if (fps_prop <= 1) fps_prop = g_config.output_fps;
|
|
||||||
|
|
||||||
int line_x = resolve_line_x(w, g_config.line_x, g_config.line_x_frac);
|
int line_x = resolve_line_x(w, g_config.line_x, g_config.line_x_frac);
|
||||||
|
|
||||||
std::cout << "RKNN+ByteTrack counter | " << w << "x" << h << " @ "
|
std::cout << "RKNN+ByteTrack counter | " << w << "x" << h << " @ "
|
||||||
@@ -161,21 +202,22 @@ int main(int argc, char* argv[]) {
|
|||||||
<< "), reconnecting..." << std::endl;
|
<< "), reconnecting..." << std::endl;
|
||||||
cap.release();
|
cap.release();
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(g_config.reconnect_delay_sec));
|
std::this_thread::sleep_for(std::chrono::seconds(g_config.reconnect_delay_sec));
|
||||||
cap = open_capture(g_config.source);
|
if (!connect_stream(cap, w, h, fps_prop)) break;
|
||||||
if (!cap.isOpened()) break;
|
|
||||||
w = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
|
|
||||||
h = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
|
|
||||||
line_x = resolve_line_x(w, g_config.line_x, g_config.line_x_frac);
|
line_x = resolve_line_x(w, g_config.line_x, g_config.line_x_frac);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto elapsed_sec = std::chrono::duration_cast<std::chrono::seconds>(
|
auto elapsed_sec = std::chrono::duration<double>(
|
||||||
std::chrono::steady_clock::now() - session_start).count();
|
std::chrono::steady_clock::now() - session_start).count();
|
||||||
auto mono = std::chrono::steady_clock::now();
|
auto mono = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
bool ayam_crossed_frame = false, batch_closed_frame = false, batch_started_frame = false;
|
bool ayam_crossed_frame = false, batch_closed_frame = false, batch_started_frame = false;
|
||||||
|
|
||||||
|
auto inf_start = std::chrono::steady_clock::now();
|
||||||
std::vector<Detection> detections = model(frame);
|
std::vector<Detection> detections = model(frame);
|
||||||
|
float inf_ms = std::chrono::duration<float, std::milli>(
|
||||||
|
std::chrono::steady_clock::now() - inf_start).count();
|
||||||
|
inf_ema = inf_ema * 0.9f + inf_ms * 0.1f;
|
||||||
|
|
||||||
if (!detections.empty()) {
|
if (!detections.empty()) {
|
||||||
std::vector<Eigen::Vector4f> ayam_boxes, talenan_boxes;
|
std::vector<Eigen::Vector4f> ayam_boxes, talenan_boxes;
|
||||||
@@ -317,6 +359,7 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
ayam_crossed_frame = true;
|
ayam_crossed_frame = true;
|
||||||
if (started_new) batch_started_frame = true;
|
if (started_new) batch_started_frame = true;
|
||||||
|
crossing_times.push_back(mono);
|
||||||
ayam_cross_flash[tid] = g_config.cross_flash_frames;
|
ayam_cross_flash[tid] = g_config.cross_flash_frames;
|
||||||
popups.push_back({
|
popups.push_back({
|
||||||
static_cast<int>(cx) - 12,
|
static_cast<int>(cx) - 12,
|
||||||
@@ -379,15 +422,19 @@ int main(int argc, char* argv[]) {
|
|||||||
int batch_num = store.current_batch_number();
|
int batch_num = store.current_batch_number();
|
||||||
int batch_count = store.current_batch_count();
|
int batch_count = store.current_batch_count();
|
||||||
int display_total = store.display_total();
|
int display_total = store.display_total();
|
||||||
double elapsed = static_cast<double>(elapsed_sec);
|
|
||||||
double rate = elapsed > 0 ? (display_total / elapsed * 60.0) : 0.0;
|
while (!crossing_times.empty() &&
|
||||||
|
std::chrono::duration<double>(mono - crossing_times.front()).count() > g_config.rate_window_sec)
|
||||||
|
crossing_times.pop_front();
|
||||||
|
double rate = crossing_times.empty() ? 0.0 :
|
||||||
|
(crossing_times.size() / static_cast<double>(g_config.rate_window_sec) * 60.0);
|
||||||
|
|
||||||
draw_elegant_counting_line(frame, line_x, h, line_pulse);
|
draw_elegant_counting_line(frame, line_x, h, line_pulse);
|
||||||
draw_hero_count(frame, line_x, h, batch_count, count_pulse);
|
draw_hero_count(frame, line_x, h, batch_count, count_pulse);
|
||||||
draw_hud(frame, w, batch_num, batch_count, display_total,
|
draw_hud(frame, w, batch_num, batch_count, display_total,
|
||||||
elapsed, rate, g_config.camera_name, now_str());
|
elapsed_sec, rate);
|
||||||
draw_batch_banner(frame, w, batch_num, batch_pulse);
|
draw_batch_banner(frame, w, batch_num, batch_pulse);
|
||||||
draw_footer(frame, w, h, frame_idx, is_live ? "LIVE-RKNN-BT" : "FILE-RKNN-BT");
|
draw_footer(frame, w, h, frame_idx, is_live ? "LIVE" : "FILE", inf_ema, model_name);
|
||||||
popups = draw_popups(frame, popups, frame_idx);
|
popups = draw_popups(frame, popups, frame_idx);
|
||||||
|
|
||||||
// Flash decay
|
// Flash decay
|
||||||
@@ -418,9 +465,10 @@ int main(int argc, char* argv[]) {
|
|||||||
frame_idx++;
|
frame_idx++;
|
||||||
if (frame_idx % g_config.flush_every_n_frames == 0) {
|
if (frame_idx % g_config.flush_every_n_frames == 0) {
|
||||||
std::cout << "[" << now_str() << "] Frame " << frame_idx
|
std::cout << "[" << now_str() << "] Frame " << frame_idx
|
||||||
<< " | Batch " << batch_num << ": " << batch_count
|
<< " | Batch " << (batch_num ? std::to_string(batch_num) : "-")
|
||||||
|
<< ": " << (batch_num ? std::to_string(batch_count) : "-")
|
||||||
<< " | Total: " << display_total
|
<< " | Total: " << display_total
|
||||||
<< " | Uptime " << (elapsed / 3600.0) << "h" << std::endl;
|
<< " | Uptime " << (elapsed_sec / 3600.0) << "h" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
prune_stale_tracks(ayam_tracked, g_config.tracked_prune_sec);
|
prune_stale_tracks(ayam_tracked, g_config.tracked_prune_sec);
|
||||||
|
|||||||
Reference in new issue
Block a user