Three keypoint-trust hardening fixes that reuse the codebase's existing "visible" confidence convention (no new tuning knobs): - P2-9 zone.py: _foot averaged both ankles unconditionally, so a hallucinated ankle (conf~0, often pinned to (0,0)) dragged the foot point into or out of a zone. Now averages only ankles with conf >= 0.2 and falls back to the bbox bottom-centre when neither is trustworthy. - P2-11 geometry.py: head_below_hips used the NOSE regardless of confidence. Now returns False when the nose is below the visibility threshold, so an unconfident/hallucinated nose can't fire the head-below-hips fall cue. The confident-nose path is unchanged (test_fall.py stays green). - P2-10 movenet_pose.py: the CPU MoveNet fallback is single-person, so at a multi-worker site every worker but one is silently invisible to fall/zone/ghost detection. Construction now logs a loud startup WARNING pointing to the Hailo backend. Tests: test_zone (bbox fallback), test_geometry (low-conf nose), and test_movenet_pose (caplog warning). Full python suite: 157 passed, 2 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Xmn8KZWqiQUguEMRtn6rW
40 lines
1.7 KiB
Python
40 lines
1.7 KiB
Python
import logging
|
|
import numpy as np
|
|
from edge.adapters.movenet_pose import MoveNetPoseAdapter
|
|
|
|
class _FakeSession:
|
|
"""MoveNet 출력 형태 [1,1,17,3] (y,x,score) 정규화."""
|
|
def get_inputs(self):
|
|
class I: name = "input"
|
|
return [I()]
|
|
def run(self, _out, feed):
|
|
kp = np.zeros((1, 1, 17, 3), dtype=np.float32)
|
|
for j in range(17):
|
|
kp[0, 0, j] = [0.5, 0.25, 0.9] # y=0.5, x=0.25, score=0.9
|
|
return [kp]
|
|
|
|
def test_infer_maps_movenet_to_coco17_persons():
|
|
frame = np.zeros((400, 800, 3), dtype=np.uint8)
|
|
a = MoveNetPoseAdapter("unused.onnx", session=_FakeSession())
|
|
persons = a.infer(frame)
|
|
assert len(persons) == 1
|
|
p = persons[0]
|
|
assert len(p.keypoints) == 17
|
|
# x = 0.25 * 800 = 200, y = 0.5 * 400 = 200
|
|
assert abs(p.keypoints[0].x - 200) < 1 and abs(p.keypoints[0].y - 200) < 1
|
|
assert abs(p.keypoints[0].conf - 0.9) < 1e-6
|
|
assert p.bbox.x1 <= 200 <= p.bbox.x2
|
|
|
|
def test_low_score_person_dropped():
|
|
class Low(_FakeSession):
|
|
def run(self, _o, feed):
|
|
return [np.zeros((1, 1, 17, 3), dtype=np.float32)] # all score 0
|
|
a = MoveNetPoseAdapter("unused.onnx", session=Low())
|
|
assert a.infer(np.zeros((10, 10, 3), np.uint8)) == []
|
|
|
|
def test_warns_that_movenet_is_single_person(caplog):
|
|
# MoveNet tracks only ONE person; at a multi-worker site the rest are silently invisible to
|
|
# every detector. Construction must warn loudly so the operator knows to use the Hailo backend.
|
|
with caplog.at_level(logging.WARNING, logger="edge.pose"):
|
|
MoveNetPoseAdapter("unused.onnx", session=_FakeSession())
|
|
assert any("SINGLE-PERSON" in r.getMessage() for r in caplog.records)
|