Use max known similarity for unknown face rule

This commit is contained in:
tian 2026-04-17 15:28:13 +08:00
parent 456cbc3727
commit a657848de2
4 changed files with 35 additions and 12 deletions

View File

@ -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,

View File

@ -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 持续存在并累计足够质量帧后才认为是陌生人这样可以避免远处小脸合成人脸或短暂模糊帧被误报成陌生人
---

View File

@ -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<float>(
item.ValueOr<double>("max_face_aspect", static_cast<double>(r.max_face_aspect)));
r.min_sim = static_cast<float>(item.ValueOr<double>("min_sim", static_cast<double>(r.min_sim)));
r.max_known_sim = static_cast<float>(
item.ValueOr<double>("max_known_sim", static_cast<double>(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 {

View File

@ -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