From a3f478aa221420aec54810ea8457b7e9828b5d2c Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Wed, 15 Apr 2026 15:30:59 +0800 Subject: [PATCH] Suppress unknown alarms for known-leaning tracks --- plugins/alarm/alarm_node.cpp | 32 ++++++++++++++----- tests/test_face_track_alarm.cpp | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/plugins/alarm/alarm_node.cpp b/plugins/alarm/alarm_node.cpp index b09d6f7..090197a 100644 --- a/plugins/alarm/alarm_node.cpp +++ b/plugins/alarm/alarm_node.cpp @@ -54,6 +54,28 @@ struct FaceTrackDecision { bool trigger_unknown = false; }; +void PruneKnownHitTimes( + FaceTrackState& state, + const FaceTrackAggregationConfig& cfg, + uint64_t now_ms) { + const uint64_t known_window_ms = static_cast(std::max(0, cfg.known_hit_window_ms)); + if (known_window_ms == 0) return; + while (!state.known_hit_times.empty() && + now_ms > state.known_hit_times.front() && + (now_ms - state.known_hit_times.front()) > known_window_ms) { + state.known_hit_times.pop_front(); + } +} + +bool IsKnownLeaningTrack( + const FaceTrackState& state, + const FaceTrackAggregationConfig& cfg) { + return !state.reported_known && + !state.known_hit_times.empty() && + static_cast(state.known_hit_times.size()) < std::max(1, cfg.known_min_hits) && + (state.best_known_person_id >= 0 || !state.best_known_name.empty()); +} + void ResetFaceTrackState(FaceTrackState& state, int track_id, uint64_t now_ms) { state = FaceTrackState{}; state.track_id = track_id; @@ -107,7 +129,7 @@ FaceTrackDecision UpdateFaceTrackState( state.last_seen_ms = now_ms; } - const auto known_window_ms = static_cast(std::max(0, cfg.known_hit_window_ms)); + PruneKnownHitTimes(state, cfg, now_ms); if (!item.unknown) { const bool same_identity = state.best_known_person_id == item.best_person_id && @@ -118,13 +140,6 @@ FaceTrackDecision UpdateFaceTrackState( state.best_known_name = item.best_name; } - if (known_window_ms > 0) { - while (!state.known_hit_times.empty() && - now_ms > state.known_hit_times.front() && - (now_ms - state.known_hit_times.front()) > known_window_ms) { - state.known_hit_times.pop_front(); - } - } state.known_hit_times.push_back(now_ms); state.quality_hits = 0; @@ -138,6 +153,7 @@ FaceTrackDecision UpdateFaceTrackState( } if (state.reported_known) return decision; + if (IsKnownLeaningTrack(state, cfg)) return decision; ++state.quality_hits; if (!state.reported_unknown && diff --git a/tests/test_face_track_alarm.cpp b/tests/test_face_track_alarm.cpp index 9baa930..431f82e 100644 --- a/tests/test_face_track_alarm.cpp +++ b/tests/test_face_track_alarm.cpp @@ -116,6 +116,28 @@ TEST(FaceTrackAlarmTest, DoesNotEmitUnknownForKnownPersonScoreWobble) { 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; + const FaceRecogItem known = MakeKnownFace(10, 1, "alice", 0.63f); + + EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known, 1000).trigger_known); + EXPECT_FALSE(UpdateFaceTrackState(cfg, state, 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 = MakeUnknownFace(10, 1, "alice", 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; @@ -188,6 +210,40 @@ TEST(FaceTrackAlarmTest, UntrackedQualifiedFaceDoesNotTriggerAlarm) { EXPECT_EQ(node.alarm_count_, 0u); } +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 = MakeUnknownFace(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, IgnoresLegacyDisableFlagAndStillUsesTrackAggregation) { AlarmNode node; NodeContext ctx;