feat(quality): score every face on sharpness and alignment before it is evidence
Every embedding now carries the quality of the input it came from. Both axes fall out of the AR-005 warp for free: crop_sharpness() is the normalised Laplacian variance over the aligned 112x112, so contrast and size cannot leak into it, and the alignment residual is the part of the landmark deformation a similarity transform cannot explain, so in-plane roll reads as zero and foreshortening does not. Carried, not consumed. Nothing discounts or thresholds on either number yet -- that is AR-030 and VR-012, and the knee has to be located against recorded data before a gate is chosen. What this change buys is that the data exists to locate it with. No face is admitted unscored: the -1 sentinel is preserved rather than clamped, and a degenerate landmark fit is counted rather than silently dropped. Takes the VR-001 dump to schema_version 2. The bump is not for readers, which check for the datasets by name and replay a v1 dump unchanged; it is so a consumer can tell "never scored" from "scored zero", which is not recoverable from the arrays afterwards. TRACES: AR-028, AR-029, AR-030 | VR-001 | SR-002
This commit is contained in:
@@ -59,11 +59,36 @@ inline constexpr float kArcFaceRef[5][2] = {
|
||||
// Landmark order matches ArcFace convention (same as SCRFD output order):
|
||||
// [0] right-eye-centre [1] left-eye-centre [2] nose
|
||||
// [3] right-mouth [4] left-mouth
|
||||
/// TRACES: AR-028 | SR-002
|
||||
struct DetectedFace {
|
||||
cv::Rect2f bbox;
|
||||
std::array<cv::Point2f, 5> landmarks;
|
||||
float confidence{0.f};
|
||||
|
||||
// ── AR-028 quality vector ────────────────────────────────────────────────
|
||||
// Three axes, kept separate and never collapsed into one scalar: they fail
|
||||
// for different reasons, have different remedies, and do not earn the same
|
||||
// response. Carried, not consumed — the vector travels with the face into
|
||||
// the VR-001 dump so a threshold can be re-litigated against recorded data
|
||||
// rather than by re-running video.
|
||||
//
|
||||
// **Size is the third axis and is deliberately not a field here.** It is
|
||||
// `bbox`, which every consumer already has, scaled by the frame's
|
||||
// `bbox_upscale` to reach the original resolution AR-002 thresholds in.
|
||||
// Copying it into a second field would put the same quantity in two
|
||||
// coordinate spaces inside one struct — the trap SCHEMA.md records for
|
||||
// `bbox_upscale` — and the copy would be the one that drifts.
|
||||
//
|
||||
// Both fields below are -1 until the aligner runs, so *unscored* is
|
||||
// distinguishable from *scored badly*. Nothing downstream may read a
|
||||
// negative value as a quality.
|
||||
|
||||
// AR-029 sharpness: normalised Laplacian variance over the aligned crop,
|
||||
// dimensionless. Falls with motion blur and soft focus; invariant to
|
||||
// contrast, and taken on the fixed 112×112 canvas so it cannot re-measure
|
||||
// face size. See crop_sharpness() for the construction and its one hazard.
|
||||
float sharpness{-1.f};
|
||||
|
||||
// AR-030 visibility: RMS landmark misfit, in canonical 112×112 pixels, left
|
||||
// over after the best similarity fit to the ArcFace template. Rises with
|
||||
// out-of-plane pose and with occlusion; blind to in-plane roll and to face
|
||||
|
||||
Reference in New Issue
Block a user