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:
@@ -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
|
||||
Reference in New Issue
Block a user