Initial commit: scene-actor-extraction pipeline

Source (KPN++ pipeline nodes, ArcFace embedders, SCRFD/YuNet detectors,
gallery builder), build scripts, and eval artifacts.

- external/KPN as a git submodule (gitea.tourolle.paris/dtourolle/KPN)
- ONNX models tracked via Git LFS (models/*.onnx)
- generated outputs, TensorRT engines, reference repos, and media ignored
This commit is contained in:
2026-06-12 15:29:01 +02:00
commit d753062c6c
50 changed files with 10100 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
#pragma once
#ifdef SAE_DEBUG
#include "types.hpp"
#include "config.hpp"
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <filesystem>
#include <iostream>
#include <string>
namespace fs = std::filesystem;
// ── DebugRendererFunc ─────────────────────────────────────────────────────────
// KPN sink node (SAE_DEBUG only): saves one directory of debug images per frame.
//
// Output layout:
// debug_frames/
// t0001.000/
// annotated.jpg original frame + coloured bboxes + name overlays
// brad_pitt_0.82.jpg 112×112 aligned crop | 1.5× context crop (side-by-side)
// unknown_0_0.77.jpg same for unidentified faces
//
// The node taps MatchedSceneFrame (before scene tracking) so every raw
// detection — including unknowns — is captured here.
struct DebugRendererFunc {
static constexpr std::string_view label() { return "debug_renderer"; }
explicit DebugRendererFunc(const Config& cfg)
: cfg_(cfg)
{
fs::create_directories(cfg_.debug_dir);
std::cerr << "[debug_renderer] output dir: " << cfg_.debug_dir << "\n";
}
void operator()(MatchedSceneFrame mf) {
if (mf.source.eof || mf.source.image.empty()) return;
// Directory for this timestamp, e.g. "debug_frames/t0042.000/"
char buf[32];
std::snprintf(buf, sizeof(buf), "t%08.3f", mf.source.timestamp_sec);
fs::path dir = fs::path(cfg_.debug_dir) / buf;
fs::create_directories(dir);
// ── Annotated frame ───────────────────────────────────────────────────
cv::Mat annotated = mf.source.image.clone();
int unknown_idx = 0;
for (const auto& ia : mf.actors) {
bool known = (ia.actor_idx >= 0);
cv::Scalar colour = known
? cv::Scalar(0, 200, 60) // green for identified
: cv::Scalar(0, 100, 220); // orange for unknown
cv::Rect2f b = ia.bbox;
cv::rectangle(annotated, b, colour, 2);
std::string lbl = known
? (ia.name + " " + fmt_pct(ia.similarity))
: ("unknown " + fmt_pct(ia.similarity));
// Background strip for readability
int baseline = 0;
cv::Size ts = cv::getTextSize(lbl, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseline);
cv::Rect strip(static_cast<int>(b.x), static_cast<int>(b.y) - ts.height - 4,
ts.width + 4, ts.height + 6);
strip &= cv::Rect(0, 0, annotated.cols, annotated.rows);
if (strip.area() > 0)
cv::rectangle(annotated, strip, colour, cv::FILLED);
cv::putText(annotated, lbl,
cv::Point(static_cast<int>(b.x) + 2, static_cast<int>(b.y) - 2),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 255, 255), 1,
cv::LINE_AA);
// ── Per-face crop image ───────────────────────────────────────────
// Left panel: 112×112 aligned crop. Right panel: expanded context.
cv::Mat panel = make_face_panel(mf.source.image, ia);
std::string stem = known
? (sanitise(ia.name) + "_" + fmt_sim(ia.similarity))
: ("unknown_" + std::to_string(unknown_idx++) + "_" + fmt_sim(ia.similarity));
cv::imwrite((dir / (stem + ".jpg")).string(), panel,
{cv::IMWRITE_JPEG_QUALITY, 90});
}
cv::imwrite((dir / "annotated.jpg").string(), annotated,
{cv::IMWRITE_JPEG_QUALITY, 90});
}
private:
const Config& cfg_;
// Build a side-by-side panel: [112×112 aligned crop | context crop resized to 112×112]
cv::Mat make_face_panel(const cv::Mat& frame, const IdentifiedActor& ia) const {
// Left: aligned 112×112
cv::Mat left = ia.crop.empty()
? cv::Mat(112, 112, CV_8UC3, cv::Scalar(60, 60, 60))
: ia.crop.clone();
// Right: expanded bbox from original frame, resized to 112×112
cv::Rect2f expanded = expand_bbox(ia.bbox, cfg_.crop_context,
frame.cols, frame.rows);
cv::Mat right_raw = frame(expanded).clone();
cv::Mat right;
cv::resize(right_raw, right, {112, 112}, 0, 0, cv::INTER_LINEAR);
// Separator line
cv::Mat sep(112, 4, CV_8UC3, cv::Scalar(200, 200, 200));
cv::Mat panel;
cv::hconcat(std::vector<cv::Mat>{left, sep, right}, panel);
return panel;
}
static cv::Rect2f expand_bbox(cv::Rect2f b, float factor, int W, int H) {
float cx = b.x + b.width * 0.5f;
float cy = b.y + b.height * 0.5f;
float nw = b.width * factor;
float nh = b.height * factor;
float x = std::max(0.f, cx - nw * 0.5f);
float y = std::max(0.f, cy - nh * 0.5f);
nw = std::min(nw, (float)W - x);
nh = std::min(nh, (float)H - y);
return {x, y, nw, nh};
}
static std::string fmt_pct(float v) {
char buf[8];
std::snprintf(buf, sizeof(buf), "%.0f%%", v * 100.f);
return buf;
}
static std::string fmt_sim(float v) {
char buf[8];
std::snprintf(buf, sizeof(buf), "%.2f", v);
return buf;
}
static std::string sanitise(const std::string& s) {
std::string out;
out.reserve(s.size());
for (char c : s)
out += (std::isalnum(c) ? std::tolower(c) : '_');
return out;
}
};
#endif // SAE_DEBUG