feat/igpu-ov: --probe-stride/--probe-max-per-scene — thin inference (not decode) on slow devices

Every-frame prediction is free on CUDA (~0.2s) and hopeless on the iGPU
(5.2s/img): a 100-frame faceless scene cost 8.7 min. stride>1 keeps
frame-perfect decode and probes every Nth frame, max M per scene, then
waits for the next scene start. Default stride=1 = original behavior.
Scheduler unit-tested (face-hit, faceless-exhaust, multi-scene).
This commit is contained in:
2026-10-20 14:12:12 -04:00
parent 460ab5f73e
commit 4b27db2e77
+39 -2
View File
@@ -119,6 +119,22 @@ def parse_arguments():
"edges. 0 (default) allows cropped faces; set >0 to reject "
"faces touching the crop lines.",
)
parser.add_argument(
"--probe-stride",
type=int,
default=1,
help="Within a scene still needing a face, run detection every Nth "
"frame instead of every frame (1 = original behavior, free on "
"CUDA). For slow devices (iGPU ~5s/img) use e.g. 25: decode "
"stays frame-perfect, only the expensive inference is thinned.",
)
parser.add_argument(
"--probe-max-per-scene",
type=int,
default=4,
help="With --probe-stride>1, probes before giving up on a scene and "
"waiting for the next scene start (default 4).",
)
parser.add_argument(
"--scene-threshold",
type=float,
@@ -276,6 +292,17 @@ def process_video(video_path, output_dir, args, model, device, draw,
face_in_scene = False
frame_idx = 0
# Probe schedule. probe_stride=1 reproduces the original every-frame
# loop (right on CUDA, where predict costs ~0.2 s). On slow devices
# (iGPU ~5 s/img) predicting every frame is hopeless: decode is ~300 fps,
# inference 0.2/s. With stride>1 we decode through frames for free and
# predict only at scene starts, retrying every stride frames up to
# probe_max attempts per scene, then waiting for the next scene start.
probe_stride = getattr(args, "probe_stride", 1)
probe_max = getattr(args, "probe_max_per_scene", 4)
next_probe = 0
probes_used = 0
while True:
ret, frame = cap.read()
if not ret:
@@ -283,10 +310,20 @@ def process_video(video_path, output_dir, args, model, device, draw,
if frame_idx in scene_starts:
face_in_scene = False
# membership test on a set; pop matched starts to keep it cheap
# (scenes strictly increase, so set stays small either way)
next_probe = frame_idx
probes_used = 0
if face_in_scene:
frame_idx += 1
continue
if probe_stride > 1 and (frame_idx < next_probe
or probes_used >= probe_max):
frame_idx += 1
continue
if not face_in_scene:
probes_used += 1
next_probe = frame_idx + probe_stride
try:
results = model.predict(frame, conf=args.conf,
iou=args.iou, imgsz=args.imgsz,