An "Eyes open" chip beside the people chips, offered only while someone is chosen and dropped when the last person goes, so no term narrows the grid with nothing on the bar to say so. It compiles the rule in dr_face::eyes into the person's face subquery — Anna, eyes open, whoever else is blinking beside her — and drops a frame only on a closed eye that could be read: sunglasses, eyes too small or soft to read, and faces never read all pass, so an old library shows everything under the chip until the measuring pass has run. A test drives the same readings through the SQL and through the rule and requires them to agree. The People screen badges a face "Eyes closed", "Sunglasses" or "Eyes unclear" so the reason a frame is or is not in the grid can be read off the face; the sweep loads the three models when they are beside the pair and reads eyes on the indexing and measuring passes from the native render; the coverage line counts unread faces as work to measure so an already-indexed library keeps its Index button. The term travels with the place.
168 lines
5.6 KiB
Rust
168 lines
5.6 KiB
Rust
//! TRACES: FR-CULL-8 | FR-EXP-9
|
|
//! Measure what indexing at native resolution is actually worth.
|
|
//!
|
|
//! cargo run -p dr-ui --example face_native -- DET.onnx EMB.onnx [--export DIR] FILE [FILE…]
|
|
//!
|
|
//! Renders each file once at native resolution, then indexes it twice: the way
|
|
//! FR-CULL-8 now specifies, and the way it used to be done — everything, both
|
|
//! stages, from a 1024px proxy. Prints the faces found and the `crop_px` each
|
|
//! run gave the embedder.
|
|
//!
|
|
//! It exists because the case for the change was made from `crop_px` readings
|
|
//! taken out of a catalog after the fact. That is evidence about what happened;
|
|
//! this is evidence about what the new code does, on the same photographs, with
|
|
//! nothing between the two runs but the resolution.
|
|
//!
|
|
//! `--export DIR` also writes each native render out as a JPEG, so the
|
|
//! model-free tooling in `dr-face`'s examples — `eyes` above all — can be
|
|
//! run over native pixels rather than proxies (docs/faces.md §17.4).
|
|
//!
|
|
//! The models must have had their input dims frozen first; see
|
|
//! `tools/fix-face-model-shapes.sh`.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
const MODEL_ID: &str = "w600k_mbf";
|
|
|
|
fn main() {
|
|
env_logger::init();
|
|
|
|
let mut args: Vec<String> = std::env::args().skip(1).collect();
|
|
let export = args.iter().position(|a| a == "--export").map(|i| {
|
|
args.remove(i);
|
|
PathBuf::from(args.remove(i))
|
|
});
|
|
if args.len() < 3 {
|
|
eprintln!("usage: face_native DETECTOR.onnx EMBEDDER.onnx [--export DIR] FILE [FILE…]");
|
|
std::process::exit(2);
|
|
}
|
|
if let Some(d) = &export {
|
|
std::fs::create_dir_all(d).expect("export dir");
|
|
}
|
|
let (detector_model, embedder_model) = (PathBuf::from(&args[0]), PathBuf::from(&args[1]));
|
|
|
|
let Some(gpu) = pollster::block_on(dr_gpu::GpuContext::new_headless()).ok() else {
|
|
eprintln!("no GPU adapter; a native render needs one");
|
|
std::process::exit(1);
|
|
};
|
|
|
|
let mut detector = match dr_face::Detector::from_path(&detector_model) {
|
|
Ok(d) => d,
|
|
Err(e) => {
|
|
eprintln!("detector: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
let mut embedder =
|
|
match dr_face::Embedder::from_path(&embedder_model, dr_face::ModelId::new(MODEL_ID)) {
|
|
Ok(e) => e,
|
|
Err(e) => {
|
|
eprintln!("embedder: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
let options = dr_face::DetectOptions::default();
|
|
|
|
println!(
|
|
"{:<28} {:>11} {:>17} {:>17}",
|
|
"file", "native", "native faces/px", "1024 faces/px"
|
|
);
|
|
|
|
let (mut n_native, mut n_proxy) = (0usize, 0usize);
|
|
let (mut px_native, mut px_proxy) = (0.0f32, 0.0f32);
|
|
|
|
for path in &args[2..] {
|
|
let bytes = match std::fs::read(path) {
|
|
Ok(b) => b,
|
|
Err(e) => {
|
|
println!("{path}: cannot read: {e}");
|
|
continue;
|
|
}
|
|
};
|
|
let frame = match dr_ui::render_native(&gpu, &bytes) {
|
|
Ok(f) => f,
|
|
Err(e) => {
|
|
println!("{path}: cannot render: {e}");
|
|
continue;
|
|
}
|
|
};
|
|
let (w, h) = (frame.width as usize, frame.height as usize);
|
|
if let Some(dir) = &export {
|
|
let stem = std::path::Path::new(path)
|
|
.file_stem()
|
|
.map(|s| s.to_string_lossy().into_owned())
|
|
.unwrap_or_default();
|
|
match dr_thumbs::codec::encode_rgba(frame.width, frame.height, &frame.rgba) {
|
|
Ok(bytes) => std::fs::write(dir.join(format!("{stem}.jpg")), bytes).expect("write"),
|
|
Err(e) => println!("{path}: export: {e}"),
|
|
}
|
|
}
|
|
|
|
let native = dr_ui::faces::index_native(
|
|
&mut detector,
|
|
&mut embedder,
|
|
None,
|
|
&frame.rgba,
|
|
w,
|
|
h,
|
|
&options,
|
|
)
|
|
.unwrap_or_default();
|
|
|
|
// The old path, reproduced exactly: one buffer at 1024, used for both
|
|
// detection and the crop.
|
|
let mut small = dr_decode::Preview {
|
|
width: frame.width,
|
|
height: frame.height,
|
|
rgba: frame.rgba.clone(),
|
|
};
|
|
small.downscale_to(dr_thumbs::ThumbSize::Large.edge());
|
|
let proxy =
|
|
dr_ui::faces::index_preview(&mut detector, &mut embedder, None, &small, &options)
|
|
.map(|(f, _)| f)
|
|
.unwrap_or_default();
|
|
|
|
let mean = |v: &[dr_catalog::faces::DetectedFace]| {
|
|
if v.is_empty() {
|
|
0.0
|
|
} else {
|
|
v.iter().map(|f| f.crop_px).sum::<f32>() / v.len() as f32
|
|
}
|
|
};
|
|
let name = std::path::Path::new(path)
|
|
.file_name()
|
|
.map(|s| s.to_string_lossy().into_owned())
|
|
.unwrap_or_else(|| path.clone());
|
|
|
|
println!(
|
|
"{:<28} {:>11} {:>10} /{:>5.0} {:>10} /{:>5.0}",
|
|
name,
|
|
format!("{}x{}", frame.width, frame.height),
|
|
native.len(),
|
|
mean(&native),
|
|
proxy.len(),
|
|
mean(&proxy),
|
|
);
|
|
|
|
n_native += native.len();
|
|
n_proxy += proxy.len();
|
|
px_native += native.iter().map(|f| f.crop_px).sum::<f32>();
|
|
px_proxy += proxy.iter().map(|f| f.crop_px).sum::<f32>();
|
|
}
|
|
|
|
println!(
|
|
"\ntotal: native {n_native} face(s), mean crop {:.0}px | \
|
|
1024 proxy {n_proxy} face(s), mean crop {:.0}px",
|
|
if n_native == 0 {
|
|
0.0
|
|
} else {
|
|
px_native / n_native as f32
|
|
},
|
|
if n_proxy == 0 {
|
|
0.0
|
|
} else {
|
|
px_proxy / n_proxy as f32
|
|
},
|
|
);
|
|
}
|