OrangePi3588Media/tests/test_face_track_alarm.cpp

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