#pragma once #include "face_utils.hpp" #include // ── 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 good_faces; std::vector 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)}; } };