Measure the faces already found rather than finding them again
Benchmarks / CPU and I/O (per commit) (push) Successful in 3m13s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Failing after 54s
Build and test / Layer separation (push) Failing after 1s
🐳 Android image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 2s
Traceability / Requirement traces (push) Successful in 52s
Build and test / Android (aarch64) (push) Successful in 30m6s

Every face stored before its quality was kept holds a unit vector, and
V14 forgot the run marker of each image holding one so that the next
sweep would look again. Looking again meant detecting again: a whole
re-detection per image, with every suggestion on it thrown away and the
confirmations carried across by box overlap, to recover one number.

The sweep now has a measuring pass between the proxy repair and the
un-indexed images. It lists every image holding an unmeasured face,
fetches the original once, warps each stored face from the landmarks it
already has, embeds it, and writes the raw vector and its length over
the old row. Ids, boxes and identities are untouched; the marker is
re-written fresh so the sync exports the measured vectors. A face whose
landmarks no longer make a warp is dropped, as detection would have
refused to store it. `faces_unindexed` leaves those images to the
measuring pass, so the V14 deletion no longer costs a second detection.
This commit is contained in:
2026-09-11 21:50:12 +02:00
parent 8b3abdb787
commit 16f3fb41a3
7 changed files with 578 additions and 48 deletions
+140
View File
@@ -442,6 +442,64 @@ pub fn index_native(
Ok(out)
}
/// What re-embedding the faces already on one image produced.
#[derive(Debug, Default, PartialEq)]
pub struct Measured {
pub measured: Vec<faces::Measurement>,
/// Faces whose stored landmarks no longer make a warp. See
/// `dr_catalog::faces::record_measurements` for what becomes of them.
pub dropped: Vec<faces::FaceId>,
}
/// TRACES: FR-CULL-8 | FR-CULL-9
/// Embed the faces already found on one image again, from its native render.
///
/// The measuring half of the sweep, for faces stored before their quality was
/// kept (schema V14). No detector: the boxes and landmarks in the catalog are
/// taken as read, scaled back from the long edge they were normalised to, and
/// each face is warped out of the native frame and embedded exactly as
/// [`index_native`] would have done on the day. What comes back is the raw
/// vector and its length, to be written over the old unit one.
///
/// The size and sharpness gates are deliberately not re-applied. They decide
/// whether a face is worth *storing*, and these are stored; what is being
/// established now is how much the model can make of each, which is the
/// quality itself, and a face that would have failed a gate is precisely one
/// that should come out short and stop vouching for anyone.
pub fn measure_native(
embedder: &mut Embedder,
rgba: &[u8],
width: usize,
height: usize,
faces: &[faces::Face],
) -> Result<Measured, dr_face::FaceError> {
let mut out = Measured::default();
if !index_native_shape_ok(rgba, width, height) {
return Ok(out);
}
let long_edge = width.max(height) as f32;
let native = dr_face::Pixels::Rgba8(rgba);
for f in faces {
let mut landmarks = [(0.0_f32, 0.0_f32); 5];
for (o, &(x, y)) in landmarks.iter_mut().zip(f.landmarks.iter()) {
*o = (x * long_edge, y * long_edge);
}
let Some(aligned) = dr_face::warp_pixels(native, width, height, &landmarks) else {
log::debug!("face {:?} has degenerate landmarks, dropped", f.id);
out.dropped.push(f.id);
continue;
};
let embedded = embedder.embed(&aligned)?;
out.measured.push(faces::Measurement {
face: f.id,
embedding: embedded.to_f16_bytes(),
quality: embedded.quality,
});
}
Ok(out)
}
/// Whether a buffer is the native frame it claims to be.
///
/// Checked before anything expensive, and before the detector above all: a
@@ -1388,6 +1446,88 @@ mod tests {
assert_eq!(out[4], (360.0, 200.0));
}
/// The measuring path end to end, with the real embedder where one is on
/// this machine: landmarks come back from the long edge they were
/// normalised to, the warp is built from them, and what is stored is the
/// raw vector, whose length is the quality reported beside it.
#[test]
fn measuring_stores_the_raw_vector_at_the_quality_it_reports() {
let dir = crate::library::shared_face_models_dir();
let path = dir.join("arcface_mbf_b1.onnx");
if !path.is_file() {
eprintln!("no embedder at {}, skipping", path.display());
return;
}
let model = ModelId::new("w600k_mbf");
let mut embedder = Embedder::from_path(&path, model.clone()).expect("load embedder");
// A 600×400 frame with a gradient in it, and one face whose landmarks
// are the ArcFace template scaled up and placed in the middle -- not
// a face, but an unambiguous warp.
let (w, h) = (600usize, 400usize);
let mut rgba = vec![0u8; w * h * 4];
for y in 0..h {
for x in 0..w {
let i = (y * w + x) * 4;
rgba[i] = (x * 255 / w) as u8;
rgba[i + 1] = (y * 255 / h) as u8;
rgba[i + 2] = ((x + y) % 256) as u8;
rgba[i + 3] = 255;
}
}
let long_edge = w.max(h) as f32;
let mut landmarks = [(0.0_f32, 0.0_f32); 5];
for (o, &(tx, ty)) in landmarks.iter_mut().zip(dr_face::ARCFACE_TEMPLATE.iter()) {
// Two and a half times the template, offset into the frame, then
// normalised to the long edge as the catalog stores it.
*o = (
(tx * 2.5 + 150.0) / long_edge,
(ty * 2.5 + 60.0) / long_edge,
);
}
let stored = faces::Face {
id: faces::FaceId(7),
image_id: ImageId(1),
x: 0.25,
y: 0.15,
w: 0.5,
h: 0.7,
landmarks,
confidence: 0.9,
crop_px: 280.0,
quality: None,
model_id: "w600k_mbf".into(),
person: None,
probability: 0.0,
confirmed: false,
};
let stored_copy = stored.clone();
let out = measure_native(&mut embedder, &rgba, w, h, &[stored]).expect("measure");
assert!(out.dropped.is_empty());
assert_eq!(out.measured.len(), 1);
let m = &out.measured[0];
assert_eq!(m.face, faces::FaceId(7));
assert!(m.quality > 0.0);
let (_, length) = dr_face::read_f16_bytes(model, &m.embedding).expect("decode");
assert!(
(length - m.quality).abs() < 0.05 * m.quality,
"stored length {length} against reported quality {}",
m.quality
);
// Degenerate landmarks -- five points on one spot, which no
// similarity can be fitted to -- are dropped, not embedded.
let junk = faces::Face {
id: faces::FaceId(8),
landmarks: [(0.3, 0.3); 5],
..stored_copy
};
let out = measure_native(&mut embedder, &rgba, w, h, &[junk]).expect("measure");
assert!(out.measured.is_empty());
assert_eq!(out.dropped, vec![faces::FaceId(8)]);
}
#[test]
fn a_buffer_that_is_not_the_stated_size_indexes_nothing() {
// No model is loaded here, so reaching the detector would panic. The
+225 -10
View File
@@ -3297,6 +3297,14 @@ fn flush_sweep(catalog: &Catalog, found: &mut Vec<MetadataFound>) {
/// so an image with no proxy is work to be done rather than work to be skipped
/// — which is the whole difference between indexing a library and indexing the
/// fraction of it that has been browsed.
///
/// **An image whose faces are merely unmeasured is not here.** Schema V14
/// forgot the run marker of every such image so that *something* would look
/// at it again; [`faces_unmeasured`] is that something, and it does the cheap
/// thing — embed the faces already found — where this list would have the
/// whole detection run again. Both lists are drawn from the same catalog in
/// the same sweep, so the exclusion is in the query rather than left to the
/// caller to remember.
fn faces_unindexed(
catalog: &Catalog,
model_id: &str,
@@ -3310,6 +3318,10 @@ fn faces_unindexed(
SELECT 1 FROM face_index fi
WHERE fi.image_id = i.id AND fi.model_id = ?1
)
AND NOT EXISTS (
SELECT 1 FROM faces f
WHERE f.image_id = i.id AND f.model_id = ?1 AND f.quality IS NULL
)
ORDER BY i.id"
))?;
let rows = stmt
@@ -3382,6 +3394,60 @@ fn faces_without_proxy(
Ok(rows)
}
/// TRACES: FR-CULL-9
/// Images holding a face this model found before its quality was kept.
///
/// The work list of the sweep's measuring pass: every stored face whose vector
/// is a unit one (schema V14) is embedded again from the native render, with
/// the landmarks it already has, and the raw vector and its length written
/// over it. Nothing is re-detected and no face changes identity — see
/// `dr_catalog::faces::record_measurements`.
///
/// Costs what the indexing pass costs per image, an original fetched and
/// rendered, because the length exists only at the moment of embedding and
/// there is no embedding without the pixels. What it saves is the detector,
/// and — the part that matters — every suggestion and confirmation on those
/// faces, which a re-detection would rebuild from box overlap.
fn faces_unmeasured(
catalog: &Catalog,
model_id: &str,
) -> Result<Vec<ThumbnailRequest>, dr_catalog::CatalogError> {
let mut stmt = catalog.connection().prepare(&format!(
"SELECT DISTINCT i.id, i.source_ref, r.file_id, i.file_size
FROM images i
JOIN remote r ON r.image_id = i.id
JOIN faces f ON f.image_id = i.id
WHERE r.file_id IS NOT NULL AND {VISIBLE}
AND f.model_id = ?1 AND f.quality IS NULL
ORDER BY i.id"
))?;
let rows = stmt
.query_map([model_id], |r| {
Ok(ThumbnailRequest {
full_resolution: true,
thumb_size: dr_thumbs::ThumbSize::Large,
row: 0,
image_id: r.get(0)?,
path: r.get(1)?,
file_id: r.get::<_, Option<i64>>(2)?.map(|v| v as u64),
size: r.get::<_, Option<i64>>(3)?.unwrap_or(0) as u64,
needs_metadata: false,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(rows)
}
/// What the sweep does with one fetched original.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SweepWork {
/// Detect and embed from scratch: an image never indexed, or one whose
/// faces have nothing left to be cut from.
Detect,
/// Embed the faces already found, again — see [`faces_unmeasured`].
Measure,
}
/// TRACES: FR-CULL-8 | FR-EXP-9 | NFR-ARCH-2 | NFR-RES-2
/// Index faces across the **whole** library, at native resolution.
///
@@ -3502,7 +3568,9 @@ pub fn spawn_face_sweep(
// un-indexed ones. Appended instead, they sit two hours of fetching
// down the queue and the screen stays empty for the whole session —
// which is indistinguishable from the repair not existing.
let mut wanted = match faces_without_proxy(&catalog, &store, &model_id) {
let mut wanted: Vec<(ThumbnailRequest, SweepWork)> = match faces_without_proxy(
&catalog, &store, &model_id,
) {
Ok(repair) => {
if !repair.is_empty() {
log::info!(
@@ -3510,7 +3578,7 @@ pub fn spawn_face_sweep(
repair.len()
);
}
repair
repair.into_iter().map(|r| (r, SweepWork::Detect)).collect()
}
Err(e) => {
log::warn!("face sweep: looking for orphaned faces: {e}");
@@ -3518,10 +3586,35 @@ pub fn spawn_face_sweep(
}
};
// Disjoint from the above by construction: an image with faces recorded
// is not an image with no `face_index` row.
// Then the faces to measure again. Behind the proxy repair and ahead
// of the rest for the same reason that one leads: these are faces the
// People screen is showing and grouping *now*, on vectors the gallery
// rule cannot act on. An image already queued for a full re-detection
// gets its quality from that, so it is not queued twice.
let mut queued: std::collections::HashSet<i64> =
wanted.iter().map(|(r, _)| r.image_id).collect();
match faces_unmeasured(&catalog, &model_id) {
Ok(measure) => {
let fresh: Vec<_> = measure
.into_iter()
.filter(|r| queued.insert(r.image_id))
.collect();
if !fresh.is_empty() {
log::info!(
"face sweep: measuring the faces on {} image(s) stored before their quality was kept",
fresh.len()
);
}
wanted.extend(fresh.into_iter().map(|r| (r, SweepWork::Measure)));
}
Err(e) => log::warn!("face sweep: looking for unmeasured faces: {e}"),
}
// Disjoint from both of the above by construction: an image with faces
// recorded is not an image with no `face_index` row, and one whose
// faces are unmeasured is excluded by the query.
match faces_unindexed(&catalog, &model_id) {
Ok(fresh) => wanted.extend(fresh),
Ok(fresh) => wanted.extend(fresh.into_iter().map(|r| (r, SweepWork::Detect))),
Err(e) => {
log::warn!("face sweep: {e}");
if wanted.is_empty() {
@@ -3587,7 +3680,7 @@ pub fn spawn_face_sweep(
// multiply the one allocation that actually threatens the budget
// while buying no parallelism that exists.
for chunk in wanted.chunks(SWEEP_LANES) {
let fetched = futures_join_all(chunk.iter().map(|req| {
let fetched = futures_join_all(chunk.iter().map(|(req, work)| {
let backend = &*backend;
let pool = &pool;
async move {
@@ -3595,11 +3688,11 @@ pub fn spawn_face_sweep(
Ok(h) => h,
Err(e) if e.indicates_offline() => {
log::info!("face sweep: {e}");
return (req, Err(FetchOutcome::Offline));
return (req, *work, Err(FetchOutcome::Offline));
}
Err(e) => {
log::debug!("face sweep: {}: {e}", req.path);
return (req, Err(FetchOutcome::Failed));
return (req, *work, Err(FetchOutcome::Failed));
}
};
@@ -3622,13 +3715,13 @@ pub fn spawn_face_sweep(
}
};
drop(held);
(req, got)
(req, *work, got)
}
}))
.await;
let mut lane_failed = 0usize;
for (req, got) in fetched {
for (req, work, got) in fetched {
let bytes = match got {
Ok(b) => b,
Err(FetchOutcome::Offline) => {
@@ -3641,6 +3734,36 @@ pub fn spawn_face_sweep(
}
};
if work == SweepWork::Measure {
let image = dr_types::ImageId(req.image_id as u64);
match measure_one_native(
&gpu,
&mut embedder,
&catalog,
image,
&model_id,
&bytes,
) {
Ok(n) => {
images += 1;
found += n;
if tx
.send(FaceSweepMessage::Indexed { image, faces: n })
.is_err()
{
log::info!("face sweep: cancelled after {images} image(s)");
pool.release_all(&*backend).await;
return;
}
}
Err(e) => {
log::debug!("face sweep: measuring {}: {e}", req.path);
lane_failed += 1;
}
}
continue;
}
match index_one_native(&gpu, &mut detector, &mut embedder, &bytes, &options) {
Ok((faces, edge, proxy)) => {
// Before the detections, so a kill between the two
@@ -3855,6 +3978,51 @@ fn index_one_native(
Ok((faces, edge, proxy))
}
/// TRACES: FR-CULL-8 | FR-CULL-9
/// Render one original at native resolution and embed its stored faces again.
///
/// [`index_one_native`]'s sibling for the measuring pass: the same render, no
/// detection, and the result written *over* the faces rather than in place of
/// them. Returns how many faces were measured.
///
/// The proxy is left alone. The faces here were found on a pass that stored
/// one, or [`faces_without_proxy`] would have claimed the image first.
fn measure_one_native(
gpu: &dr_gpu::GpuContext,
embedder: &mut dr_face::Embedder,
catalog: &Catalog,
image: dr_types::ImageId,
model_id: &str,
bytes: &[u8],
) -> Result<usize, String> {
let faces = dr_catalog::faces::unmeasured_on_image(catalog.connection(), image, model_id)
.map_err(|e| e.to_string())?;
if faces.is_empty() {
return Ok(0);
}
let frame = render_native(gpu, bytes)?;
let edge = frame.width.max(frame.height);
let measured = crate::faces::measure_native(
embedder,
&frame.rgba,
frame.width as usize,
frame.height as usize,
&faces,
)
.map_err(|e| e.to_string())?;
let n = measured.measured.len();
dr_catalog::faces::record_measurements(
catalog.connection(),
image,
model_id,
edge,
&measured.measured,
&measured.dropped,
)
.map_err(|e| e.to_string())?;
Ok(n)
}
/// The class the whole-library pass fills.
///
/// Grid only, deliberately. The large class is four times the transfer for a
@@ -6055,6 +6223,53 @@ mod tests {
assert_eq!(faces_unindexed(&catalog, "other").unwrap().len(), 3);
}
/// The state schema V14 leaves: a face with no quality and an image with
/// no marker. It is the measuring pass's work, and *only* that pass's — a
/// full re-detection of the same image would throw away every suggestion
/// on it for nothing.
#[test]
fn an_unmeasured_face_is_measured_rather_than_re_detected() {
let catalog = with_images(3);
let ids = image_ids(&catalog);
let stored = |quality: Option<f32>| dr_catalog::faces::DetectedFace {
x: 0.1,
y: 0.1,
w: 0.2,
h: 0.2,
landmarks: [(0.0, 0.0); 5],
confidence: 0.9,
embedding: vec![0u8; 1024],
crop_px: 120.0,
quality,
crop: Vec::new(),
model_id: "w600k_mbf".into(),
};
let conn = catalog.connection();
dr_catalog::faces::record_detections(conn, ids[0], "w600k_mbf", 4000, &[stored(None)])
.unwrap();
dr_catalog::faces::record_detections(
conn,
ids[1],
"w600k_mbf",
4000,
&[stored(Some(18.0))],
)
.unwrap();
// What V14 does to the first: the marker goes, the face stays.
dr_catalog::faces::clear_index_marker(conn, ids[0], "w600k_mbf").unwrap();
let measure = faces_unmeasured(&catalog, "w600k_mbf").unwrap();
assert_eq!(measure.len(), 1);
assert_eq!(measure[0].image_id, ids[0].0 as i64);
assert!(measure[0].full_resolution);
// Not re-detected, marker or no marker; the third image, never seen,
// still is.
let detect = faces_unindexed(&catalog, "w600k_mbf").unwrap();
assert_eq!(detect.len(), 1);
assert_eq!(detect[0].image_id, ids[2].0 as i64);
}
#[test]
fn a_scoped_grid_shows_only_that_collections_images() {
use dr_catalog::collections::{self as coll, CollectionKind};