Revert "Level up with python version"

This reverts commit 9aec3fbd78.
This commit is contained in:
proitlab committed 2026-07-06 03:17:44 +07:00
1 parent 9aec3fbd78
commit 5483837769
16 files changed
+121 -1949

No files matched your search

+14 -32
View File
@@ -1,20 +1,17 @@
# Edge RK3588 production counter — copy to .env on device
# cp config.env.example .env && nano .env
# cp .env.example .env && nano .env
#DEBUG_TRACKING=true
SITE_NAME=Salatiga
OUTPUT_DIR=/opt/bytetrack-counter
DB_PATH=/opt/bytetrack-counter/bytetrack_counter.db
OUTPUT_DIR=/opt/bytetrack-counter-cpp
DB_PATH=/opt/bytetrack-counter-cpp/bytetrack_counter.db
STATE_FILE=/tmp/bytetrack_current_batch.json
# Direct LAN camera RTSP (low latency)
#SOURCE=rtsp://frigate:zenai@127.0.0.1:8554/camera_stream_640
SOURCE=rtsp://10.38.30.64:8554/my_stream
#SOURCE=rtsp://frigate:zenai@192.168.192.16:8554/camera_stream_640
SOURCE=rtsp://192.168.192.53:8554/my_stream
OPENCV_FFMPEG_CAPTURE_OPTIONS=rtsp_transport;tcp|fflags;nobuffer|flags;low_delay
# RKNN model — export'd from YOLO9t with imgsz=320
MODEL_PATH=/opt/models/zenai_apc_salatiga_20260703.rknn
# RKNN model — export'd from YOLO with imgsz=320
MODEL_PATH=/opt/models/zenai_apc_cicalengka_20260609.rknn
IMGSZ=320
HALF=false
CONF=0.3
@@ -24,31 +21,24 @@ CORE_MASK=7
# YOLO decoder params (must match model export)
NUM_CLASSES=2
NUM_KEYPOINTS=9
REG_MAX=16
STRIDES=8,16,32
SCORE_SIGMOID=false
CAMERA_NAME=ZenAi
CAMERA_NAME=CC1
OBJECT_LABEL=ayam-potong
CLASS_AYAM=ayam
CLASS_TALENAN=talenan
CLASS_TALENAN=telenan
# Line crossing: rtl (default) | ltr | both
CROSS_DIRECTION=rtl
CROSS_DIRECTION=ltr
LINE_X=
LINE_X_FRAC=0.5
DAILY_CUTOFF_TIME=20:00
CUTOFF_TIME=20:00
DAILY_CUTOFF_TIME=17:00
BATCH_TIMEOUT_SECONDS=300
IGNORE_BATCH_LABEL_TIMEOUT_SECONDS=30
MIN_OBJECT_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
RECORD_VIDEO=false
@@ -57,14 +47,6 @@ LIVE_STREAM_FRAME_PATH=/dev/shm/bytetrack-counter/live_frame.jpg
LIVE_STREAM_QUALITY=75
LIVE_STREAM_EVERY_N=2
# Lower high_thresh to match CONF so all valid detections create tracks
TRACK_HIGH_THRESH_0=0.3
TRACK_HIGH_THRESH_1=0.3
LOG_LEVEL=info
# 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
DASHBOARD_PORT=5001
-18
View File
@@ -32,21 +32,3 @@
*.out
*.app
# ---> Build
build/
# ---> Environment (contains secrets)
.env
# ---> Runtime artifacts
*.db
*.csv
current_batch.json
# ---> IDE
.idea/
.vscode/
*.swp
*.swo
*~
+2 -3
View File
@@ -8,10 +8,9 @@ set(CMAKE_CXX_EXTENSIONS OFF)
find_package(OpenCV REQUIRED COMPONENTS core imgproc videoio highgui imgcodecs)
set(Eigen3_DIR /usr/share/eigen3/cmake)
find_package(Eigen3 REQUIRED NO_MODULE)
find_package(nlohmann_json REQUIRED)
find_package(SQLite3 REQUIRED)
option(HAS_RKNN "Build with RKNN NPU support" ON)
option(HAS_RKNN "Build with RKNN NPU support" OFF)
if(HAS_RKNN)
add_definitions(-DHAS_RKNN)
@@ -53,4 +52,4 @@ if(HAS_RKNN)
endif()
endif()
install(TARGETS bytetrack-counter DESTINATION /opt/bytetrack-counter-cpp/build)
install(TARGETS bytetrack-counter DESTINATION /opt/bytetrack-counter-cpp/bin)
-36
View File
@@ -1,36 +0,0 @@
# 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,9 +213,6 @@ bytetrack-counter_IS_TOP_LEVEL:STATIC=ON
//Value Computed by CMake
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
@@ -354,8 +351,6 @@ CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
FIND_PACKAGE_MESSAGE_DETAILS_OpenCV:INTERNAL=[/usr][cfound components: core imgproc videoio highgui imgcodecs ][v4.6.0()]
//Details about finding SQLite3
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
SQLite3_INCLUDE_DIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: SQLite3_LIBRARY
-3
View File
@@ -32,9 +32,6 @@ set(CMAKE_MAKEFILE_DEPENDS
"/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/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/Eigen3ConfigVersion.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()
if(CMAKE_INSTALL_COMPONENT STREQUAL "Unspecified" OR NOT CMAKE_INSTALL_COMPONENT)
if(EXISTS "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter" AND
NOT IS_SYMLINK "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter")
if(EXISTS "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter" AND
NOT IS_SYMLINK "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter")
file(RPATH_CHECK
FILE "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter"
FILE "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter"
RPATH "")
endif()
list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES
"/opt/bytetrack-counter-cpp/build/bytetrack-counter")
"/opt/bytetrack-counter-cpp/bin/bytetrack-counter")
if(CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION)
message(WARNING "ABSOLUTE path INSTALL DESTINATION : ${CMAKE_ABSOLUTE_DESTINATION_FILES}")
endif()
if(CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION)
message(FATAL_ERROR "ABSOLUTE path INSTALL DESTINATION forbidden (by caller): ${CMAKE_ABSOLUTE_DESTINATION_FILES}")
endif()
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/build/bytetrack-counter" AND
NOT IS_SYMLINK "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter")
file(INSTALL DESTINATION "/opt/bytetrack-counter-cpp/bin" TYPE EXECUTABLE FILES "/opt/bytetrack-counter-cpp/build/bytetrack-counter")
if(EXISTS "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter" AND
NOT IS_SYMLINK "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter")
if(CMAKE_INSTALL_DO_STRIP)
execute_process(COMMAND "/usr/bin/strip" "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/build/bytetrack-counter")
execute_process(COMMAND "/usr/bin/strip" "$ENV{DESTDIR}/opt/bytetrack-counter-cpp/bin/bytetrack-counter")
endif()
endif()
endif()
Binary file not shown.
-152
View File
@@ -1,152 +0,0 @@
# =============================================================================
# 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
+73 -142
View File
@@ -10,7 +10,6 @@ import csv
import os
import signal
import time
from collections import deque
from datetime import datetime
from pathlib import Path
@@ -49,18 +48,11 @@ NUM_CLASSES = int(os.getenv("NUM_CLASSES", "2"))
SCORE_SIGMOID = os.getenv("SCORE_SIGMOID", "false").lower() == "true"
# ByteTrack settings
# Ayam index 0
TRACK_HIGH_THRESH_0 = float(os.getenv("TRACK_HIGH_THRESH_0", "0.5"))
TRACK_LOW_THRESH_0 = float(os.getenv("TRACK_LOW_THRESH_0", "0.1"))
TRACK_MATCH_THRESH_0 = float(os.getenv("TRACK_MATCH_THRESH_0", "0.8"))
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"))
TRACK_HIGH_THRESH = float(os.getenv("TRACK_HIGH_THRESH", "0.5"))
TRACK_LOW_THRESH = float(os.getenv("TRACK_LOW_THRESH", "0.1"))
TRACK_MATCH_THRESH = float(os.getenv("TRACK_MATCH_THRESH", "0.8"))
TRACK_BUFFER = int(os.getenv("TRACK_BUFFER", "30"))
TRACK_MIN_HITS = int(os.getenv("TRACK_MIN_HITS", "3"))
DAILY_CUTOFF_TIME = os.getenv("DAILY_CUTOFF_TIME", "20:00")
BATCH_TIMEOUT_SECONDS = float(os.getenv("BATCH_TIMEOUT_SECONDS", "300"))
@@ -73,7 +65,6 @@ MIN_DURATION_PER_BATCH = int(os.getenv("MIN_DURATION_PER_BATCH", "60"))
EXPORT_CSV = os.getenv("EXPORT_CSV", "true").lower() == "true"
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"))
RECONNECT_DELAY_SEC = int(os.getenv("RECONNECT_DELAY_SEC", "3"))
MAX_RECONNECT_ATTEMPTS = int(os.getenv("MAX_RECONNECT_ATTEMPTS", "0"))
@@ -354,13 +345,11 @@ class ByteTracker:
# --- separate detections by score ---
if len(boxes_xyxy) > 0:
remain = scores > self.low_thresh
remain_orig_idx = np.where(remain)[0]
dets = boxes_xyxy[remain]
det_scores = scores[remain]
is_high = det_scores > self.high_thresh
is_low = ~is_high
else:
remain_orig_idx = np.zeros(0, dtype=np.int64)
dets = np.zeros((0, 4), dtype=np.float32)
det_scores = np.zeros(0, dtype=np.float32)
is_high = np.zeros(0, dtype=bool)
@@ -400,11 +389,10 @@ class ByteTracker:
for di, ti in matches:
det_global = int(high_idx[di])
orig_idx = int(remain_orig_idx[det_global])
track_pool[ti].update(dets[det_global])
track_pool[ti].hit_streak = max(1, track_pool[ti].hit_streak)
matched_track_idx.add(ti)
det_to_track[orig_idx] = track_pool[ti].track_id
det_to_track[det_global] = track_pool[ti].track_id
tracked_map[track_pool[ti].track_id] = track_pool[ti].get_cx()
match_pairs_high.append((det_global, ti))
@@ -420,20 +408,17 @@ class ByteTracker:
unmatched_boxes = track_boxes[unmatched_tracks]
iou_mat = _ious_xyxy(low_dets, unmatched_boxes)
cost_mat = 1.0 - iou_mat
matches2 = _greedy_match(
cost_mat, threshold=1.0 - self.match_thresh
)
matches2 = _greedy_match(cost_mat, threshold=0.5)
for di, uti in matches2:
det_global = int(low_idx[di])
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].hit_streak = max(
1, track_pool[pool_idx].hit_streak
)
matched_track_idx.add(pool_idx)
det_to_track[orig_idx] = track_pool[pool_idx].track_id
det_to_track[det_global] = track_pool[pool_idx].track_id
tracked_map[track_pool[pool_idx].track_id] = track_pool[
pool_idx
].get_cx()
@@ -469,11 +454,10 @@ class ByteTracker:
high_all = np.where(is_high)[0]
matched_det_ids = set(det_to_track.keys())
for dg in high_all:
orig_idx = int(remain_orig_idx[int(dg)])
if orig_idx not in matched_det_ids:
trk = KalmanBoxTracker(dets[int(dg)])
if int(dg) not in matched_det_ids:
trk = KalmanBoxTracker(dets[dg])
self.tracked_tracks.append(trk)
det_to_track[orig_idx] = trk.track_id
det_to_track[int(dg)] = trk.track_id
tracked_map[trk.track_id] = trk.get_cx()
return tracked_map, det_to_track, lost_map
@@ -765,59 +749,61 @@ def draw_hero_count(img, line_x, h, count, pulse_remaining=0):
text = str(count)
font = cv2.FONT_HERSHEY_SIMPLEX
boost = 0.35 * (pulse_remaining / max(COUNT_PULSE_FRAMES, 1))
font_scale, thickness = 1.4 + boost, 3
font_scale, thickness = 1.6 + boost, 3
(tw, th), _ = cv2.getTextSize(text, font, font_scale, thickness)
pad = 14
tx, ty = line_x - tw // 2, h // 3 + th // 2
tx, ty = line_x - tw // 2, h // 2 + th // 2
overlay_rect(
img, tx - pad, ty - th - pad, tx + tw + pad, ty + pad, C_PANEL, alpha=0.78
img, tx - pad, ty - th - pad, tx + tw + pad, ty + pad // 2, C_PANEL, alpha=0.78
)
cv2.rectangle(
img, (tx - pad, ty - th - pad), (tx + tw + pad, ty + pad), C_LINE_CORE, 2
img, (tx - pad, ty - th - pad), (tx + tw + pad, ty + pad // 2), C_LINE_CORE, 2
)
cv2.putText(img, text, (tx, ty), font, font_scale, C_GREEN, thickness, cv2.LINE_AA)
def draw_hud(img, w, batch_num, batch_count, total_ayam, elapsed_sec, rate):
bar_h = 40
def draw_hud(
img, w, batch_num, batch_count, total_ayam, elapsed_sec, rate, camera_id, clock
):
bar_h = 52
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.putText(
img, "BATCH", (14, 14), cv2.FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv2.LINE_AA
img, "BATCH", (16, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv2.LINE_AA
)
batch_label = str(batch_num) if batch_num else "--"
batch_label = str(batch_num) if batch_num else "\u2014"
cv2.putText(
img,
batch_label,
(14, 32),
(16, 44),
cv2.FONT_HERSHEY_SIMPLEX,
0.55,
0.9,
C_ACCENT,
1,
2,
cv2.LINE_AA,
)
cv2.putText(
img, "COUNT", (90, 14), cv2.FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv2.LINE_AA
img, "COUNT", (100, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv2.LINE_AA
)
cv2.putText(
img,
str(batch_count),
(90, 32),
(100, 44),
cv2.FONT_HERSHEY_SIMPLEX,
0.55,
0.9,
C_GREEN,
1,
2,
cv2.LINE_AA,
)
cv2.putText(
img, "TOTAL", (170, 14), cv2.FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv2.LINE_AA
img, "TOTAL", (190, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv2.LINE_AA
)
cv2.putText(
img,
str(total_ayam),
(170, 32),
(190, 44),
cv2.FONT_HERSHEY_SIMPLEX,
0.45,
0.7,
C_TEXT,
1,
cv2.LINE_AA,
@@ -825,9 +811,9 @@ def draw_hud(img, w, batch_num, batch_count, total_ayam, elapsed_sec, rate):
cv2.putText(
img,
"UPTIME",
(250, 14),
(280, 20),
cv2.FONT_HERSHEY_SIMPLEX,
0.32,
0.45,
C_MUTED,
1,
cv2.LINE_AA,
@@ -835,34 +821,45 @@ def draw_hud(img, w, batch_num, batch_count, total_ayam, elapsed_sec, rate):
cv2.putText(
img,
f"{elapsed_sec / 3600:.1f}h",
(250, 32),
(280, 44),
cv2.FONT_HERSHEY_SIMPLEX,
0.45,
0.7,
C_TEXT,
1,
cv2.LINE_AA,
)
cv2.putText(
img, "RATE", (340, 14), cv2.FONT_HERSHEY_SIMPLEX, 0.32, C_MUTED, 1, cv2.LINE_AA
img, "RATE", (380, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.45, C_MUTED, 1, cv2.LINE_AA
)
cv2.putText(
img,
f"{rate:.1f}/min",
(340, 32),
(380, 44),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
C_ACCENT,
1,
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_ACCENT,
C_MUTED,
1,
cv2.LINE_AA,
)
def draw_footer(img, w, h, frame_idx, live_tag, inf_ms=0.0, model_name=""):
def draw_footer(img, w, h, frame_idx, live_tag):
bar_h = 28
overlay_rect(img, 0, h - bar_h, w, h, C_PANEL, alpha=0.55)
cv2.putText(
img,
f"{live_tag} | {model_name} | Frame {frame_idx} | Inf {inf_ms:.1f}ms",
f"{live_tag} | Frame {frame_idx}",
(12, h - 9),
cv2.FONT_HERSHEY_SIMPLEX,
0.45,
@@ -985,22 +982,26 @@ def run():
score_sigmoid=SCORE_SIGMOID,
)
ayam_cls = int(os.getenv("AYAM_CLASS_ID", "0"))
talenan_cls = int(os.getenv("TALENAN_CLASS_ID", "1"))
CLASS_IDS = {
os.getenv("CLASS_AYAM", "ayam"): 0,
os.getenv("CLASS_TALENAN", "talenan"): 1,
}
ayam_cls = CLASS_IDS[CLASS_AYAM]
talenan_cls = CLASS_IDS[CLASS_TALENAN]
ayam_tracker = ByteTracker(
track_high_thresh=TRACK_HIGH_THRESH_0,
track_low_thresh=TRACK_LOW_THRESH_0,
match_thresh=TRACK_MATCH_THRESH_0,
track_buffer=TRACK_BUFFER_0,
min_hits=TRACK_MIN_HITS_0,
track_high_thresh=TRACK_HIGH_THRESH,
track_low_thresh=TRACK_LOW_THRESH,
match_thresh=TRACK_MATCH_THRESH,
track_buffer=TRACK_BUFFER,
min_hits=TRACK_MIN_HITS,
)
talenan_tracker = ByteTracker(
track_high_thresh=TRACK_HIGH_THRESH_1,
track_low_thresh=TRACK_LOW_THRESH_1,
match_thresh=TRACK_MATCH_THRESH_1,
track_buffer=TRACK_BUFFER_1,
min_hits=TRACK_MIN_HITS_1,
track_high_thresh=TRACK_HIGH_THRESH,
track_low_thresh=TRACK_LOW_THRESH,
match_thresh=TRACK_MATCH_THRESH,
track_buffer=TRACK_BUFFER,
min_hits=TRACK_MIN_HITS,
)
ayam_line_crossed = set()
@@ -1013,9 +1014,7 @@ def run():
session_start = time.time()
frame_idx = 0
inf_ms = 0.0
video_writer = None
crossing_times = deque()
cap, w, h, fps = connect_stream(SOURCE)
if cap is None:
@@ -1029,12 +1028,8 @@ def run():
)
print(f"Model: {MODEL_PATH} | imgsz={IMGSZ} | core_mask={CORE_MASK}")
print(
f"ByteTrack Index 0: high_thresh={TRACK_HIGH_THRESH_0} low_thresh={TRACK_LOW_THRESH_0} "
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}"
f"ByteTrack: high_thresh={TRACK_HIGH_THRESH} low_thresh={TRACK_LOW_THRESH} "
f"match_thresh={TRACK_MATCH_THRESH} buffer={TRACK_BUFFER}"
)
print(f"DB: {DB_PATH}")
print(f"State: {STATE_FILE}")
@@ -1066,9 +1061,7 @@ def run():
mono = time.monotonic()
ayam_crossed_frame = batch_closed_frame = batch_started_frame = False
inf_start = time.time()
detections = model(frame)
inf_ms = inf_ms * 0.9 + (time.time() - inf_start) * 1000 * 0.1
if detections:
ayam_boxes_xyxy = []
@@ -1111,56 +1104,6 @@ def run():
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
for di in range(len(talenan_boxes_xyxy)):
tid = talenan_det_to_track.get(di)
@@ -1202,11 +1145,6 @@ def run():
crossed_line(prev_cx, cx, line_x)
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)
_, started_new = store.record_ayam_crossing(tid)
if cross_logger:
@@ -1221,7 +1159,6 @@ def run():
ayam_crossed_frame = True
if started_new:
batch_started_frame = True
crossing_times.append(mono)
ayam_cross_flash[tid] = CROSS_FLASH_FRAMES
popups.append(
{
@@ -1279,9 +1216,7 @@ def run():
batch_num = store.current_batch_number or 0
batch_count = store.current_batch_count
display_total = store.display_total()
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
rate = (display_total / elapsed * 60) if elapsed > 0 else 0.0
draw_elegant_counting_line(frame, line_x, h, line_pulse)
draw_hero_count(frame, line_x, h, batch_count, count_pulse)
@@ -1293,16 +1228,12 @@ def run():
display_total,
elapsed,
rate,
CAMERA_NAME,
now_str(),
)
draw_batch_banner(frame, w, batch_num, batch_pulse)
draw_footer(
frame,
w,
h,
frame_idx,
"LIVE" if IS_LIVE else "FILE",
inf_ms,
Path(MODEL_PATH).name,
frame, w, h, frame_idx, "LIVE-RKNN-BT" if IS_LIVE else "FILE-RKNN-BT"
)
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 cost_mat = Eigen::MatrixXf::Constant(n_low, n_unmatched, 1.0f) - iou_mat;
auto matches2 = greedy_match(cost_mat, 1.0f - match_thresh_);
auto matches2 = greedy_match(cost_mat, 0.5f);
for (auto [di, uti] : matches2) {
int det_global = low_indices[di];
+11 -36
View File
@@ -74,32 +74,18 @@ void AppConfig::load_from_env(const char* env_path) {
auto ss_s = get_env("SCORE_SIGMOID");
score_sigmoid = (ss_s == "true");
auto th0_s = get_env("TRACK_HIGH_THRESH_0");
if (!th0_s.empty()) track_high_thresh_0 = std::stof(th0_s);
auto tl0_s = get_env("TRACK_LOW_THRESH_0");
if (!tl0_s.empty()) track_low_thresh_0 = std::stof(tl0_s);
auto tm0_s = get_env("TRACK_MATCH_THRESH_0");
if (!tm0_s.empty()) track_match_thresh_0 = std::stof(tm0_s);
auto tb0_s = get_env("TRACK_BUFFER_0");
if (!tb0_s.empty()) track_buffer_0 = std::stoi(tb0_s);
auto tmh0_s = get_env("TRACK_MIN_HITS_0");
if (!tmh0_s.empty()) track_min_hits_0 = std::stoi(tmh0_s);
auto th_s = get_env("TRACK_HIGH_THRESH");
if (!th_s.empty()) track_high_thresh = std::stof(th_s);
auto tl_s = get_env("TRACK_LOW_THRESH");
if (!tl_s.empty()) track_low_thresh = std::stof(tl_s);
auto tm_s = get_env("TRACK_MATCH_THRESH");
if (!tm_s.empty()) track_match_thresh = std::stof(tm_s);
auto tb_s = get_env("TRACK_BUFFER");
if (!tb_s.empty()) track_buffer = std::stoi(tb_s);
auto tmh_s = get_env("TRACK_MIN_HITS");
if (!tmh_s.empty()) track_min_hits = std::stoi(tmh_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", get_env("CUTOFF_TIME", daily_cutoff_time));
daily_cutoff_time = get_env("DAILY_CUTOFF_TIME", daily_cutoff_time);
auto bto_s = get_env("BATCH_TIMEOUT_SECONDS");
if (!bto_s.empty()) batch_timeout_seconds = std::stof(bto_s);
auto ibl_s = get_env("IGNORE_BATCH_LABEL_TIMEOUT_SECONDS");
@@ -139,15 +125,4 @@ void AppConfig::load_from_env(const char* env_path) {
if (!lsn_s.empty()) live_stream_every_n = std::stoi(lsn_s);
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);
}
+5 -13
View File
@@ -25,19 +25,11 @@ struct AppConfig {
int num_classes = 2;
bool score_sigmoid = false;
float track_high_thresh_0 = 0.5f;
float track_low_thresh_0 = 0.1f;
float track_match_thresh_0 = 0.8f;
int track_buffer_0 = 30;
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;
float track_high_thresh = 0.5f;
float track_low_thresh = 0.1f;
float track_match_thresh = 0.8f;
int track_buffer = 30;
int track_min_hits = 3;
std::string daily_cutoff_time = "20:00";
float batch_timeout_seconds = 300.0f;
+7 -15
View File
@@ -105,12 +105,12 @@ int main(int argc, char* argv[]) {
g_config.imgsz, g_config.conf, 0.45f,
g_config.num_classes, 0, g_config.score_sigmoid);
ByteTracker ayam_tracker(g_config.track_high_thresh_0, g_config.track_low_thresh_0,
g_config.track_match_thresh_0, g_config.track_buffer_0,
g_config.track_min_hits_0);
ByteTracker talenan_tracker(g_config.track_high_thresh_1, g_config.track_low_thresh_1,
g_config.track_match_thresh_1, g_config.track_buffer_1,
g_config.track_min_hits_1);
ByteTracker ayam_tracker(g_config.track_high_thresh, g_config.track_low_thresh,
g_config.track_match_thresh, g_config.track_buffer,
g_config.track_min_hits);
ByteTracker talenan_tracker(g_config.track_high_thresh, g_config.track_low_thresh,
g_config.track_match_thresh, g_config.track_buffer,
g_config.track_min_hits);
std::unordered_set<int> ayam_line_crossed;
std::unordered_set<int> talenan_line_crossed;
@@ -122,7 +122,6 @@ int main(int argc, char* argv[]) {
auto session_start = std::chrono::steady_clock::now();
int frame_idx = 0;
std::deque<std::chrono::steady_clock::time_point> crossing_times;
cv::VideoCapture cap = open_capture(g_config.source);
bool is_live = g_config.source.substr(0, 7) == "rtsp://" ||
@@ -318,7 +317,6 @@ int main(int argc, char* argv[]) {
}
ayam_crossed_frame = true;
if (started_new) batch_started_frame = true;
crossing_times.push_back(mono);
ayam_cross_flash[tid] = g_config.cross_flash_frames;
popups.push_back({
static_cast<int>(cx) - 12,
@@ -382,13 +380,7 @@ int main(int argc, char* argv[]) {
int batch_count = store.current_batch_count();
int display_total = store.display_total();
double elapsed = static_cast<double>(elapsed_sec);
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);
double rate = elapsed > 0 ? (display_total / elapsed * 60.0) : 0.0;
draw_elegant_counting_line(frame, line_x, h, line_pulse);
draw_hero_count(frame, line_x, h, batch_count, count_pulse);