OrangePi3588Media/tests/test_face_track_alarm.cpp

580 lines
22 KiB
C++

#include <gtest/gtest.h>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
#include "node.h"
#include "utils/simple_json.h"
#define private public
#undef REGISTER_NODE
#define REGISTER_NODE(NodeClass, NodeTypeStr)
#include "../plugins/alarm/alarm_node.cpp"
namespace rk3588 {
namespace {
FaceRecogItem MakeKnownFace(int track_id, int person_id, const std::string& name, float best_sim) {
FaceRecogItem item;
item.person_track_id = track_id;
item.best_person_id = person_id;
item.best_name = name;
item.candidate_person_id = person_id;
item.candidate_name = name;
item.best_sim = best_sim;
item.second_sim = 0.20f;
item.state = FaceRecogState::Known;
return item;
}
FaceRecogItem MakeUncertainFace(int track_id, int candidate_person_id, const std::string& candidate_name, float best_sim) {
FaceRecogItem item;
item.person_track_id = track_id;
item.candidate_person_id = candidate_person_id;
item.candidate_name = candidate_name;
item.best_sim = best_sim;
item.second_sim = best_sim - 0.02f;
item.state = FaceRecogState::Uncertain;
return item;
}
FramePtr MakeFaceFrame(uint64_t frame_id, const FaceRecogItem& item, int img_w = 100, int img_h = 100) {
auto frame = std::make_shared<Frame>();
frame->frame_id = frame_id;
frame->width = img_w;
frame->height = img_h;
frame->face_recog = std::make_shared<FaceRecogResult>();
frame->face_recog->img_w = img_w;
frame->face_recog->img_h = img_h;
frame->face_recog->items.push_back(item);
return frame;
}
SimpleJson ParseConfig(const std::string& text) {
SimpleJson config;
std::string err;
const bool ok = ParseSimpleJson(text, config, err);
EXPECT_TRUE(ok) << err;
return config;
}
AlarmNode::FaceRule MakeKnownRule() {
AlarmNode::FaceRule rule;
rule.name = "known_person";
rule.kind = AlarmNode::FaceRule::Kind::Person;
rule.persons = {"alice"};
rule.cooldown_ms = 0;
return rule;
}
TEST(FaceTrackAlarmTest, IgnoresLowQualityUntrackedFace) {
FaceTrackAggregationConfig cfg;
FaceTrackState state;
std::unordered_map<int, uint64_t> known_identity_last_alarm_ms;
FaceRecogItem item = MakeUncertainFace(-1, -1, "", 0.40f);
const FaceTrackDecision decision = UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1000);
EXPECT_FALSE(decision.trigger_known);
EXPECT_FALSE(decision.trigger_unknown);
}
TEST(FaceTrackAlarmTest, EmitsKnownAfterStableHitsOnSameTrack) {
FaceTrackAggregationConfig cfg;
cfg.known_min_hits = 3;
cfg.known_hit_window_ms = 3000;
FaceTrackState state;
std::unordered_map<int, uint64_t> known_identity_last_alarm_ms;
const FaceRecogItem item = MakeKnownFace(5, 1, "reg_001", 0.62f);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1000).trigger_known);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1500).trigger_known);
const FaceTrackDecision decision = UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 2000);
EXPECT_TRUE(decision.trigger_known);
EXPECT_FALSE(decision.trigger_unknown);
}
TEST(FaceTrackAlarmTest, SuppressesKnownReentryInsideConfiguredCooldown) {
FaceTrackAggregationConfig cfg;
cfg.known_min_hits = 2;
cfg.known_hit_window_ms = 3000;
cfg.known_reentry_cooldown_ms = 5000;
std::unordered_map<int, uint64_t> known_identity_last_alarm_ms;
const FaceRecogItem initial_track = MakeKnownFace(8, 1, "alice", 0.66f);
FaceTrackState first_track_state;
EXPECT_FALSE(UpdateFaceTrackState(cfg, first_track_state, known_identity_last_alarm_ms, initial_track, 1000).trigger_known);
EXPECT_TRUE(UpdateFaceTrackState(cfg, first_track_state, known_identity_last_alarm_ms, initial_track, 1500).trigger_known);
FaceTrackState reentry_state;
FaceRecogItem reentry_track = initial_track;
reentry_track.person_track_id = 18;
EXPECT_FALSE(UpdateFaceTrackState(cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4000).trigger_known);
const FaceTrackDecision suppressed = UpdateFaceTrackState(
cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4500);
EXPECT_FALSE(suppressed.trigger_known);
EXPECT_FALSE(reentry_state.reported_known);
EXPECT_EQ(known_identity_last_alarm_ms.at(1), 1500u);
const FaceTrackDecision after_cooldown = UpdateFaceTrackState(
cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 7000);
EXPECT_TRUE(after_cooldown.trigger_known);
EXPECT_TRUE(reentry_state.reported_known);
EXPECT_EQ(known_identity_last_alarm_ms.at(1), 7000u);
}
TEST(FaceTrackAlarmTest, DoesNotEmitUnknownForKnownPersonScoreWobble) {
FaceTrackAggregationConfig cfg;
cfg.known_min_hits = 3;
cfg.known_hit_window_ms = 3000;
cfg.unknown_min_track_age_ms = 1000;
cfg.unknown_min_quality_hits = 1;
FaceTrackState state;
std::unordered_map<int, uint64_t> known_identity_last_alarm_ms;
const FaceRecogItem known = MakeKnownFace(9, 1, "reg_001", 0.63f);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 1000).trigger_known);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 1500).trigger_known);
EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 2000).trigger_known);
const FaceRecogItem wobble = MakeUncertainFace(9, 1, "reg_001", 0.42f);
const FaceTrackDecision decision = UpdateFaceTrackState(
cfg, state, known_identity_last_alarm_ms, wobble, 2600);
EXPECT_FALSE(decision.trigger_known);
EXPECT_FALSE(decision.trigger_unknown);
}
TEST(FaceTrackAlarmTest, DoesNotEmitUnknownWhileTrackIsKnownLeaning) {
FaceTrackAggregationConfig cfg;
cfg.known_min_hits = 3;
cfg.known_hit_window_ms = 3000;
cfg.unknown_min_track_age_ms = 1000;
cfg.unknown_min_quality_hits = 1;
FaceTrackState state;
std::unordered_map<int, uint64_t> known_identity_last_alarm_ms;
const FaceRecogItem known = MakeKnownFace(10, 1, "alice", 0.63f);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 1000).trigger_known);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 1500).trigger_known);
EXPECT_EQ(state.best_known_person_id, 1);
EXPECT_EQ(state.best_known_name, "alice");
EXPECT_EQ(state.known_hit_times.size(), 2u);
const FaceRecogItem wobble = MakeUncertainFace(10, 1, "alice", 0.42f);
const FaceTrackDecision decision = UpdateFaceTrackState(
cfg, state, known_identity_last_alarm_ms, wobble, 2600);
EXPECT_FALSE(decision.trigger_known);
EXPECT_FALSE(decision.trigger_unknown);
}
TEST(FaceTrackAlarmTest, ResetsKnownStateWhenIdentityChangesOnSameTrack) {
FaceTrackAggregationConfig cfg;
cfg.known_min_hits = 2;
cfg.known_hit_window_ms = 3000;
FaceTrackState state;
std::unordered_map<int, uint64_t> known_identity_last_alarm_ms;
const FaceRecogItem alice = MakeKnownFace(7, 1, "alice", 0.80f);
const FaceRecogItem bob = MakeKnownFace(7, 2, "bob", 0.82f);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, alice, 1000).trigger_known);
EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, alice, 1500).trigger_known);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, bob, 2000).trigger_known);
EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, bob, 2500).trigger_known);
}
TEST(FaceTrackAlarmTest, ExpiresStateAfterTrackInactivity) {
FaceTrackAggregationConfig cfg;
cfg.known_min_hits = 2;
cfg.known_hit_window_ms = 3000;
cfg.state_expire_ms = 1000;
FaceTrackState state;
std::unordered_map<int, uint64_t> known_identity_last_alarm_ms;
const FaceRecogItem item = MakeKnownFace(8, 1, "alice", 0.80f);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1000).trigger_known);
EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1500).trigger_known);
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 4000).trigger_known);
EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 4500).trigger_known);
}
TEST(FaceTrackAlarmTest, DisqualifiedFramesDoNotAdvanceTrackAggregation) {
AlarmNode node;
node.track_agg_cfg_.known_min_hits = 2;
node.track_agg_cfg_.known_hit_window_ms = 5000;
auto rule = MakeKnownRule();
rule.min_face_area_ratio = 0.03f;
node.face_rules_.push_back(rule);
FaceRecogItem small = MakeKnownFace(11, 1, "alice", 0.85f);
small.bbox = Rect{0.0f, 0.0f, 5.0f, 5.0f};
FaceRecogItem valid = MakeKnownFace(11, 1, "alice", 0.85f);
valid.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
EXPECT_EQ(node.Process(MakeFaceFrame(1, small)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
EXPECT_EQ(node.Process(MakeFaceFrame(2, valid)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
EXPECT_EQ(node.Process(MakeFaceFrame(3, valid)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
}
TEST(FaceTrackAlarmTest, UntrackedQualifiedFaceDoesNotTriggerAlarm) {
AlarmNode node;
node.track_agg_cfg_.known_min_hits = 2;
node.track_agg_cfg_.known_hit_window_ms = 5000;
node.face_rules_.push_back(MakeKnownRule());
FaceRecogItem first = MakeKnownFace(-1, 5, "alice", 0.88f);
first.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
FaceRecogItem second = first;
EXPECT_EQ(node.Process(MakeFaceFrame(1, first)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
EXPECT_EQ(node.Process(MakeFaceFrame(2, second)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
}
TEST(FaceTrackAlarmTest, UnknownRuleAcceptsLowKnownSimilarityCandidate) {
AlarmNode::FaceRule rule;
rule.name = "unknown_face";
rule.kind = AlarmNode::FaceRule::Kind::Unknown;
rule.max_known_sim = 0.35f;
FaceRecogItem item = MakeUncertainFace(101, 7, "reg_007", 0.20f);
item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
AlarmNode::FaceRuleEval eval;
EXPECT_TRUE(AlarmNode::FaceItemMatchesRule(rule, item, 10000.0, &eval));
EXPECT_TRUE(eval.matched);
}
TEST(FaceTrackAlarmTest, UnknownRuleDiagnosticsExplainMaxKnownSimRejection) {
AlarmNode::FaceRule rule;
rule.name = "unknown_face";
rule.kind = AlarmNode::FaceRule::Kind::Unknown;
rule.max_known_sim = 0.35f;
FaceRecogItem item = MakeUncertainFace(101, 7, "reg_007", 0.42f);
item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
AlarmNode::FaceRuleEval eval;
EXPECT_FALSE(AlarmNode::FaceItemMatchesRule(rule, item, 10000.0, &eval));
EXPECT_FALSE(eval.matched);
EXPECT_EQ(eval.reject_reason, "max_known_sim");
EXPECT_NE(eval.detail.find("best_sim=0.420"), std::string::npos);
EXPECT_NE(eval.detail.find("max=0.350"), std::string::npos);
}
TEST(FaceTrackAlarmTest, UnknownRuleDiagnosticsUseHighPrecisionAreaRatio) {
AlarmNode::FaceRule rule;
rule.name = "unknown_face";
rule.kind = AlarmNode::FaceRule::Kind::Unknown;
rule.min_face_area_ratio = 0.001f;
FaceRecogItem item = MakeUncertainFace(102, 7, "reg_007", 0.40f);
item.bbox = Rect{0.0f, 0.0f, 28.0f, 40.0f};
AlarmNode::FaceRuleEval eval;
EXPECT_FALSE(AlarmNode::FaceItemMatchesRule(rule, item, 1920.0 * 1080.0, &eval));
EXPECT_EQ(eval.reject_reason, "min_face_area_ratio");
EXPECT_NE(eval.detail.find("area_ratio=0.000540"), std::string::npos);
EXPECT_NE(eval.detail.find("min=0.001000"), std::string::npos);
}
TEST(FaceTrackAlarmTest, ParsesFaceDebugUnknownCandidateLogging) {
AlarmNode node;
NodeContext ctx;
ctx.input_queue = std::make_shared<SpscQueue<FramePtr>>(4, QueueDropStrategy::DropOldest);
const SimpleJson config = ParseConfig(R"({
"id": "alarm",
"face_debug": {
"log_unknown_candidates": true,
"unknown_candidate_interval_ms": 123
}
})");
ASSERT_TRUE(node.Init(config, ctx));
EXPECT_TRUE(node.face_debug_log_unknown_candidates_);
EXPECT_EQ(node.face_debug_unknown_candidate_interval_ms_, 123);
}
TEST(FaceTrackAlarmTest, KnownLeaningTrackDoesNotFireUnknownRule) {
AlarmNode node;
node.track_agg_cfg_.known_min_hits = 3;
node.track_agg_cfg_.known_hit_window_ms = 5000;
node.track_agg_cfg_.unknown_min_track_age_ms = 1000;
node.track_agg_cfg_.unknown_min_quality_hits = 1;
auto known_rule = MakeKnownRule();
AlarmNode::FaceRule unknown_rule;
unknown_rule.name = "unknown_face";
unknown_rule.kind = AlarmNode::FaceRule::Kind::Unknown;
unknown_rule.cooldown_ms = 0;
node.face_rules_.push_back(known_rule);
node.face_rules_.push_back(unknown_rule);
FaceRecogItem known = MakeKnownFace(41, 5, "alice", 0.88f);
known.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
FaceRecogItem wobble = MakeUncertainFace(41, 5, "alice", 0.42f);
wobble.bbox = known.bbox;
EXPECT_EQ(node.Process(MakeFaceFrame(1, known)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
EXPECT_EQ(node.Process(MakeFaceFrame(2, known)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
EXPECT_EQ(node.Process(MakeFaceFrame(3, wobble)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
EXPECT_EQ(node.Process(MakeFaceFrame(4, known)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
}
TEST(FaceTrackAlarmTest, OverlappingRulesDoNotDoubleCountSingleFrameEvidence) {
AlarmNode node;
node.track_agg_cfg_.known_min_hits = 2;
node.track_agg_cfg_.known_hit_window_ms = 5000;
auto first_rule = MakeKnownRule();
first_rule.name = "known_person_primary";
auto second_rule = MakeKnownRule();
second_rule.name = "known_person_secondary";
node.face_rules_.push_back(first_rule);
node.face_rules_.push_back(second_rule);
FaceRecogItem item = MakeKnownFace(51, 5, "alice", 0.88f);
item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
EXPECT_EQ(node.Process(MakeFaceFrame(1, item)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
const auto it_first = node.face_track_states_.find(51);
ASSERT_NE(it_first, node.face_track_states_.end());
EXPECT_EQ(it_first->second.known_hit_times.size(), 1u);
EXPECT_FALSE(it_first->second.reported_known);
EXPECT_EQ(node.Process(MakeFaceFrame(2, item)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 2u);
const auto it_second = node.face_track_states_.find(51);
ASSERT_NE(it_second, node.face_track_states_.end());
EXPECT_EQ(it_second->second.known_hit_times.size(), 2u);
EXPECT_TRUE(it_second->second.reported_known);
}
TEST(FaceTrackAlarmTest, KnownPersonDoesNotReAlarmWhileTrackStaysActive) {
AlarmNode node;
node.track_agg_cfg_.known_min_hits = 2;
node.track_agg_cfg_.known_hit_window_ms = 5000;
auto rule = MakeKnownRule();
rule.cooldown_ms = 0;
node.face_rules_.push_back(rule);
FaceRecogItem item = MakeKnownFace(61, 5, "alice", 0.88f);
item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
EXPECT_EQ(node.Process(MakeFaceFrame(1, item)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
EXPECT_EQ(node.Process(MakeFaceFrame(2, item)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
EXPECT_EQ(node.Process(MakeFaceFrame(3, item)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
EXPECT_EQ(node.Process(MakeFaceFrame(4, item)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
}
TEST(FaceTrackAlarmTest, KnownPersonReEntryInsideCooldownOnNewTrackDoesNotReAlarm) {
AlarmNode node;
node.track_agg_cfg_.known_min_hits = 2;
node.track_agg_cfg_.known_hit_window_ms = 5000;
node.track_agg_cfg_.known_reentry_cooldown_ms = 1000;
auto rule = MakeKnownRule();
rule.cooldown_ms = 0;
node.face_rules_.push_back(rule);
FaceRecogItem first_track = MakeKnownFace(71, 5, "alice", 0.88f);
first_track.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
FaceRecogItem second_track = MakeKnownFace(72, 5, "alice", 0.88f);
second_track.bbox = first_track.bbox;
EXPECT_EQ(node.Process(MakeFaceFrame(1, first_track)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
EXPECT_EQ(node.Process(MakeFaceFrame(2, first_track)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
ASSERT_EQ(node.known_identity_last_alarm_ms_.size(), 1u);
const auto first_alarm_it = node.known_identity_last_alarm_ms_.find(5);
ASSERT_NE(first_alarm_it, node.known_identity_last_alarm_ms_.end());
EXPECT_EQ(node.Process(MakeFaceFrame(3, second_track)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
EXPECT_EQ(node.Process(MakeFaceFrame(4, second_track)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
EXPECT_EQ(node.known_identity_last_alarm_ms_.at(5), first_alarm_it->second);
}
TEST(FaceTrackAlarmTest, KnownPersonReEntryTrackCanAlarmAfterCooldownExpires) {
AlarmNode node;
node.track_agg_cfg_.known_min_hits = 2;
node.track_agg_cfg_.known_hit_window_ms = 5000;
node.track_agg_cfg_.known_reentry_cooldown_ms = 1000;
auto rule = MakeKnownRule();
rule.cooldown_ms = 0;
node.face_rules_.push_back(rule);
FaceRecogItem first_track = MakeKnownFace(81, 5, "alice", 0.88f);
first_track.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
FaceRecogItem second_track = MakeKnownFace(82, 5, "alice", 0.88f);
second_track.bbox = first_track.bbox;
EXPECT_EQ(node.Process(MakeFaceFrame(1, first_track)), NodeStatus::OK);
EXPECT_EQ(node.Process(MakeFaceFrame(2, first_track)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
EXPECT_EQ(node.Process(MakeFaceFrame(3, second_track)), NodeStatus::OK);
EXPECT_EQ(node.Process(MakeFaceFrame(4, second_track)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
EXPECT_FALSE(node.face_track_states_.at(82).reported_known);
node.known_identity_last_alarm_ms_[5] = 0;
EXPECT_EQ(node.Process(MakeFaceFrame(5, second_track)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 2u);
EXPECT_TRUE(node.face_track_states_.at(82).reported_known);
}
TEST(FaceTrackAlarmTest, SuppressedKnownReentryTrackDoesNotAgeIntoUnknownAfterSuppressionWindowWobble) {
FaceTrackAggregationConfig cfg;
cfg.known_min_hits = 2;
cfg.known_hit_window_ms = 5000;
cfg.known_reentry_cooldown_ms = 5000;
cfg.unknown_min_track_age_ms = 1000;
cfg.unknown_min_quality_hits = 1;
std::unordered_map<int, uint64_t> known_identity_last_alarm_ms;
const FaceRecogItem initial_track = MakeKnownFace(91, 5, "alice", 0.88f);
FaceTrackState first_track_state;
EXPECT_FALSE(UpdateFaceTrackState(cfg, first_track_state, known_identity_last_alarm_ms, initial_track, 1000).trigger_known);
EXPECT_TRUE(UpdateFaceTrackState(cfg, first_track_state, known_identity_last_alarm_ms, initial_track, 1500).trigger_known);
FaceTrackState reentry_state;
FaceRecogItem reentry_track = initial_track;
reentry_track.person_track_id = 92;
EXPECT_FALSE(UpdateFaceTrackState(cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4000).trigger_known);
EXPECT_FALSE(UpdateFaceTrackState(cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4500).trigger_known);
FaceRecogItem wobble = MakeUncertainFace(92, 5, "alice", 0.42f);
const FaceTrackDecision wobble_decision = UpdateFaceTrackState(
cfg, reentry_state, known_identity_last_alarm_ms, wobble, 7000);
EXPECT_FALSE(wobble_decision.trigger_unknown);
EXPECT_FALSE(reentry_state.reported_unknown);
}
TEST(FaceTrackAlarmTest, IgnoresLegacyDisableFlagAndStillUsesTrackAggregation) {
AlarmNode node;
NodeContext ctx;
ctx.input_queue = std::make_shared<SpscQueue<FramePtr>>(4, QueueDropStrategy::DropOldest);
const SimpleJson config = ParseConfig(R"({
"id": "alarm",
"face_rules": [
{
"name": "known_person",
"type": "person",
"persons": ["alice"],
"cooldown_ms": 0
}
],
"face_track_aggregation": {
"enable": false,
"known": {
"min_hits": 2,
"hit_window_ms": 5000,
"reentry_cooldown_ms": 60000
}
}
})");
ASSERT_TRUE(node.Init(config, ctx));
EXPECT_EQ(node.track_agg_cfg_.known_min_hits, 2);
EXPECT_EQ(node.track_agg_cfg_.known_hit_window_ms, 5000);
EXPECT_EQ(node.track_agg_cfg_.known_reentry_cooldown_ms, 60000);
FaceRecogItem item = MakeKnownFace(31, 5, "alice", 0.88f);
item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
EXPECT_EQ(node.Process(MakeFaceFrame(1, item)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 0u);
EXPECT_EQ(node.Process(MakeFaceFrame(2, item)), NodeStatus::OK);
EXPECT_EQ(node.alarm_count_, 1u);
}
TEST(FaceTrackAlarmTest, ParsesTrackAggregationWithoutUnknownReentryCooldownContract) {
AlarmNode node;
NodeContext ctx;
ctx.input_queue = std::make_shared<SpscQueue<FramePtr>>(4, QueueDropStrategy::DropOldest);
const SimpleJson config = ParseConfig(R"({
"id": "alarm",
"face_rules": [
{
"name": "unknown_face",
"type": "unknown",
"cooldown_ms": 0,
"max_known_sim": 0.35
}
],
"face_track_aggregation": {
"known": {
"min_hits": 2,
"hit_window_ms": 5000,
"reentry_cooldown_ms": 60000
},
"unknown": {
"min_track_age_ms": 1200,
"min_quality_hits": 2
}
}
})");
ASSERT_TRUE(node.Init(config, ctx));
EXPECT_EQ(node.track_agg_cfg_.known_reentry_cooldown_ms, 60000);
EXPECT_EQ(node.track_agg_cfg_.unknown_min_track_age_ms, 1200);
EXPECT_EQ(node.track_agg_cfg_.unknown_min_quality_hits, 2);
ASSERT_EQ(node.face_rules_.size(), 1u);
EXPECT_FLOAT_EQ(node.face_rules_[0].max_known_sim, 0.35f);
}
} // namespace
} // namespace rk3588