Level up with python version

This commit is contained in:
proitlab committed 2026-07-06 02:51:03 +07:00
1 parent fc172117cc
commit 9aec3fbd78
16 files changed
+1949 -121

No files matched your search

+32 -14
View File
@@ -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
+18
View File
@@ -32,3 +32,21 @@
*.out *.out
*.app *.app
# ---> Build
build/
# ---> Environment (contains secrets)
.env
# ---> Runtime artifacts
*.db
*.csv
current_batch.json
# ---> IDE
.idea/
.vscode/
*.swp
*.swo
*~
+3 -2
View File
@@ -8,9 +8,10 @@ set(CMAKE_CXX_EXTENSIONS OFF)
find_package(OpenCV REQUIRED COMPONENTS core imgproc videoio highgui imgcodecs) find_package(OpenCV REQUIRED COMPONENTS core imgproc videoio highgui imgcodecs)
set(Eigen3_DIR /usr/share/eigen3/cmake) set(Eigen3_DIR /usr/share/eigen3/cmake)
find_package(Eigen3 REQUIRED NO_MODULE) find_package(Eigen3 REQUIRED NO_MODULE)
find_package(nlohmann_json REQUIRED)
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)
@@ -52,4 +53,4 @@ if(HAS_RKNN)
endif() endif()
endif() endif()
install(TARGETS bytetrack-counter DESTINATION /opt/bytetrack-counter-cpp/bin) install(TARGETS bytetrack-counter DESTINATION /opt/bytetrack-counter-cpp/build)
+36
View File
@@ -0,0 +1,36 @@
# ByteTrack Counter C++ — Run Instructions
## Run from build directory
```bash
cd /opt/bytetrack-counter-cpp/build
./bytetrack-counter /opt/bytetrack-counter-cpp/.env
```
The `.env` path is passed as the first argument. If omitted, it defaults to `.env` in the current working directory:
```bash
./bytetrack-counter # looks for .env in CWD
./bytetrack-counter .env # same
```
You can also set `ENV_FILE` environment variable instead:
```bash
ENV_FILE=/opt/bytetrack-counter-cpp/.env ./bytetrack-counter
```
## Run as systemd service
```bash
systemctl restart bytetrack-counter
journalctl -u bytetrack-counter -f
```
## Build
```bash
cd /opt/bytetrack-counter-cpp/build
cmake .. -DHAS_RKNN=ON
make -j$(nproc)
```
+1485
View File
File diff suppressed because it is too large. Load diff
+5
View File
@@ -213,6 +213,9 @@ bytetrack-counter_IS_TOP_LEVEL:STATIC=ON
//Value Computed by CMake //Value Computed by CMake
bytetrack-counter_SOURCE_DIR:STATIC=/opt/bytetrack-counter-cpp bytetrack-counter_SOURCE_DIR:STATIC=/opt/bytetrack-counter-cpp
//The directory containing a CMake configuration file for nlohmann_json.
nlohmann_json_DIR:PATH=/usr/share/cmake/nlohmann_json
######################## ########################
# INTERNAL cache entries # INTERNAL cache entries
@@ -351,6 +354,8 @@ CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
FIND_PACKAGE_MESSAGE_DETAILS_OpenCV:INTERNAL=[/usr][cfound components: core imgproc videoio highgui imgcodecs ][v4.6.0()] FIND_PACKAGE_MESSAGE_DETAILS_OpenCV:INTERNAL=[/usr][cfound components: core imgproc videoio highgui imgcodecs ][v4.6.0()]
//Details about finding SQLite3 //Details about finding SQLite3
FIND_PACKAGE_MESSAGE_DETAILS_SQLite3:INTERNAL=[/usr/include][/usr/lib/aarch64-linux-gnu/libsqlite3.so][v3.45.1()] FIND_PACKAGE_MESSAGE_DETAILS_SQLite3:INTERNAL=[/usr/include][/usr/lib/aarch64-linux-gnu/libsqlite3.so][v3.45.1()]
//Details about finding nlohmann_json
FIND_PACKAGE_MESSAGE_DETAILS_nlohmann_json:INTERNAL=[/usr/share/cmake/nlohmann_json/nlohmann_jsonConfig.cmake][v3.11.3()]
//ADVANCED property for variable: SQLite3_INCLUDE_DIR //ADVANCED property for variable: SQLite3_INCLUDE_DIR
SQLite3_INCLUDE_DIR-ADVANCED:INTERNAL=1 SQLite3_INCLUDE_DIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: SQLite3_LIBRARY //ADVANCED property for variable: SQLite3_LIBRARY
+3
View File
@@ -32,6 +32,9 @@ set(CMAKE_MAKEFILE_DEPENDS
"/usr/share/cmake-3.28/Modules/Platform/Linux-Initialize.cmake" "/usr/share/cmake-3.28/Modules/Platform/Linux-Initialize.cmake"
"/usr/share/cmake-3.28/Modules/Platform/Linux.cmake" "/usr/share/cmake-3.28/Modules/Platform/Linux.cmake"
"/usr/share/cmake-3.28/Modules/Platform/UnixPaths.cmake" "/usr/share/cmake-3.28/Modules/Platform/UnixPaths.cmake"
"/usr/share/cmake/nlohmann_json/nlohmann_jsonConfig.cmake"
"/usr/share/cmake/nlohmann_json/nlohmann_jsonConfigVersion.cmake"
"/usr/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake"
"/usr/share/eigen3/cmake/Eigen3Config.cmake" "/usr/share/eigen3/cmake/Eigen3Config.cmake"
"/usr/share/eigen3/cmake/Eigen3ConfigVersion.cmake" "/usr/share/eigen3/cmake/Eigen3ConfigVersion.cmake"
"/usr/share/eigen3/cmake/Eigen3Targets.cmake" "/usr/share/eigen3/cmake/Eigen3Targets.cmake"
Binary file not shown.
+8 -8
View File
@@ -43,25 +43,25 @@ if(NOT DEFINED CMAKE_OBJDUMP)
endif() endif()
if(CMAKE_INSTALL_COMPONENT STREQUAL "Unspecified" OR NOT CMAKE_INSTALL_COMPONENT) if(CMAKE_INSTALL_COMPONENT STREQUAL "Unspecified" OR NOT CMAKE_INSTALL_COMPONENT)
if(EXISTS "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter" AND if(EXISTS "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter" AND
NOT IS_SYMLINK "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter") NOT IS_SYMLINK "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter")
file(RPATH_CHECK file(RPATH_CHECK
FILE "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter" FILE "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter"
RPATH "") RPATH "")
endif() endif()
list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES
"/opt/bytetrack-counter-cpp/bin/bytetrack-counter") "/opt/bytetrack-counter-cpp/build/bytetrack-counter")
if(CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION) if(CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION)
message(WARNING "ABSOLUTE path INSTALL DESTINATION : ${CMAKE_ABSOLUTE_DESTINATION_FILES}") message(WARNING "ABSOLUTE path INSTALL DESTINATION : ${CMAKE_ABSOLUTE_DESTINATION_FILES}")
endif() endif()
if(CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION) if(CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION)
message(FATAL_ERROR "ABSOLUTE path INSTALL DESTINATION forbidden (by caller): ${CMAKE_ABSOLUTE_DESTINATION_FILES}") message(FATAL_ERROR "ABSOLUTE path INSTALL DESTINATION forbidden (by caller): ${CMAKE_ABSOLUTE_DESTINATION_FILES}")
endif() endif()
file(INSTALL DESTINATION "/opt/bytetrack-counter-cpp/bin" TYPE EXECUTABLE FILES "/opt/bytetrack-counter-cpp/build/bytetrack-counter") file(INSTALL DESTINATION "/opt/bytetrack-counter-cpp/build" TYPE EXECUTABLE FILES "/opt/bytetrack-counter-cpp/build/bytetrack-counter")
if(EXISTS "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter" AND if(EXISTS "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter" AND
NOT IS_SYMLINK "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter") NOT IS_SYMLINK "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter")
if(CMAKE_INSTALL_DO_STRIP) if(CMAKE_INSTALL_DO_STRIP)
execute_process(COMMAND "/usr/bin/strip" "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter") execute_process(COMMAND "/usr/bin/strip" "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter")
endif() endif()
endif() endif()
endif() endif()
Binary file not shown.
+152
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -134,7 +134,7 @@ TrackResult ByteTracker::update(const Eigen::MatrixXf& boxes_xyxy, const Eigen::
auto iou_mat = ious_xyxy(low_mat, unmatched_boxes); auto iou_mat = ious_xyxy(low_mat, unmatched_boxes);
auto cost_mat = Eigen::MatrixXf::Constant(n_low, n_unmatched, 1.0f) - iou_mat; auto cost_mat = Eigen::MatrixXf::Constant(n_low, n_unmatched, 1.0f) - iou_mat;
auto matches2 = greedy_match(cost_mat, 0.5f); auto matches2 = greedy_match(cost_mat, 1.0f - match_thresh_);
for (auto [di, uti] : matches2) { for (auto [di, uti] : matches2) {
int det_global = low_indices[di]; int det_global = low_indices[di];
+36 -11
View File
@@ -74,18 +74,32 @@ void AppConfig::load_from_env(const char* env_path) {
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 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);
daily_cutoff_time = get_env("DAILY_CUTOFF_TIME", daily_cutoff_time); 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", get_env("CUTOFF_TIME", daily_cutoff_time));
auto bto_s = get_env("BATCH_TIMEOUT_SECONDS"); auto bto_s = get_env("BATCH_TIMEOUT_SECONDS");
if (!bto_s.empty()) batch_timeout_seconds = std::stof(bto_s); if (!bto_s.empty()) batch_timeout_seconds = std::stof(bto_s);
auto ibl_s = get_env("IGNORE_BATCH_LABEL_TIMEOUT_SECONDS"); auto ibl_s = get_env("IGNORE_BATCH_LABEL_TIMEOUT_SECONDS");
@@ -125,4 +139,15 @@ void AppConfig::load_from_env(const char* env_path) {
if (!lsn_s.empty()) live_stream_every_n = std::stoi(lsn_s); if (!lsn_s.empty()) live_stream_every_n = std::stoi(lsn_s);
rtsp_ffmpeg_options = get_env("OPENCV_FFMPEG_CAPTURE_OPTIONS", rtsp_ffmpeg_options); rtsp_ffmpeg_options = get_env("OPENCV_FFMPEG_CAPTURE_OPTIONS", rtsp_ffmpeg_options);
auto cff_s = get_env("CROSS_FLASH_FRAMES");
if (!cff_s.empty()) cross_flash_frames = std::stoi(cff_s);
auto pl_s = get_env("POPUP_LIFETIME");
if (!pl_s.empty()) popup_lifetime = std::stoi(pl_s);
auto lpf_s = get_env("LINE_PULSE_FRAMES");
if (!lpf_s.empty()) line_pulse_frames = std::stoi(lpf_s);
auto cpf_s = get_env("COUNT_PULSE_FRAMES");
if (!cpf_s.empty()) count_pulse_frames = std::stoi(cpf_s);
auto bpf_s = get_env("BATCH_PULSE_FRAMES");
if (!bpf_s.empty()) batch_pulse_frames = std::stoi(bpf_s);
} }
+13 -5
View File
@@ -25,11 +25,19 @@ struct AppConfig {
int num_classes = 2; int num_classes = 2;
bool score_sigmoid = false; bool score_sigmoid = false;
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;
+15 -7
View File
@@ -105,12 +105,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;
@@ -122,6 +122,7 @@ int main(int argc, char* argv[]) {
auto session_start = std::chrono::steady_clock::now(); auto session_start = std::chrono::steady_clock::now();
int frame_idx = 0; int frame_idx = 0;
std::deque<std::chrono::steady_clock::time_point> crossing_times;
cv::VideoCapture cap = open_capture(g_config.source); cv::VideoCapture cap = open_capture(g_config.source);
bool is_live = g_config.source.substr(0, 7) == "rtsp://" || bool is_live = g_config.source.substr(0, 7) == "rtsp://" ||
@@ -317,6 +318,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,
@@ -380,7 +382,13 @@ int main(int argc, char* argv[]) {
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 elapsed = static_cast<double>(elapsed_sec);
double rate = elapsed > 0 ? (display_total / elapsed * 60.0) : 0.0; while (!crossing_times.empty() &&
std::chrono::duration_cast<std::chrono::seconds>(
mono - crossing_times.front()).count() > g_config.rate_window_sec) {
crossing_times.pop_front();
}
double rate = crossing_times.empty() ? 0.0
: (static_cast<double>(crossing_times.size()) / 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);