hermes_bringup starts the face, camera, face detector and audio levels from one launch file with their parameters in robot.yaml, each node its own respawned process, the face first and the others after start_delay. robot.service runs it at boot, so everything is ROS and the face is up whenever the robot is powered. Reboot-tested: all services and nodes return by themselves, the face draws at about 28 s on the boot clock, the mic gain survives, nothing throttles. Cut the CPU: the camera runs at 15 fps (20% of a core, vs 40% at 30), and the detector went from 131% to 40% by pinning OpenCV to one thread (its DNN burns 3-4x its elapsed time across cores), decoding the JPEG at half size, and re-identifying a tracked face at most once a second. The audio scope is published only while someone is speaking or listening. A standalone face unit that skipped `ros2 launch` saved only 3.7 s and was dropped: the owner prefers everything in ROS and is fine with up to a minute. trim-boot.sh disables the unused snapd and ModemManager. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AFN8nRRCiDiMN7ChBpHJhs
41 lines
1.6 KiB
Python
41 lines
1.6 KiB
Python
from hermes_vision.detect import Face
|
|
from hermes_vision.recognize import IdentityCache, iou
|
|
|
|
|
|
def test_iou():
|
|
a = Face(0, 0, 100, 100)
|
|
assert iou(a, a) == 1.0
|
|
assert iou(a, Face(200, 200, 50, 50)) == 0.0
|
|
assert 0.3 < iou(a, Face(50, 0, 100, 100)) < 0.4
|
|
|
|
|
|
def test_a_tracked_face_is_identified_once_per_period():
|
|
calls = []
|
|
cache = IdentityCache(period_s=1.0)
|
|
ident = lambda f: (calls.append(f), 'kyle')[1]
|
|
assert cache.update([Face(100, 100, 80, 80)], 0.0, ident) == ['kyle']
|
|
for i in range(1, 8): # the face drifts a little, 4 times a second
|
|
assert cache.update([Face(100 + i, 100, 80, 80)], i * 0.125, ident) == ['kyle']
|
|
assert len(calls) == 1
|
|
cache.update([Face(108, 100, 80, 80)], 1.1, ident) # stale: rechecked
|
|
assert len(calls) == 2
|
|
|
|
|
|
def test_a_new_face_is_identified_immediately_and_unknown_is_cached_too():
|
|
calls = []
|
|
cache = IdentityCache()
|
|
ident = lambda f: (calls.append(f), '' if f.x > 400 else 'kyle')[1]
|
|
names = cache.update([Face(100, 100, 80, 80), Face(500, 100, 80, 80)], 0.0, ident)
|
|
assert names == ['kyle', ''] and len(calls) == 2
|
|
assert cache.update([Face(101, 100, 80, 80), Face(501, 100, 80, 80)], 0.3, ident) == ['kyle', '']
|
|
assert len(calls) == 2 # the stranger is not re-embedded every frame
|
|
|
|
|
|
def test_a_face_that_jumps_is_a_different_face():
|
|
calls = []
|
|
cache = IdentityCache()
|
|
ident = lambda f: (calls.append(f), 'kyle')[1]
|
|
cache.update([Face(100, 100, 80, 80)], 0.0, ident)
|
|
cache.update([Face(600, 300, 80, 80)], 0.1, ident)
|
|
assert len(calls) == 2
|