diff --git a/configs/overlays/face_test_sensitive.json b/configs/overlays/face_test_sensitive.json new file mode 100644 index 0000000..ce2821b --- /dev/null +++ b/configs/overlays/face_test_sensitive.json @@ -0,0 +1,48 @@ +{ + "description": "Use sensitive face alarm thresholds for short validation videos. Keep production templates conservative.", + "instance_overrides": { + "*": { + "override": { + "nodes": { + "alarm": { + "face_track_aggregation": { + "known": { + "min_hits": 1, + "hit_window_ms": 3000, + "reentry_cooldown_ms": 8000 + }, + "unknown": { + "min_track_age_ms": 1500, + "min_quality_hits": 4 + } + }, + "face_rules": [ + { + "name": "unknown_face", + "type": "unknown", + "cooldown_ms": 7000, + "max_known_sim": 0.35, + "min_hits": 1, + "hit_window_ms": 1500, + "min_face_area_ratio": 0.001, + "min_face_aspect": 0.6, + "max_face_aspect": 1.6 + }, + { + "name": "known_person", + "type": "person", + "cooldown_ms": 7000, + "min_sim": 0.45, + "min_hits": 1, + "hit_window_ms": 1500, + "min_face_area_ratio": 0.0002, + "min_face_aspect": 0.55, + "max_face_aspect": 1.6 + } + ] + } + } + } + } + } +} diff --git a/configs/overlays/shoe_test_sensitive.json b/configs/overlays/shoe_test_sensitive.json new file mode 100644 index 0000000..c7fc7c0 --- /dev/null +++ b/configs/overlays/shoe_test_sensitive.json @@ -0,0 +1,28 @@ +{ + "description": "Use sensitive workshoe alarm thresholds for short validation videos. Keep production templates conservative.", + "instance_overrides": { + "*": { + "override": { + "nodes": { + "alarm": { + "rules": [ + { + "name": "non_compliant_workshoe", + "class_ids": [2], + "roi": {"x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0}, + "min_score": 0.1, + "min_box_area_ratio": 0.0, + "require_track_id": false, + "min_duration_ms": 0, + "min_hits": 1, + "hit_window_ms": 3000, + "cooldown_ms": 1000, + "per_track_cooldown_ms": 0 + } + ] + } + } + } + } + } +} diff --git a/configs/templates/workshop_face_shoe_alarm.json b/configs/templates/workshop_face_shoe_alarm.json index f95d4e3..1f98666 100644 --- a/configs/templates/workshop_face_shoe_alarm.json +++ b/configs/templates/workshop_face_shoe_alarm.json @@ -70,7 +70,7 @@ "nms_thresh": 0.4, "max_faces": 50, "debug": { - "stats": true, + "stats": false, "stats_interval": 30 } }, @@ -103,8 +103,8 @@ "dtype": "auto" }, "debug": { - "enabled": true, - "log_matches": true, + "enabled": false, + "log_matches": false, "min_log_interval_ms": 0 } }, @@ -142,9 +142,9 @@ "bottom": 0.16 }, "debug": { - "stats": true, + "stats": false, "stats_interval": 30, - "detections": true + "detections": false } }, { @@ -344,29 +344,29 @@ "w": 1.0, "h": 1.0 }, - "min_score": 0.1, + "min_score": 0.3, "min_box_area_ratio": 0.0, "require_track_id": false, - "min_duration_ms": 0, - "min_hits": 1, - "hit_window_ms": 3000, - "cooldown_ms": 1000, + "min_duration_ms": 800, + "min_hits": 2, + "hit_window_ms": 2000, + "cooldown_ms": 15000, "per_track_cooldown_ms": 0 } ], "face_track_aggregation": { "known": { - "min_hits": 1, + "min_hits": 3, "hit_window_ms": 3000, - "reentry_cooldown_ms": 8000 + "reentry_cooldown_ms": 300000 }, "unknown": { - "min_track_age_ms": 1500, + "min_track_age_ms": 2000, "min_quality_hits": 4 } }, "face_debug": { - "log_unknown_candidates": true, + "log_unknown_candidates": false, "unknown_candidate_interval_ms": 0 }, "face_rules": [ @@ -375,7 +375,7 @@ "type": "unknown", "cooldown_ms": 7000, "max_known_sim": 0.35, - "min_hits": 1, + "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.001, "min_face_aspect": 0.6, @@ -385,11 +385,11 @@ "name": "known_person", "type": "person", "cooldown_ms": 7000, - "min_sim": 0.45, - "min_hits": 1, + "min_sim": 0.6, + "min_hits": 2, "hit_window_ms": 1500, - "min_face_area_ratio": 0.0002, - "min_face_aspect": 0.55, + "min_face_area_ratio": 0.001, + "min_face_aspect": 0.6, "max_face_aspect": 1.6 } ], diff --git a/docs/config_guide.md b/docs/config_guide.md index 790219c..4d4600f 100644 --- a/docs/config_guide.md +++ b/docs/config_guide.md @@ -546,24 +546,31 @@ python tools/analyze_face_recog_log.py .\logs\media-server_latest.log } ``` -当前告警策略: +生产默认告警策略: - 只对 `cls=2 non_black_shoe` 告警 - 蓝框稳定出现 2 次以上,且持续约 `800ms`,才触发 - 触发后进入 `15s` 冷却,避免反复刷屏 +测试短视频如果需要快速验证链路,可以使用 `configs/overlays/shoe_test_sensitive.json` 临时恢复更灵敏的阈值: + +- `min_score=0.1` +- `min_duration_ms=0` +- `min_hits=1` +- `cooldown_ms=1000` + 完整流程中还可以配置人脸告警: ```json { "face_track_aggregation": { "known": { - "min_hits": 1, + "min_hits": 3, "hit_window_ms": 3000, - "reentry_cooldown_ms": 8000 + "reentry_cooldown_ms": 300000 }, "unknown": { - "min_track_age_ms": 1500, + "min_track_age_ms": 2000, "min_quality_hits": 4 } }, @@ -573,7 +580,7 @@ python tools/analyze_face_recog_log.py .\logs\media-server_latest.log "type": "unknown", "cooldown_ms": 7000, "max_known_sim": 0.35, - "min_hits": 1, + "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.001, "min_face_aspect": 0.6, @@ -583,11 +590,11 @@ python tools/analyze_face_recog_log.py .\logs\media-server_latest.log "name": "known_person", "type": "person", "cooldown_ms": 7000, - "min_sim": 0.45, - "min_hits": 1, + "min_sim": 0.6, + "min_hits": 2, "hit_window_ms": 1500, - "min_face_area_ratio": 0.0002, - "min_face_aspect": 0.55, + "min_face_area_ratio": 0.001, + "min_face_aspect": 0.6, "max_face_aspect": 1.6 } ] @@ -598,12 +605,12 @@ python tools/analyze_face_recog_log.py .\logs\media-server_latest.log | 参数 | 作用 | 设置建议 | |------|------|----------| -| `face_track_aggregation.known.min_hits` | 同一人体 track 需要多少次 `known` 才触发已知人告警 | 验证链路用 `1`;正式运行建议 `2` 或 `3` | +| `face_track_aggregation.known.min_hits` | 同一人体 track 需要多少次 `known` 才触发已知人告警 | 正式运行建议 `2` 或 `3`;验证链路可用 `face_test_sensitive` 降为 `1` | | `face_track_aggregation.known.hit_window_ms` | `known` 证据累计窗口 | `3000`,人脸出现时间短可适当加大 | -| `face_track_aggregation.known.reentry_cooldown_ms` | 同一已知人短时间离开再进入时的抑制时间 | 打卡场景建议开启,例如 `8000` 以上 | -| `face_track_aggregation.unknown.min_track_age_ms` | 陌生人候选 track 至少持续多久 | 当前测试为 `1500`;正式场景可提高到 `2000` 以减少短暂误报 | +| `face_track_aggregation.known.reentry_cooldown_ms` | 同一已知人短时间离开再进入时的抑制时间 | 打卡场景建议使用分钟级,例如模板默认 `300000` | +| `face_track_aggregation.unknown.min_track_age_ms` | 陌生人候选 track 至少持续多久 | 正式默认 `2000`;测试短视频可用 `face_test_sensitive` 降为 `1500` | | `face_track_aggregation.unknown.min_quality_hits` | 陌生人候选需要多少次有效质量帧 | 建议 `4` 起,保证陌生人告警更准 | -| `known_person.min_sim` | 确认已知人的最低相似度条件 | 当前测试为 `0.45`,越高越保守 | +| `known_person.min_sim` | 确认已知人的最低相似度条件 | 模板默认 `0.6`;测试短视频可用 `face_test_sensitive` 降为 `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`,正式按后台接收频率调整 | @@ -668,6 +675,7 @@ python tools/render_config.py \ --template configs/templates/workshop_face_shoe_alarm.json \ --profile configs/profiles/local_3588_test.json \ --overlay configs/overlays/face_debug.json \ + --overlay configs/overlays/face_test_sensitive.json \ --out configs/generated/local_3588_face_debug.json ``` @@ -686,6 +694,13 @@ python tools/render_config.py \ | `configs/overlays/` | 测试或运行场景覆盖,例如 debug、阈值、频率 | | `configs/generated/` | 渲染产物,不手工维护,不提交生成的 JSON | +`--overlay` 可以指定多次,后面的 overlay 会覆盖前面的同名字段。建议: + +- 生产:不加测试敏感 overlay,或只加 `production_quiet.json`。 +- 人脸测试:`face_debug.json` + `face_test_sensitive.json`。 +- 鞋子测试:`shoe_debug.json` + `shoe_test_sensitive.json`。 +- 人脸和鞋子同时测试:四个 overlay 可按 debug、test sensitive 的顺序叠加。 + --- ## 7. 调参顺序 diff --git a/docs/design/ConfigTemplate_Rendering_Design.md b/docs/design/ConfigTemplate_Rendering_Design.md index 0f2b830..1030aab 100644 --- a/docs/design/ConfigTemplate_Rendering_Design.md +++ b/docs/design/ConfigTemplate_Rendering_Design.md @@ -32,7 +32,7 @@ configs/ - HLS/RTSP 发布 - 告警、截图、录像、MinIO 上传、External API 上传 -模板中只保留 DAG 和插件结构,设备差异通过占位符表达,例如 `${rtsp_url}`、`${face_gallery_path}`、`${minio_endpoint}`、`${external_get_token_url}`。 +模板中保留 DAG、插件结构和生产默认阈值。设备差异通过占位符表达,例如 `${rtsp_url}`、`${face_gallery_path}`、`${minio_endpoint}`、`${external_get_token_url}`。短视频验证、临时放宽阈值或打开高频日志,应通过 overlay 完成,不应直接改模板。 ## Profile @@ -44,9 +44,12 @@ Overlay 用于测试或运行场景覆盖: - `configs/overlays/face_debug.json`:打开人脸识别和陌生人候选诊断日志。 - `configs/overlays/shoe_debug.json`:打开鞋子关联和颜色判断 debug。 +- `configs/overlays/face_test_sensitive.json`:恢复人脸短视频测试用的灵敏触发阈值。 +- `configs/overlays/shoe_test_sensitive.json`:恢复鞋子短视频测试用的灵敏触发阈值。 - `configs/overlays/production_quiet.json`:关闭高频 debug 日志,适合正式运行。 Overlay 支持 `instance_overrides."*"`,可以一次覆盖所有 instance;也可以使用具体 instance 名只覆盖单路相机。 +`--overlay` 可以指定多次,后指定的 overlay 覆盖同名字段。建议将 debug overlay 和 test-sensitive overlay 分开组合,避免把测试阈值误带入生产。 ## 渲染命令 @@ -55,9 +58,12 @@ python tools/render_config.py \ --template configs/templates/workshop_face_shoe_alarm.json \ --profile configs/profiles/local_3588_test.json \ --overlay configs/overlays/face_debug.json \ + --overlay configs/overlays/face_test_sensitive.json \ --out configs/generated/local_3588_face_debug.json ``` +生产运行通常不加 test-sensitive overlay;需要安静日志时可叠加 `configs/overlays/production_quiet.json`。 + 设备运行: ```bash diff --git a/tests/test_render_config.py b/tests/test_render_config.py index 08f1af1..af5f170 100644 --- a/tests/test_render_config.py +++ b/tests/test_render_config.py @@ -1,6 +1,7 @@ import importlib.util import pathlib import sys +import tempfile import unittest @@ -65,6 +66,80 @@ class RenderConfigTest(unittest.TestCase): self.assertTrue(node_override["debug"]["enabled"]) self.assertTrue(node_override["debug"]["log_matches"]) + def test_render_applies_multiple_overlays_in_order(self): + module = load_module() + with tempfile.TemporaryDirectory() as tmp_dir: + tmp = pathlib.Path(tmp_dir) + template_path = tmp / "template.json" + profile_path = tmp / "profile.json" + overlay1_path = tmp / "overlay1.json" + overlay2_path = tmp / "overlay2.json" + + template_path.write_text( + """ + { + "name": "pipeline", + "template": { + "nodes": [{"id": "alarm", "type": "alarm"}], + "edges": [] + } + } + """, + encoding="utf-8", + ) + profile_path.write_text( + """ + { + "instances": [{"name": "cam1", "params": {}}] + } + """, + encoding="utf-8", + ) + overlay1_path.write_text( + """ + { + "instance_overrides": { + "*": { + "override": { + "nodes": { + "alarm": { + "face_debug": {"log_unknown_candidates": true}, + "rules": [{"name": "shoe", "cooldown_ms": 1000}] + } + } + } + } + } + } + """, + encoding="utf-8", + ) + overlay2_path.write_text( + """ + { + "instance_overrides": { + "*": { + "override": { + "nodes": { + "alarm": { + "face_debug": {"unknown_candidate_interval_ms": 0}, + "rules": [{"name": "shoe", "cooldown_ms": 15000}] + } + } + } + } + } + } + """, + encoding="utf-8", + ) + + rendered = module.render(template_path, profile_path, [overlay1_path, overlay2_path]) + alarm = rendered["instances"][0]["override"]["nodes"]["alarm"] + self.assertTrue(alarm["face_debug"]["log_unknown_candidates"]) + self.assertEqual(alarm["face_debug"]["unknown_candidate_interval_ms"], 0) + self.assertEqual(alarm["rules"][0]["cooldown_ms"], 15000) + if __name__ == "__main__": unittest.main()