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

+142 -73
View File
@@ -10,6 +10,7 @@ import csv
import os
import signal
import time
from collections import deque
from datetime import datetime
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"
# ByteTrack settings
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"))
# 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"))
DAILY_CUTOFF_TIME = os.getenv("DAILY_CUTOFF_TIME", "20:00")
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"
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"))
@@ -345,11 +354,13 @@ 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)
@@ -389,10 +400,11 @@ 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[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()
match_pairs_high.append((det_global, ti))
@@ -408,17 +420,20 @@ 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=0.5)
matches2 = _greedy_match(
cost_mat, threshold=1.0 - self.match_thresh
)
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[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[
pool_idx
].get_cx()
@@ -454,10 +469,11 @@ class ByteTracker:
high_all = np.where(is_high)[0]
matched_det_ids = set(det_to_track.keys())
for dg in high_all:
if int(dg) not in matched_det_ids:
trk = KalmanBoxTracker(dets[dg])
orig_idx = int(remain_orig_idx[int(dg)])
if orig_idx not in matched_det_ids:
trk = KalmanBoxTracker(dets[int(dg)])
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()
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)
font = cv2.FONT_HERSHEY_SIMPLEX
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)
pad = 14
tx, ty = line_x - tw // 2, h // 2 + th // 2
tx, ty = line_x - tw // 2, h // 3 + th // 2
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(
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)
def draw_hud(
img, w, batch_num, batch_count, total_ayam, elapsed_sec, rate, camera_id, clock
):
bar_h = 52
def draw_hud(img, w, batch_num, batch_count, total_ayam, elapsed_sec, rate):
bar_h = 40
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", (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(
img,
batch_label,
(16, 44),
(14, 32),
cv2.FONT_HERSHEY_SIMPLEX,
0.9,
0.55,
C_ACCENT,
2,
1,
cv2.LINE_AA,
)
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(
img,
str(batch_count),
(100, 44),
(90, 32),
cv2.FONT_HERSHEY_SIMPLEX,
0.9,
0.55,
C_GREEN,
2,
1,
cv2.LINE_AA,
)
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(
img,
str(total_ayam),
(190, 44),
(170, 32),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
0.45,
C_TEXT,
1,
cv2.LINE_AA,
@@ -811,9 +825,9 @@ def draw_hud(
cv2.putText(
img,
"UPTIME",
(280, 20),
(250, 14),
cv2.FONT_HERSHEY_SIMPLEX,
0.45,
0.32,
C_MUTED,
1,
cv2.LINE_AA,
@@ -821,45 +835,34 @@ def draw_hud(
cv2.putText(
img,
f"{elapsed_sec / 3600:.1f}h",
(280, 44),
(250, 32),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
0.45,
C_TEXT,
1,
cv2.LINE_AA,
)
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(
img,
f"{rate:.1f}/min",
(380, 44),
(340, 32),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
0.45,
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_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
overlay_rect(img, 0, h - bar_h, w, h, C_PANEL, alpha=0.55)
cv2.putText(
img,
f"{live_tag} | Frame {frame_idx}",
f"{live_tag} | {model_name} | Frame {frame_idx} | Inf {inf_ms:.1f}ms",
(12, h - 9),
cv2.FONT_HERSHEY_SIMPLEX,
0.45,
@@ -982,26 +985,22 @@ def run():
score_sigmoid=SCORE_SIGMOID,
)
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_cls = int(os.getenv("AYAM_CLASS_ID", "0"))
talenan_cls = int(os.getenv("TALENAN_CLASS_ID", "1"))
ayam_tracker = ByteTracker(
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,
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,
)
talenan_tracker = ByteTracker(
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,
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,
)
ayam_line_crossed = set()
@@ -1014,7 +1013,9 @@ 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:
@@ -1028,8 +1029,12 @@ def run():
)
print(f"Model: {MODEL_PATH} | imgsz={IMGSZ} | core_mask={CORE_MASK}")
print(
f"ByteTrack: high_thresh={TRACK_HIGH_THRESH} low_thresh={TRACK_LOW_THRESH} "
f"match_thresh={TRACK_MATCH_THRESH} buffer={TRACK_BUFFER}"
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}"
)
print(f"DB: {DB_PATH}")
print(f"State: {STATE_FILE}")
@@ -1061,7 +1066,9 @@ 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 = []
@@ -1104,6 +1111,56 @@ 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)
@@ -1145,6 +1202,11 @@ 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:
@@ -1159,6 +1221,7 @@ 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(
{
@@ -1216,7 +1279,9 @@ def run():
batch_num = store.current_batch_number or 0
batch_count = store.current_batch_count
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_hero_count(frame, line_x, h, batch_count, count_pulse)
@@ -1228,12 +1293,16 @@ 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-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)