From 4b27db2e773bcd86a3f07c32a46f1fb237629f1f Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 20 Oct 2026 14:12:12 -0400 Subject: [PATCH] =?UTF-8?q?feat/igpu-ov:=20--probe-stride/--probe-max-per-?= =?UTF-8?q?scene=20=E2=80=94=20thin=20inference=20(not=20decode)=20on=20sl?= =?UTF-8?q?ow=20devices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- anime_facesaver.py | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/anime_facesaver.py b/anime_facesaver.py index 75a9b27..c71322e 100644 --- a/anime_facesaver.py +++ b/anime_facesaver.py @@ -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,