546 lines
20 KiB
C++
546 lines
20 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, UnknownRuleDiagnosticsExplainMinSimRejection) {
|
|
AlarmNode::FaceRule rule;
|
|
rule.name = "unknown_face";
|
|
rule.kind = AlarmNode::FaceRule::Kind::Unknown;
|
|
rule.min_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_FALSE(AlarmNode::FaceItemMatchesRule(rule, item, 10000.0, &eval));
|
|
EXPECT_FALSE(eval.matched);
|
|
EXPECT_EQ(eval.reject_reason, "min_sim");
|
|
EXPECT_NE(eval.detail.find("best_sim=0.200"), 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
|
|
}
|
|
],
|
|
"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);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace rk3588
|