The engine was searching an empty room fifteen times a second

Measured rather than guessed, and the first guess was wrong. Wall clock
said H.265 decode cost 58 ms a frame; cap.read() blocks until the next
frame arrives, so that was the frame interval, not work. As CPU time:
decode 3.7 ms, detection 31.0 ms - and detection ran on every frame
whether or not anything was in front of the camera, 6,649 of 8,634
frames with faces_seen 0 and active_tracks 0 throughout.

detect_threads: OpenCV spreads a small repeated job over eight threads,
costing 31.0 ms of CPU for 8.9 ms of wall. One thread costs 15.3 ms for
15.3 ms, against a 66 ms budget at 15 fps. Half the CPU for latency
nothing can notice.

motion_gate: a 160x90 greyscale absdiff, 0.1 ms against detection's 15.
Consulted only while no track is open; forced to look every
motion_max_skip frames; compared against the last frame SEARCHED so a
slow drift cannot creep under the threshold; and a threshold above this
camera's measured noise and far below a person, so anything ambiguous
detects. tests/test_motion_gate.py pins each of those rather than the
saving, including asserting the longest run of skips rather than the
total - counting the total would pass a gate that slept forty frames
and then looked forty times.

Together 80% -> 16% of a core, detection skipped on 92% of frames.
faces_seen is still 0 and the gate is not why: run directly over the
same frames the detector finds nothing at threshold 0.50 either. The
placement is the limit, as recorded; the CPU was being spent to
rediscover that fifteen times a second.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
2026-09-24 13:58:05 +05:30
parent f97ffc913a
commit 2e60fbb57a
5 changed files with 242 additions and 0 deletions

View File

@@ -21,6 +21,17 @@ def cmd_run(args: argparse.Namespace) -> int:
cfg = load_config(args.config)
setup_logging(cfg.app.log_level, cfg.app.data_dir)
if cfg.app.detect_threads > 0:
# OpenCV sizes its pool for one big job on an idle machine. This is a
# small job repeated forever on a machine also running the recogniser,
# the tracker and possibly three other cameras, so the default costs
# twice the CPU for no useful latency. Measured: 31 ms CPU/frame at the
# default against 15 ms at one thread, for 6 ms more wall time against
# a 66 ms budget.
import cv2
cv2.setNumThreads(cfg.app.detect_threads)
log.info("detection threads: %d (OpenCV default was %d)",
cfg.app.detect_threads, cv2.getNumThreads())
missing = setup_models(cfg.app.models_dir)
if missing:
log.error("required models missing: %s", ", ".join(missing))