diff --git a/configs/full_pipeline_1080p_test_alarm.json b/configs/full_pipeline_1080p_test_alarm.json index 9737f12..909a35e 100644 --- a/configs/full_pipeline_1080p_test_alarm.json +++ b/configs/full_pipeline_1080p_test_alarm.json @@ -103,6 +103,11 @@ "load_on_start": true, "expected_dim": 512, "dtype": "auto" + }, + "debug": { + "enabled": false, + "log_matches": false, + "min_log_interval_ms": 1000 } }, { diff --git a/include/face/face_recog_debug.h b/include/face/face_recog_debug.h new file mode 100644 index 0000000..fdc992c --- /dev/null +++ b/include/face/face_recog_debug.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "face/face_result.h" +#include "utils/simple_json.h" + +namespace rk3588 { + +struct FaceRecogDebugConfig { + bool enabled = false; + bool log_matches = false; + int min_log_interval_ms = 2000; +}; + +inline FaceRecogDebugConfig ParseFaceRecogDebugConfig( + const SimpleJson& cfg, const FaceRecogDebugConfig& base = {}) { + FaceRecogDebugConfig out = base; + const SimpleJson* debug = cfg.Find("debug"); + if (!debug || !debug->IsObject()) return out; + + out.enabled = debug->ValueOr("enabled", out.enabled); + out.log_matches = debug->ValueOr("log_matches", out.log_matches); + out.min_log_interval_ms = std::max(0, debug->ValueOr("min_log_interval_ms", out.min_log_interval_ms)); + return out; +} + +inline std::string BuildFaceRecogDebugSummaryLine( + const std::string& node_id, uint64_t frame_id, const FaceRecogItem& item) { + const float sim_margin = item.best_sim - item.second_sim; + const std::string candidate = item.candidate_name.empty() ? "n/a" : item.candidate_name; + const std::string status = item.unknown ? "unknown" : "known"; + + std::ostringstream oss; + oss << "[ai_face_recog] match" + << " id=" << node_id + << " frame=" << frame_id + << " status=" << status + << " candidate=" << candidate + << " candidate_id=" << item.candidate_person_id + << " best_sim=" << std::fixed << std::setprecision(2) << item.best_sim + << " second_sim=" << std::fixed << std::setprecision(2) << item.second_sim + << " sim_margin=" << std::fixed << std::setprecision(2) << sim_margin + << " bbox=(" << static_cast(std::lround(item.bbox.x)) + << "," << static_cast(std::lround(item.bbox.y)) + << "," << static_cast(std::lround(item.bbox.w)) + << "," << static_cast(std::lround(item.bbox.h)) + << ")"; + return oss.str(); +} + +} // namespace rk3588 diff --git a/plugins/ai_face_recog/ai_face_recog_node.cpp b/plugins/ai_face_recog/ai_face_recog_node.cpp index 53578a9..a45f73b 100644 --- a/plugins/ai_face_recog/ai_face_recog_node.cpp +++ b/plugins/ai_face_recog/ai_face_recog_node.cpp @@ -13,6 +13,7 @@ #include #include "hw/i_infer_backend.h" +#include "face/face_recog_debug.h" #include "face/face_result.h" #include "node.h" #include "utils/dma_alloc.h" @@ -570,6 +571,8 @@ struct FaceRecogConfigSnapshot { int gallery_expected_dim = 512; std::string gallery_dtype = "auto"; int gallery_reload_seq = 0; + + FaceRecogDebugConfig debug; }; static bool BuildFaceRecogConfigSnapshot(const SimpleJson& config, @@ -627,6 +630,7 @@ static bool BuildFaceRecogConfigSnapshot(const SimpleJson& config, snap->gallery_dtype = g->ValueOr("dtype", snap->gallery_dtype); snap->gallery_reload_seq = g->ValueOr("reload_seq", snap->gallery_reload_seq); } + snap->debug = ParseFaceRecogDebugConfig(config, snap->debug); for (auto& c : snap->gallery_backend) c = static_cast(std::tolower(static_cast(c))); for (auto& c : snap->gallery_dtype) c = static_cast(std::tolower(static_cast(c))); @@ -719,10 +723,16 @@ public: const bool align = cfg ? cfg->align : false; const float thr_accept = cfg ? cfg->thr_accept : 0.0f; const float thr_margin = cfg ? cfg->thr_margin : 0.0f; + const bool debug_enabled = cfg ? cfg->debug.enabled : false; + const bool debug_log_matches = cfg ? cfg->debug.log_matches : false; + const int debug_interval_ms = cfg ? cfg->debug.min_log_interval_ms : 0; LogInfo("[ai_face_recog] start id=" + id_ + " align=" + std::string(align ? "true" : "false") + " thr_accept=" + std::to_string(thr_accept) + " thr_margin=" + std::to_string(thr_margin) + - " infer_interval_ms=" + std::to_string(infer_interval_ms_)); + " infer_interval_ms=" + std::to_string(infer_interval_ms_) + + " debug=" + std::string(debug_enabled ? "true" : "false") + + " debug_log_matches=" + std::string(debug_log_matches ? "true" : "false") + + " debug_min_log_interval_ms=" + std::to_string(debug_interval_ms)); return true; } @@ -960,6 +970,7 @@ private: item.unknown = !accept; if (cfg->emit_embedding) item.embedding = emb; + MaybeLogDebugMatch(*cfg, frame->frame_id, frame->pts, item); rr.items.push_back(std::move(item)); } @@ -967,6 +978,24 @@ private: frame->face_recog = std::make_shared(std::move(rr)); } + + void MaybeLogDebugMatch(const FaceRecogConfigSnapshot& cfg, uint64_t frame_id, uint64_t pts_us, + const FaceRecogItem& item) { + if (!cfg.debug.enabled || !cfg.debug.log_matches) return; + + if (cfg.debug.min_log_interval_ms > 0) { + const int64_t now_key_ms = pts_us > 0 + ? static_cast(pts_us / 1000ULL) + : static_cast(frame_id); + if (now_key_ms > 0 && + (now_key_ms - last_debug_log_pts_ms_) < cfg.debug.min_log_interval_ms) { + return; + } + last_debug_log_pts_ms_ = now_key_ms; + } + + LogInfo(BuildFaceRecogDebugSummaryLine(id_, frame_id, item)); + } #endif std::string id_; @@ -989,6 +1018,7 @@ private: int64_t infer_interval_ms_ = 0; int64_t infer_phase_ms_ = 0; int64_t last_infer_pts_ms_ = 0; + int64_t last_debug_log_pts_ms_ = std::numeric_limits::min(); }; REGISTER_NODE(AiFaceRecogNode, "ai_face_recog"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 19d6998..53108e4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(rk3588_gtests test_event_fusion.cpp test_alarm_behavior_events.cpp test_log_action.cpp + test_face_recog_debug.cpp test_infer_backend.cpp test_image_processor.cpp test_codec_backend.cpp diff --git a/tests/test_face_recog_debug.cpp b/tests/test_face_recog_debug.cpp new file mode 100644 index 0000000..8d91d39 --- /dev/null +++ b/tests/test_face_recog_debug.cpp @@ -0,0 +1,57 @@ +#include + +#include + +#include "face/face_recog_debug.h" +#include "utils/simple_json.h" + +namespace rk3588 { +namespace { + +TEST(FaceRecogDebugTest, ParsesDebugConfigWithDefaultsAndOverrides) { + FaceRecogDebugConfig defaults; + EXPECT_FALSE(defaults.enabled); + EXPECT_FALSE(defaults.log_matches); + EXPECT_EQ(defaults.min_log_interval_ms, 2000); + + SimpleJson::Object debug_obj; + debug_obj.emplace("enabled", SimpleJson(true)); + debug_obj.emplace("log_matches", SimpleJson(true)); + debug_obj.emplace("min_log_interval_ms", SimpleJson(350.0)); + + SimpleJson::Object root_obj; + root_obj.emplace("debug", SimpleJson(std::move(debug_obj))); + SimpleJson cfg(std::move(root_obj)); + + FaceRecogDebugConfig parsed = ParseFaceRecogDebugConfig(cfg, defaults); + EXPECT_TRUE(parsed.enabled); + EXPECT_TRUE(parsed.log_matches); + EXPECT_EQ(parsed.min_log_interval_ms, 350); +} + +TEST(FaceRecogDebugTest, BuildsSummaryLineForUnknownCandidate) { + FaceRecogItem item; + item.bbox = Rect{10.0f, 20.0f, 30.0f, 40.0f}; + item.candidate_person_id = 7; + item.candidate_name = "alice"; + item.best_name = "unknown"; + item.best_sim = 0.44f; + item.second_sim = 0.41f; + item.unknown = true; + + const std::string line = BuildFaceRecogDebugSummaryLine("face_recog", 42, item); + + EXPECT_NE(line.find("[ai_face_recog] match"), std::string::npos); + EXPECT_NE(line.find("id=face_recog"), std::string::npos); + EXPECT_NE(line.find("frame=42"), std::string::npos); + EXPECT_NE(line.find("status=unknown"), std::string::npos); + EXPECT_NE(line.find("candidate=alice"), std::string::npos); + EXPECT_NE(line.find("candidate_id=7"), std::string::npos); + EXPECT_NE(line.find("best_sim=0.44"), std::string::npos); + EXPECT_NE(line.find("second_sim=0.41"), std::string::npos); + EXPECT_NE(line.find("sim_margin=0.03"), std::string::npos); + EXPECT_NE(line.find("bbox=(10,20,30,40)"), std::string::npos); +} + +} // namespace +} // namespace rk3588