Files
scene-actor-extraction/src/nodes/debug_renderer_node.hpp
T
dtourolle d753062c6c 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
2026-06-12 15:29:01 +02:00

151 lines
5.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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