// build_gallery — build an actor embedding gallery from a directory of images // // Gallery directory layout: // gallery_root/ // nm0000093_Brad_Pitt/ // img1.jpg // img2.jpg // nm0000129_Cate_Blanchett/ // ... // // Usage: // build_gallery --root --output [options] // // Options: // --detector SCRFD detector model (default: models/scrfd_500m_bnkps.onnx) // --arcface ArcFace model (default: models/arcface_w600k_r50.onnx) // --conf face detection confidence threshold (default: 0.5) // --nms NMS IoU threshold (default: 0.4) #include "gallery/gallery_builder.hpp" #include "gallery/gallery_report.hpp" #include "gallery/gallery_calibration.hpp" #include "gallery/gallery_store.hpp" #include "config.hpp" #include #include #include #include int main(int argc, char** argv) { std::string root_path, output_path; std::string detector_model = kDefaultDetectorModel; std::string arcface_model = kDefaultArcfaceModel; float conf = 0.5f, nms_thr = 0.4f; int max_side = 500; float min_face_px = 0.f; for (int i = 1; i < argc; ++i) { auto arg = [&](const char* f) { return std::strcmp(argv[i], f) == 0; }; auto next = [&]() -> std::string { if (++i >= argc) throw std::runtime_error(std::string("missing arg after ") + argv[i-1]); return argv[i]; }; try { if (arg("--root")) root_path = next(); else if (arg("--output")) output_path = next(); else if (arg("--detector")) detector_model = next(); else if (arg("--arcface")) arcface_model = next(); else if (arg("--conf")) conf = std::stof(next()); else if (arg("--nms")) nms_thr = std::stof(next()); else if (arg("--max-side")) max_side = std::stoi(next()); else if (arg("--min-face-px")) min_face_px = std::stof(next()); else { std::cerr << "[warn] unknown flag: " << argv[i] << "\n"; } } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << "\n"; return 1; } } if (root_path.empty() || output_path.empty()) { std::cerr << "Usage: build_gallery --root --output " "[--detector ] [--arcface ] [--max-side ]\n" " [--min-face-px ]\n"; return 1; } BuildConfig cfg; cfg.gallery_root = root_path; cfg.detector_model = detector_model; cfg.arcface_model = arcface_model; cfg.detector_conf = conf; cfg.detector_nms = nms_thr; cfg.max_side = max_side; cfg.min_face_px = min_face_px; try { ActorGallery gallery = build_gallery(cfg); if (gallery.actors.empty()) { std::cerr << "No actors built — check your gallery directory.\n"; return 1; } save_gallery(output_path, gallery); std::cerr << "Gallery saved to: " << output_path << "\n"; /// TRACES: GR-003 | SR-001 // Fit the calibration here and persist what it learned. The matcher // fits the same sigmoid at analysis time, but that is the wrong place // to audit a gallery from: by then the answer is per-run and nobody is // looking. Build time is when the gallery's quality is decided, and a // gallery can be quietly bad — heavily overlapping intra/inter // distributions, actors with no usable image — while looking fine. std::vector flat; std::vector flat_actor; for (int ai = 0; ai < static_cast(gallery.actors.size()); ++ai) for (const auto& e : gallery.actors[ai].embeddings) { flat.push_back(e); flat_actor.push_back(ai); } GalleryCalibrationStats stats; GalleryCalibration cal = calibrate_gallery(flat, flat_actor, &stats); const GalleryReport report = build_gallery_report(gallery, cal, stats, nullptr, output_path); const std::string report_path = gallery_report_path(output_path); save_gallery_report(report_path, report); std::cerr << "Gallery report saved to: " << report_path << "\n"; } catch (const std::exception& e) { std::cerr << "Fatal: " << e.what() << "\n"; return 1; } return 0; }