alex d9312ed431 find_dupes: streaming row-chunked compare (fits ~4GB cgroup caps)
The single-matrix fix still oom-killed at n=31,937: Hermes background
jobs run in a systemd scope capped near 4GB, and the n^2 uint64 xor
matrix alone is 8GB. Now row chunks of 1024 stream through both hash
gates (peak a few hundred MB temps), the only resident n^2 structure
is the bool close-matrix (1GB), and the color-histogram L1 is computed
only for rows surviving both hashes (replaces the 4GB float32 matrix).
Verified on a 2,502-file corpus incl planted cross-chunk duplicate
pair and an orphan copy (correctly untouched).
2026-10-29 08:04:39 -04:00

anime-facesaver

Scene-gated still extractor for building anime training datasets from video. Fork of facesaver, created Oct 2026.

Why this fork exists

The original facesaver ran stock COCO yolov11l.pt and accepted class 0 — person — as a "proxy for face" (their comment, not ours). COCO has no face class, so every frame containing a large enough person passed: full-body shots, headless crops, a hand at 75% confidence. That is why the old Gabriel Dropout output contained stills of shoes.

anime-facesaver uses a dedicated anime-face detector instead: yolov8x6_animeface — yolov8x6 fine-tuned at 1280px on 10k manually annotated anime images (P=0.957 R=0.924 mAP50=0.955 on its test set). It detects faces. Only faces. The scene-change gate from the original is preserved: at most one still is saved per detected scene, which keeps near-duplicates out of the dataset.

Setup

uv venv --python 3.12 .venv           # or: python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt       # add --index-url for your CUDA ver
python anime_facesaver.py --download-model ...   # fetches weights once

Weights land in models/yolov8x6_animeface.pt (~195 MB, AGPL-3.0).

Usage

python anime_facesaver.py \
    -I /home/alex/Documents/image-training-datasets/gabriel-dropout-show \
    -O /home/alex/Documents/image-training-datasets/gabriel-dropout-v2 \
    --gpu 1 \
    --preview-dir /home/alex/Documents/image-training-datasets/gabriel-dropout-v2-preview

GPU pinning (important on the shared .80 box)

--gpu sets CUDA_VISIBLE_DEVICES before torch initializes, so no context leaks onto the wrong card:

flag effect
--gpu 1 pin to physical GPU 1 (recommended on .80)
--gpu 0 pin to GPU 0 — OOMs while Strata holds it
--gpu cpu force CPU
--gpu auto let torch pick

Key options

flag default meaning
--conf 0.3 detector confidence (model's own recommendation; it is high-precision)
--min-width / --min-height 100 minimum face box size in px
--min-face-fraction 0.005 face box area as fraction of frame — kills distant background extras; 0 disables
--max-face-fraction 0.30 face box area upper cap — kills detector meltdowns on full-frame ED/OP effects (feather storms etc.); 0 disables
--min-aspect / --max-aspect 0.45 / 1.6 face box W/H ratio window — effect blobs rarely keep face proportions
--edge-margin 0 require faces this far inside frame edges; 0 accepts edge-touching faces (original hard-rejected them)
--scene-threshold 30.0 PySceneDetect ContentDetector threshold (same as original)
--probe-stride 1 within a scene still needing a face, run inference every Nth frame instead of every frame (1 = original behavior). Decode stays frame-perfect; only inference is thinned
--probe-max-per-scene 4 with --probe-stride > 1, probes per scene before giving up and waiting for the next scene start
--imgsz 1280 inference size; model was trained at 1280, 640 is faster at some recall cost
--all-files off also scan OP/ED files. Off by default: music videos are full of stylized graphics (feather storms, transformation emblems) that false-positive as face-shaped; episodes carry the real character close-ups
--preview-dir off also write annotated copies (green boxes) of every saved frame for eyeball QC

Probe scheduling (thinned inference)

The original loop predicts on every frame until a scene yields a face — free on a 3090 (~0.2 s/predict), hopeless on an iGPU (~5 s/img). With --probe-stride N the decode stays frame-perfect but inference runs only at scene starts and then every Nth frame, giving up after --probe-max-per-scene probes and waiting for the next scene. Ported from the feat/igpu-ov branch to main (Oct 28) so both platforms share one schedule.

mode (same 43k-frame special, 3090 GPU1) wall time stills
default (every frame) 6m11s 475
--probe-stride 25 --probe-max-per-scene 4 2m34s 459 (96.6%)

Use 25/4 for real runs; stride 1 keeps byte-identical behavior, so older extracted corpora are unaffected. Accepted trade-off: a face visible for less than ~stride frames between probes can slip through.

Notes

  • Runs every frame of every scene until a face qualifies, then waits for the next scene start. That is the original behavior and it is why dense scenes don't flood the output.
  • The detector ships as yolov8x6 — big and a little slow (it was trained to be precise, not cheap). On a 3090 at 1280px expect roughly 8-12 fps; an hour of 24fps anime is ~15 min of inference per GPU.
  • License: the model weights are AGPL-3.0 (Fuyucchi); ultralytics is AGPL-3.0 too. Fine for personal/research dataset building; if this ever ships in a product, revisit.

QC: auditing an existing collection

audit_stills.py runs the same detector over a directory of stills and reports how many contain no face at all — e.g. measuring the damage the old person-proxy pipeline did:

python audit_stills.py -I ~/Documents/image-training-datasets/gabriel-dropout \
    --gpu 1 --write-report old_run_report.csv

Verified run (Gabriel Dropout, Oct 18 2026)

12 episodes + 2 specials, OP/ED skipped: 3,526 stills vs 887 from the old person-proxy run (~4x yield — chibi reaction faces no longer missed). QC: full box-size distribution audited (p5=0.7%, median 6.6%, max 29.9%); 5 largest and 5 smallest saves inspected with annotated copies — all real faces; zero faceless images found. The 5 largest sit against the 30% area cap and are legitimate extreme close-ups (crying faces count).

Verification workflow

Always run with --preview-dir and eyeball the _ann.png files: every saved frame should show a green box on a real face. Pay attention to saves clustered against a gate (e.g. many boxes at ~max area) — that pattern means the gate is doing work and deserves a look. For a known-dirty legacy collection, audit_stills.py quantifies the damage.

Recipe:

python anime_facesaver.py -I <show_dir> -O <out> --gpu <n> \
    --preview-dir <out>-preview
S
Description
Scene-gated anime still extractor with real anime face detection (fork of facesaver)
Readme
138 KiB
Languages
Python 100%