231 lines
7.5 KiB
C++
231 lines
7.5 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#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.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->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;
|
|
|
|
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<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
|
|
}
|
|
}
|
|
})");
|
|
|
|
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
|