Four test files and one node header carried no TRACES tag, so the requirements they verify read as implemented-but-unverified. Tagging a test is what distinguishes the two. test_calibration.cpp is AR-023; its three [report] cases verify GR-003 and are tagged separately, since the report is fitted from the same distributions but is its own requirement. test_similarity.cpp is the CI half of AR-026 — equivalence against hand-computed dot products, where throughput at scale is AR-027 and cannot run on this host. test_face_tracker.cpp is AR-007 and AR-008. Two headers described code that no longer exists. face_aligner_node.hpp still documented the RANSAC fit AR-005 replaced with an Umeyama least-squares fit over all five points — not merely out of date but the opposite of what the file does, and it reads as a rationale for discarding the landmarks AR-030 measures. test_face_tracker.cpp still described the park/revive branch AR-008 deleted, and the raw-cosine cut_revive_sim that guarded it, which AR-024 retired. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> TRACES: AR-005, AR-007, AR-008, AR-023, AR-026, AR-030 | GR-003 | SR-001, SR-002
48 lines
1.9 KiB
C++
48 lines
1.9 KiB
C++
#pragma once
|
||
#include "face_utils.hpp"
|
||
|
||
#include <iostream>
|
||
|
||
// ── FaceAlignerFunc ───────────────────────────────────────────────────────────
|
||
/// TRACES: AR-005, AR-030 | SR-002
|
||
///
|
||
// KPN node: applies a 5-point similarity transform to each detected face,
|
||
// producing a 112×112 BGR crop suitable for ArcFace inference.
|
||
//
|
||
// Alignment is an Umeyama least-squares fit over all five landmarks (AR-005),
|
||
// not a robust one: a RANSAC fit discards the very landmarks AR-030 reads.
|
||
// Degenerate detections (where the fit fails) are dropped from the output
|
||
// vectors. The fit's residual is the AR-030 visibility measure and comes free,
|
||
// since the warp needs the transform anyway.
|
||
|
||
struct FaceAlignerFunc {
|
||
static constexpr std::string_view label() { return "face_aligner"; }
|
||
|
||
AlignedSceneFrame operator()(SceneFrame sf) {
|
||
if (sf.source.eof || sf.faces.empty())
|
||
return {std::move(sf.source), {}, {}};
|
||
|
||
std::vector<DetectedFace> good_faces;
|
||
std::vector<cv::Mat> crops;
|
||
good_faces.reserve(sf.faces.size());
|
||
crops.reserve(sf.faces.size());
|
||
|
||
for (auto& face : sf.faces) {
|
||
// The AR-030 misfit comes from the transform the warp already needs,
|
||
// so visibility costs no extra fit.
|
||
float residual = -1.f;
|
||
cv::Mat crop = align_face(sf.source.image, face.landmarks, &residual);
|
||
if (crop.empty()) {
|
||
std::cerr << "[face_aligner] degenerate detection skipped\n";
|
||
continue;
|
||
}
|
||
face.alignment_residual = residual;
|
||
good_faces.push_back(face);
|
||
crops.push_back(std::move(crop));
|
||
}
|
||
|
||
return {std::move(sf.source), std::move(good_faces), std::move(crops)};
|
||
}
|
||
|
||
};
|