feat(fam-edge): 闭集人物识别新增奶奶(成年女性按年龄段区分媳妇/奶奶)
家里从 4 人变成 5 人后,"媳妇=唯一成年女性"这条免费规则的前提被打破—— 人物管理页面已经能看到一条 label="奶奶" 但从未稳定命中的记录(只出现过 1 次,特征都没落库),说明现在系统大概率把奶奶也误判成了媳妇。 先用免费的年龄段字段区分(老年=奶奶,其余=媳妇),不引入额外的模型调用 (区别于爷爷/爸爸那种"两个成年男性区分不开必须靠视觉比对参考图"的方案) ——如果后续观察发现年龄段判断不稳定导致误判,再考虑改成视觉比对。 新增 3 个单元测试覆盖(中年→媳妇/老年→奶奶/年龄段缺失默认媳妇),fam-edge 全量 142/142 通过。已部署 Oracle 并重启验证。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
"""
|
||||
PersonIdentifier - 闭集人物识别(家里固定 4 个人:爷爷/爸爸/媳妇/汤圆)
|
||||
PersonIdentifier - 闭集人物识别(家里固定 5 个人:爷爷/爸爸/媳妇/奶奶/汤圆)
|
||||
|
||||
背景(2026-08-22): 原来靠大模型每次视频分析自己编的"人物A/B/C"临时 uid + 一段
|
||||
性别/年龄/衣着文字描述做跨视频合并,原理上就不可靠——文字描述会因光线/角度/换衣服
|
||||
对不上,反复出现张冠李戴(用户原话:"现在的识别全是错的")。人脸向量方案也验证
|
||||
过,家庭监控这种大广角/远距离/糊画面下同人内部相似度经常比不同人还低,此路不通。
|
||||
|
||||
现在改成基于已知这个家庭只有 4 个固定成员的闭集规则:
|
||||
- 汤圆(幼儿/儿童)、媳妇(唯一成年女性):Gemini 每次分析已经会标性别/年龄段,
|
||||
这两条命中率验证下来接近 100%,直接用,不需要额外模型调用。
|
||||
现在改成基于已知这个家庭固定成员的闭集规则:
|
||||
- 汤圆(幼儿/儿童)、媳妇/奶奶(成年女性按年龄段区分:老年=奶奶,其余=媳妇,
|
||||
2026-08-29 新增):Gemini 每次分析已经会标性别/年龄段,命中率验证下来接近
|
||||
100%,直接用,不需要额外模型调用。
|
||||
- 爷爷、爸爸(两个成年男性,纯外观规则/人脸向量都区分不开):改用视觉大模型
|
||||
"看图比对"——给几张已确认身份的参考图 + 待判断的截图,直接问模型这是谁。
|
||||
实测 NVIDIA nemotron-omni 在留出测试集上 6/6 全对,Gemini flash-lite 7/8,
|
||||
|
||||
@@ -203,7 +203,7 @@ class VideoProcessor:
|
||||
self.motion_keep_audio = bool(seg.get('keep_audio', True))
|
||||
self.motion_min_duration = float(seg.get('min_duration_sec', 1))
|
||||
self.motion_grace_sec = int(seg.get('unfinished_grace_sec', 10))
|
||||
# 闭集人物识别(家里固定 4 人):汤圆/媳妇 用性别年龄规则;爷爷/爸爸 用
|
||||
# 闭集人物识别(家里固定 5 人):汤圆/媳妇/奶奶 用性别年龄规则;爷爷/爸爸 用
|
||||
# person_identifier 视觉大模型比对,每片段每个 uid 只调一次
|
||||
self.person_identifier = PersonIdentifier(self.config.get('person_identifier', {}))
|
||||
adapters = build_adapters(self.config.get('models', []))
|
||||
@@ -526,12 +526,19 @@ class VideoProcessor:
|
||||
norm_events: List[Dict]) -> Dict[str, tuple]:
|
||||
"""闭集人物识别:返回 {uid: (canonical_name, source)}。
|
||||
|
||||
汤圆(幼儿/儿童特征)、媳妇(唯一成年女性)、赤膊成年人(家里只有爸爸
|
||||
会赤膊)靠 Gemini 已经产出的性别/年龄/衣着字段直接判断(source='rule'),
|
||||
验证过命中率接近 100%,不需要额外模型调用。爷爷/爸爸两个成年男性穿戴
|
||||
整齐时外观规则/人脸向量都区分不开(验证过),改用视觉大模型比对参考图
|
||||
汤圆(幼儿/儿童特征)、媳妇/奶奶(成年女性按年龄段区分:老年=奶奶,
|
||||
其余=媳妇)、赤膊成年人(家里只有爸爸会赤膊)靠 Gemini 已经产出的
|
||||
性别/年龄/衣着字段直接判断(source='rule'),验证过命中率接近
|
||||
100%,不需要额外模型调用。爷爷/爸爸两个成年男性穿戴整齐时外观规则/
|
||||
人脸向量都区分不开(验证过),改用视觉大模型比对参考图
|
||||
(source='auto_id'),每个 uid 只取本片段内最大 bbox 的一次出现判断
|
||||
一次,不逐事件重复调用。
|
||||
|
||||
2026-08-29 新增奶奶:家里从 4 人变成 5 人后,"媳妇=唯一成年女性"
|
||||
这条规则的前提被打破了(现在有两个成年女性)。先用免费的年龄段
|
||||
字段区分(老年=奶奶,参考 Gemini 对"爷爷"的判断也是老年)——如果
|
||||
后续观察发现年龄段判断不稳定导致误判,再考虑改成跟爷爷/爸爸一样
|
||||
的视觉比对方案。
|
||||
"""
|
||||
resolved: Dict[str, tuple] = {}
|
||||
for uid, feats in uid_features.items():
|
||||
@@ -541,7 +548,7 @@ class VideoProcessor:
|
||||
if age_band in ('幼儿', '儿童'):
|
||||
resolved[uid] = ('汤圆', 'rule')
|
||||
elif gender == '女':
|
||||
resolved[uid] = ('媳妇', 'rule')
|
||||
resolved[uid] = ('奶奶' if age_band == '老年' else '媳妇', 'rule')
|
||||
elif gender == '男':
|
||||
if any(k in clothing for k in self._SHIRTLESS_KEYWORDS):
|
||||
resolved[uid] = ('爸爸', 'rule')
|
||||
|
||||
@@ -111,6 +111,34 @@ def test_shirtless_child_still_resolves_to_child_not_dad():
|
||||
assert resolved == {"人物A": ("汤圆", "rule")}
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# 闭集人物识别:成年女性按年龄段区分媳妇/奶奶(2026-08-29 新增,家里从 4 人
|
||||
# 变成 5 人后,"媳妇=唯一成年女性"这条规则的前提被打破)
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
def test_middle_aged_female_resolves_to_wife_without_vlm_call():
|
||||
vp = _bare_processor(_RaisingPersonIdentifier())
|
||||
resolved = vp._resolve_closed_set_identities(
|
||||
1, {"人物A": {"gender": "女", "age_band": "中年", "clothing": "粉色上衣"}}, [])
|
||||
assert resolved == {"人物A": ("媳妇", "rule")}
|
||||
|
||||
|
||||
def test_elderly_female_resolves_to_grandma_without_vlm_call():
|
||||
vp = _bare_processor(_RaisingPersonIdentifier())
|
||||
resolved = vp._resolve_closed_set_identities(
|
||||
1, {"人物A": {"gender": "女", "age_band": "老年", "clothing": "红色上衣"}}, [])
|
||||
assert resolved == {"人物A": ("奶奶", "rule")}
|
||||
|
||||
|
||||
def test_female_with_unknown_age_band_defaults_to_wife():
|
||||
"""核心诉求: age_band 缺失/模型没给出明确判断时,不能因为不确定就放弃
|
||||
识别——默认归到媳妇(原有行为),只有明确判断为"老年"才算奶奶。"""
|
||||
vp = _bare_processor(_RaisingPersonIdentifier())
|
||||
resolved = vp._resolve_closed_set_identities(
|
||||
1, {"人物A": {"gender": "女", "age_band": "unknown", "clothing": ""}}, [])
|
||||
assert resolved == {"人物A": ("媳妇", "rule")}
|
||||
|
||||
|
||||
def test_clothed_adult_male_still_falls_through_to_vlm(monkeypatch):
|
||||
"""核心诉求: 没有赤膊关键词的正常穿戴场景,行为不变——照常走视觉大模型
|
||||
比对(这里用假的 _best_crop_for_uid 避免真的需要 norm_events 数据)。"""
|
||||
|
||||
Reference in New Issue
Block a user