From a657848de2576f66683e2b98cf757aa3d429f475 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Fri, 17 Apr 2026 15:28:13 +0800 Subject: [PATCH] Use max known similarity for unknown face rule --- configs/full_pipeline_1080p_test_alarm.json | 2 +- docs/config_guide.md | 8 +++--- plugins/alarm/alarm_node.cpp | 9 ++++--- tests/test_face_track_alarm.cpp | 28 +++++++++++++++++---- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/configs/full_pipeline_1080p_test_alarm.json b/configs/full_pipeline_1080p_test_alarm.json index ee79963..a00ab0c 100644 --- a/configs/full_pipeline_1080p_test_alarm.json +++ b/configs/full_pipeline_1080p_test_alarm.json @@ -377,7 +377,7 @@ "name": "unknown_face", "type": "unknown", "cooldown_ms": 7000, - "min_sim": 0.35, + "max_known_sim": 0.35, "min_hits": 1, "hit_window_ms": 1500, "min_face_area_ratio": 0.001, diff --git a/docs/config_guide.md b/docs/config_guide.md index f4e1766..4b78466 100644 --- a/docs/config_guide.md +++ b/docs/config_guide.md @@ -571,7 +571,7 @@ python tools/analyze_face_recog_log.py .\logs\media-server_latest.log "name": "unknown_face", "type": "unknown", "cooldown_ms": 7000, - "min_sim": 0.35, + "max_known_sim": 0.35, "min_hits": 1, "hit_window_ms": 1500, "min_face_area_ratio": 0.001, @@ -602,7 +602,8 @@ python tools/analyze_face_recog_log.py .\logs\media-server_latest.log | `face_track_aggregation.known.reentry_cooldown_ms` | 同一已知人短时间离开再进入时的抑制时间 | 打卡场景建议开启,例如 `8000` 以上 | | `face_track_aggregation.unknown.min_track_age_ms` | 陌生人候选 track 至少持续多久 | 建议 `2000` 起,避免一闪而过的小脸误报 | | `face_track_aggregation.unknown.min_quality_hits` | 陌生人候选需要多少次有效质量帧 | 建议 `4` 起,保证陌生人告警更准 | -| `face_rules[].min_sim` | 进入该规则的最低相似度条件 | `known_person` 当前测试为 `0.45` | +| `known_person.min_sim` | 确认已知人的最低相似度条件 | 当前测试为 `0.45`,越高越保守 | +| `unknown_face.max_known_sim` | 陌生人候选允许的最高“已知人相似度” | 当前测试为 `0.35`;超过该值说明仍像库中某人,不直接报陌生人 | | `face_rules[].min_face_area_ratio` | 过滤小脸框 | 1080p 下 `0.0002` 约等于 `415px²`,`0.001` 约等于 `2074px²` | | `face_rules[].cooldown_ms` | 同一规则冷却 | 测试可 `7000`,正式按后台接收频率调整 | | `face_rules[].min_face_aspect / max_face_aspect` | 过滤异常长宽比人脸框 | `known_person` 当前测试为 `0.55` 到 `1.6`;`unknown_face` 建议保持 `0.6` 到 `1.6` | @@ -689,6 +690,7 @@ python tools/analyze_face_recog_log.py .\logs\media-server_latest.log | `face_recog` | `threshold.accept` | 控制已知人识别最低相似度 | 误认减少,但 known 变少 | known 变多,但误认风险升高 | | `face_recog` | `threshold.margin` | 控制 top1 和 top2 的区分度 | 相似人员误认减少,但 known 变少 | known 变多,但相似人员更容易混淆 | | `known_person` | `min_face_area_ratio` | 控制已知人告警可接受的人脸最小尺寸 | 小脸告警减少,更稳 | 更容易触发,但小脸质量风险升高 | +| `unknown_face` | `max_known_sim` | 控制陌生人候选是否“仍太像已知人” | 减少把已知人波动报成陌生人 | 更容易把未知人纳入陌生人聚合 | | `face_track_aggregation.known` | `min_hits` | 控制已知人需要多少次稳定识别才告警 | 更稳,适合正式打卡 | 更灵敏,适合验证链路 | | `alarm` | `min_duration_ms` | 控制要稳定多久才报警 | 更稳,但慢一点 | 更灵敏,但更容易闪报 | | `alarm` | `cooldown_ms` | 控制两次告警间隔 | 减少重复告警 | 同一事件会更频繁重复报 | @@ -779,7 +781,7 @@ python tools/analyze_face_recog_log.py .\logs\media-server_latest.log ### Q5: unknown 和 uncertain 有什么区别? -`uncertain` 表示“像某个已知人,但证据不足”,不能当作陌生人。`unknown_face` 应用于“持续存在、质量足够、且没有形成已知人证据”的人体 track。这样可以避免远处小脸、合成人脸或短暂模糊帧被误报成陌生人。 +`uncertain` 是单帧识别状态,表示“没有稳定确认是已知人”。它本身不能直接当作陌生人。`unknown_face` 是告警规则,使用开放集识别思路:人脸质量足够、不是 known、`best_sim < unknown_face.max_known_sim`、同一人体 track 持续存在并累计足够质量帧后,才认为是陌生人。这样可以避免远处小脸、合成人脸或短暂模糊帧被误报成陌生人。 --- diff --git a/plugins/alarm/alarm_node.cpp b/plugins/alarm/alarm_node.cpp index ce88fde..32b5d25 100644 --- a/plugins/alarm/alarm_node.cpp +++ b/plugins/alarm/alarm_node.cpp @@ -645,6 +645,7 @@ private: float min_face_aspect = 0.0f; float max_face_aspect = 0.0f; float min_sim = 0.0f; + float max_known_sim = 0.0f; }; struct FaceRuleEval { @@ -704,6 +705,8 @@ private: r.max_face_aspect = static_cast( item.ValueOr("max_face_aspect", static_cast(r.max_face_aspect))); r.min_sim = static_cast(item.ValueOr("min_sim", static_cast(r.min_sim))); + r.max_known_sim = static_cast( + item.ValueOr("max_known_sim", static_cast(r.max_known_sim))); if (const SimpleJson* persons = item.Find("persons"); persons && persons->IsArray()) { for (const auto& p : persons->AsArray()) { @@ -765,9 +768,9 @@ private: out.detail = "status=known"; return false; } - if (it.best_sim < rule.min_sim) { - out.reject_reason = "min_sim"; - out.detail = "best_sim=" + Fixed3(it.best_sim) + " min=" + Fixed3(rule.min_sim); + if (rule.max_known_sim > 0.0f && it.best_sim >= rule.max_known_sim) { + out.reject_reason = "max_known_sim"; + out.detail = "best_sim=" + Fixed3(it.best_sim) + " max=" + Fixed3(rule.max_known_sim); return false; } } else { diff --git a/tests/test_face_track_alarm.cpp b/tests/test_face_track_alarm.cpp index bb0fc5d..8ce09c3 100644 --- a/tests/test_face_track_alarm.cpp +++ b/tests/test_face_track_alarm.cpp @@ -250,20 +250,35 @@ TEST(FaceTrackAlarmTest, UntrackedQualifiedFaceDoesNotTriggerAlarm) { EXPECT_EQ(node.alarm_count_, 0u); } -TEST(FaceTrackAlarmTest, UnknownRuleDiagnosticsExplainMinSimRejection) { +TEST(FaceTrackAlarmTest, UnknownRuleAcceptsLowKnownSimilarityCandidate) { AlarmNode::FaceRule rule; rule.name = "unknown_face"; rule.kind = AlarmNode::FaceRule::Kind::Unknown; - rule.min_sim = 0.35f; + rule.max_known_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_TRUE(AlarmNode::FaceItemMatchesRule(rule, item, 10000.0, &eval)); + EXPECT_TRUE(eval.matched); +} + +TEST(FaceTrackAlarmTest, UnknownRuleDiagnosticsExplainMaxKnownSimRejection) { + AlarmNode::FaceRule rule; + rule.name = "unknown_face"; + rule.kind = AlarmNode::FaceRule::Kind::Unknown; + rule.max_known_sim = 0.35f; + + FaceRecogItem item = MakeUncertainFace(101, 7, "reg_007", 0.42f); + 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); + EXPECT_EQ(eval.reject_reason, "max_known_sim"); + EXPECT_NE(eval.detail.find("best_sim=0.420"), std::string::npos); + EXPECT_NE(eval.detail.find("max=0.350"), std::string::npos); } TEST(FaceTrackAlarmTest, UnknownRuleDiagnosticsUseHighPrecisionAreaRatio) { @@ -535,7 +550,8 @@ TEST(FaceTrackAlarmTest, ParsesTrackAggregationWithoutUnknownReentryCooldownCont { "name": "unknown_face", "type": "unknown", - "cooldown_ms": 0 + "cooldown_ms": 0, + "max_known_sim": 0.35 } ], "face_track_aggregation": { @@ -555,6 +571,8 @@ TEST(FaceTrackAlarmTest, ParsesTrackAggregationWithoutUnknownReentryCooldownCont 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); + ASSERT_EQ(node.face_rules_.size(), 1u); + EXPECT_FLOAT_EQ(node.face_rules_[0].max_known_sim, 0.35f); } } // namespace