Improve face alarm log explainability

This commit is contained in:
tian 2026-04-15 10:27:00 +08:00
parent a458b46f42
commit ee4edd2d93
7 changed files with 101 additions and 2 deletions

View File

@ -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<Point2f, 5> landmarks{};

View File

@ -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;

View File

@ -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<Detection> detections;
std::vector<FaceAlarmMatch> face_matches;
std::vector<BehaviorEventItem> behavior_events;
std::string snapshot_url;
std::string clip_url;

View File

@ -70,6 +70,24 @@ void LogAction::Execute(AlarmEvent& event, std::shared_ptr<Frame> /*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;
}

View File

@ -519,7 +519,9 @@ private:
bool matched = false;
std::string matched_name;
std::vector<Detection> dets;
std::vector<FaceAlarmMatch> 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<FaceAlarmMatch> 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;

View File

@ -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
)

56
tests/test_log_action.cpp Normal file
View File

@ -0,0 +1,56 @@
#include <gtest/gtest.h>
#include <memory>
#include <string>
#include <vector>
#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<Frame>());
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