Replace the NotImplementedError stub with a working HailoPoseAdapter for Pi5 + Hailo-8. yolov8s_pose has no on-chip NMS, so the adapter does host-side DFL box decode + keypoint decode + greedy NMS + letterbox un-map + COCO-17 assembly (numpy decode ported from Hailo's MIT PoseEstPostProcessing, locked by a synthetic 9-tensor unit test; the HailoRT device wiring is gated + device-validated on bringup). daemon.main() gains a POSE_BACKEND switch (hailo|movenet).
64 lines
2.4 KiB
Python
64 lines
2.4 KiB
Python
"""TDD for HailoPoseAdapter — locks the yolov8s_pose host decode (DFL box + keypoint
|
|
decode + NMS + COCO-17 assembly + letterbox un-mapping) WITHOUT hardware/hailort,
|
|
by injecting a fake infer_fn that returns synthetic raw output tensors.
|
|
|
|
yolov8s_pose emits 9 raw tensors (no on-chip NMS): per stride (8/16/32) a box(H,W,64
|
|
DFL), score(H,W,1 already-sigmoid), and kpts(H,W,51 = 17*(x,y,score)).
|
|
"""
|
|
import numpy as np
|
|
|
|
from edge.adapters.hailo_pose import HailoPoseAdapter
|
|
|
|
INPUT = 640
|
|
STRIDES = (8, 16, 32)
|
|
|
|
|
|
def _blank_outputs():
|
|
outs = []
|
|
for s in STRIDES:
|
|
size = INPUT // s
|
|
outs.append(np.zeros((1, size, size, 64), np.float32)) # box DFL
|
|
outs.append(np.zeros((1, size, size, 1), np.float32)) # class score (sigmoid'd)
|
|
outs.append(np.zeros((1, size, size, 51), np.float32)) # keypoints
|
|
return outs
|
|
|
|
|
|
def _one_person_outputs():
|
|
outs = _blank_outputs()
|
|
# activate a single cell on the stride-8 map (size 80), grid cell (40, 40)
|
|
box, score, kpts = outs[0], outs[1], outs[2]
|
|
r = c = 40
|
|
score[0, r, c, 0] = 0.9 # person prob (already sigmoid on-chip)
|
|
# DFL: one-hot each of the 4 sides at bin index 2 -> distance = 2 cells * stride8 = 16 px
|
|
b = box[0, r, c].reshape(4, 16)
|
|
b[:, 2] = 50.0
|
|
box[0, r, c] = b.reshape(64)
|
|
# keypoints: raw x=y=0.5, raw score=3.0 for all 17 joints
|
|
k = kpts[0, r, c].reshape(17, 3)
|
|
k[:, 0] = 0.5
|
|
k[:, 1] = 0.5
|
|
k[:, 2] = 3.0
|
|
kpts[0, r, c] = k.reshape(51)
|
|
return outs
|
|
|
|
|
|
def test_infer_maps_yolov8_pose_to_coco17_person():
|
|
frame = np.zeros((640, 640, 3), np.uint8) # already 640x640 -> scale 1, no padding
|
|
outs = _one_person_outputs()
|
|
a = HailoPoseAdapter("unused.hef", infer_fn=lambda f: outs)
|
|
persons = a.infer(frame)
|
|
assert len(persons) == 1
|
|
p = persons[0]
|
|
assert len(p.keypoints) == 17
|
|
# grid center = (40 + 0.5) * 8 = 324 ; box +/- 16 -> [308, 340]
|
|
assert abs(p.bbox.x1 - 308) < 2 and abs(p.bbox.y1 - 308) < 2
|
|
assert abs(p.bbox.x2 - 340) < 2 and abs(p.bbox.y2 - 340) < 2
|
|
# keypoint decode: stride*(2*0.5 - 0.5) + 324 = 8*0.5 + 324 = 328
|
|
assert abs(p.keypoints[0].x - 328) < 2 and abs(p.keypoints[0].y - 328) < 2
|
|
# joint score sigmoided: sigmoid(3.0) ~= 0.9526
|
|
assert abs(p.keypoints[0].conf - 0.9526) < 0.01
|
|
|
|
|
|
def test_no_detection_returns_empty():
|
|
a = HailoPoseAdapter("unused.hef", infer_fn=lambda f: _blank_outputs())
|
|
assert a.infer(np.zeros((640, 640, 3), np.uint8)) == []
|