#pragma once #ifdef SAE_DEBUG #include "types.hpp" #include "config.hpp" #include #include #include #include #include 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(b.x), static_cast(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(b.x) + 2, static_cast(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{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