diff --git a/include/face/face_result.h b/include/face/face_result.h index a7a41ed..b6ef4f5 100644 --- a/include/face/face_result.h +++ b/include/face/face_result.h @@ -37,6 +37,8 @@ struct FaceRecogItem { float best_sim = 0.0f; bool unknown = true; float second_sim = 0.0f; + int candidate_person_id = -1; + std::string candidate_name; bool has_landmarks = false; std::array landmarks{}; diff --git a/plugins/ai_face_recog/ai_face_recog_node.cpp b/plugins/ai_face_recog/ai_face_recog_node.cpp index 72640c6..53578a9 100644 --- a/plugins/ai_face_recog/ai_face_recog_node.cpp +++ b/plugins/ai_face_recog/ai_face_recog_node.cpp @@ -951,6 +951,8 @@ private: item.has_landmarks = face.has_landmarks; item.landmarks = face.landmarks; + item.candidate_person_id = sr.best_person_id; + item.candidate_name = sr.best_name; item.best_person_id = accept ? sr.best_person_id : -1; item.best_name = accept ? sr.best_name : "unknown"; item.best_sim = sr.best_sim; diff --git a/plugins/alarm/actions/action_base.h b/plugins/alarm/actions/action_base.h index 9c22769..420ec05 100644 --- a/plugins/alarm/actions/action_base.h +++ b/plugins/alarm/actions/action_base.h @@ -9,12 +9,21 @@ namespace rk3588 { +struct FaceAlarmMatch { + bool unknown = true; + int candidate_person_id = -1; + std::string candidate_name; + float best_sim = 0.0f; + float second_sim = 0.0f; +}; + struct AlarmEvent { std::string rule_name; std::string node_id; uint64_t timestamp_ms; uint64_t frame_id; std::vector detections; + std::vector face_matches; std::vector behavior_events; std::string snapshot_url; std::string clip_url; diff --git a/plugins/alarm/actions/log_action.cpp b/plugins/alarm/actions/log_action.cpp index 7500952..31b9947 100644 --- a/plugins/alarm/actions/log_action.cpp +++ b/plugins/alarm/actions/log_action.cpp @@ -70,6 +70,24 @@ void LogAction::Execute(AlarmEvent& event, std::shared_ptr /*frame*/) { oss << "]"; } + if (!event.face_matches.empty()) { + oss << " face_matches=["; + for (size_t i = 0; i < event.face_matches.size(); ++i) { + const auto& match = event.face_matches[i]; + if (i > 0) oss << ", "; + const float sim_margin = match.best_sim - match.second_sim; + const std::string candidate = match.candidate_name.empty() ? "n/a" : match.candidate_name; + oss << "{status=" << (match.unknown ? "unknown" : "known") + << " candidate=" << candidate + << " candidate_id=" << match.candidate_person_id + << " best_sim=" << std::fixed << std::setprecision(2) << match.best_sim + << " second_sim=" << std::fixed << std::setprecision(2) << match.second_sim + << " sim_margin=" << std::fixed << std::setprecision(2) << sim_margin + << "}"; + } + oss << "]"; + } + if (!event.snapshot_url.empty()) { oss << " snapshot=" << event.snapshot_url; } diff --git a/plugins/alarm/alarm_node.cpp b/plugins/alarm/alarm_node.cpp index 83c1ad9..d17f3e3 100644 --- a/plugins/alarm/alarm_node.cpp +++ b/plugins/alarm/alarm_node.cpp @@ -519,7 +519,9 @@ private: bool matched = false; std::string matched_name; std::vector dets; + std::vector face_matches; dets.reserve(3); + face_matches.reserve(3); for (const auto& it : frame->face_recog->items) { if (rule.min_face_area_ratio > 0.0f && img_area > 0.0) { @@ -576,6 +578,13 @@ private: d.bbox = it.bbox; d.track_id = it.best_person_id; dets.push_back(std::move(d)); + FaceAlarmMatch fm; + fm.unknown = it.unknown; + fm.candidate_person_id = it.candidate_person_id; + fm.candidate_name = it.candidate_name; + fm.best_sim = it.best_sim; + fm.second_sim = it.second_sim; + face_matches.push_back(std::move(fm)); if (dets.size() >= 3) break; } @@ -604,7 +613,7 @@ private: fake.rule_name = rule.name + ":" + matched_name; } fake.matched_detections = std::move(dets); - TriggerAlarm(fake, frame); + TriggerAlarm(fake, frame, std::move(face_matches)); } } @@ -654,7 +663,7 @@ private: } } - void TriggerAlarm(const RuleMatchResult& result, FramePtr frame) { + void TriggerAlarm(const RuleMatchResult& result, FramePtr frame, std::vector face_matches = {}) { ++alarm_count_; AlarmEvent event; @@ -664,6 +673,7 @@ private: std::chrono::system_clock::now().time_since_epoch()).count(); event.frame_id = frame->frame_id; event.detections = result.matched_detections; + event.face_matches = std::move(face_matches); event.behavior_events = result.matched_behavior_events; AlarmJob job; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f2c1cd6..19d6998 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,6 +46,7 @@ add_executable(rk3588_gtests test_action_recog.cpp test_event_fusion.cpp test_alarm_behavior_events.cpp + test_log_action.cpp test_infer_backend.cpp test_image_processor.cpp test_codec_backend.cpp @@ -59,6 +60,7 @@ add_executable(rk3588_gtests ${CMAKE_SOURCE_DIR}/plugins/action_recog/action_recog_node.cpp ${CMAKE_SOURCE_DIR}/plugins/event_fusion/event_fusion_node.cpp ${CMAKE_SOURCE_DIR}/plugins/logic_gate/color_analyzer.cpp + ${CMAKE_SOURCE_DIR}/plugins/alarm/actions/log_action.cpp ${CMAKE_SOURCE_DIR}/plugins/alarm/rule_engine.cpp ) diff --git a/tests/test_log_action.cpp b/tests/test_log_action.cpp new file mode 100644 index 0000000..fff8e42 --- /dev/null +++ b/tests/test_log_action.cpp @@ -0,0 +1,56 @@ +#include + +#include +#include +#include + +#include "utils/logger.h" +#include "../plugins/alarm/actions/log_action.h" + +namespace rk3588 { +namespace { + +TEST(LogActionTest, IncludesFaceRecognitionDetailsInAlarmLog) { + Logger::Instance().SetLevel(LogLevel::Info); + + LogAction action; + SimpleJson cfg(SimpleJson::Object{ + {"level", SimpleJson(std::string("info"))}, + {"include_detections", SimpleJson(true)}, + }); + ASSERT_TRUE(action.Init(cfg)); + + AlarmEvent event; + event.node_id = "alarm_face_cam1"; + event.rule_name = "unknown_face"; + event.frame_id = 42; + Detection det; + det.cls_id = -1; + det.score = 0.44f; + det.bbox = Rect{10.0f, 20.0f, 30.0f, 40.0f}; + det.track_id = -1; + event.detections.push_back(det); + + FaceAlarmMatch match; + match.unknown = true; + match.candidate_person_id = 7; + match.candidate_name = "alice"; + match.best_sim = 0.44f; + match.second_sim = 0.41f; + event.face_matches.push_back(match); + + action.Execute(event, std::make_shared()); + + const auto lines = Logger::Instance().RecentLines(10); + ASSERT_FALSE(lines.empty()); + const std::string& line = lines.back(); + EXPECT_NE(line.find("rule=unknown_face"), std::string::npos); + EXPECT_NE(line.find("candidate=alice"), std::string::npos); + EXPECT_NE(line.find("status=unknown"), 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); +} + +} // namespace +} // namespace rk3588