#include #include #include #include #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.unknown = false; return item; } FaceRecogItem MakeUnknownFace(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.unknown = true; 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_id = frame_id; frame->width = img_w; frame->height = img_h; frame->face_recog = std::make_shared(); 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; FaceRecogItem item = MakeUnknownFace(-1, -1, "", 0.40f); const FaceTrackDecision decision = UpdateFaceTrackState(cfg, state, 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; const FaceRecogItem item = MakeKnownFace(5, 1, "reg_001", 0.62f); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, item, 1000).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, item, 1500).trigger_known); const FaceTrackDecision decision = UpdateFaceTrackState(cfg, state, item, 2000); EXPECT_TRUE(decision.trigger_known); EXPECT_FALSE(decision.trigger_unknown); } 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; const FaceRecogItem known = MakeKnownFace(9, 1, "reg_001", 0.63f); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known, 1000).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known, 1500).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known, 2000).trigger_known); const FaceRecogItem wobble = MakeUnknownFace(9, 1, "reg_001", 0.42f); const FaceTrackDecision decision = UpdateFaceTrackState(cfg, state, 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; const FaceRecogItem alice = MakeKnownFace(7, 1, "alice", 0.80f); const FaceRecogItem bob = MakeKnownFace(7, 2, "bob", 0.82f); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, alice, 1000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, alice, 1500).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, bob, 2000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, 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; const FaceRecogItem item = MakeKnownFace(8, 1, "alice", 0.80f); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, item, 1000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, item, 1500).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, item, 4000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, 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, IgnoresLegacyDisableFlagAndStillUsesTrackAggregation) { AlarmNode node; NodeContext ctx; ctx.input_queue = std::make_shared>(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 } } })"); 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); 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); } } // namespace } // namespace rk3588